feat: landing settings, instant withdraw deduct, and OTP site URL

Add has_landing plus landing_* deploy keys, deduct balance on withdraw request, keep app_url for SMS login links, and clarify Zarin rial wallet document titles.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-05 17:01:39 +03:30
co-authored by Cursor
parent 1c7ea16300
commit 8cee34bfba
13 changed files with 344 additions and 58 deletions
@@ -17,8 +17,11 @@ use App\Http\Controllers\Admin\TahesabController;
use App\Services\ConfigService;
use App\Services\AssetSummaryService;
use App\Services\WalletBalanceService;
use App\Services\WalletBroadcastService;
use App\Services\ZaarboxPriceService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Rooyesh\Wallet\interfaces\TransactionInterface;
@@ -372,43 +375,72 @@ class WalletController extends Controller
return response()->json(['message' => 'لطفا کارت بانکی را وارد کنید'], 422);
}
$bCart = BankAccount::where('id', $data['card_id'])->first();
if ($bCart->sts != 2) {
if (!$bCart || $bCart->sts != 2) {
return response()->json(['message' => 'کارت انتخاب شده نا معتبر است'], 422);
}
$wallet_interface_fund = new WalletInterface(auth('customer')->id(), auth('customer')->user()->type);
if ($data['value'] < 0) {
$amount = (int) ($data['value'] ?? 0);
if ($amount <= 0) {
throw new UnprocessableEntityHttpException('مقدار برداشت باید مثبت باشد');
}
$this->validateWithdrawAmount((int) $data['value']);
$extraNote = trim((string) ($data['extra_note'] ?? ''));
if (strlen($extraNote) > 500) {
return response()->json(['message' => 'توضیحات اضافی حداکثر ۵۰۰ کاراکتر است'], 422);
}
$Withdraw = Withdraw::create([
'customer_id' => auth('customer')->id(),
"cash" => $data['value'],
"note" => "برداشت ریالی",
'extra_note' => $extraNote !== '' ? $extraNote : null,
"bank_account_id" => $data['card_id'],
'sts' => 1,
'type' => 2
]);
$transaction = new TransactionInterface($wallet_interface_fund, $_inp = [
"cash" => $data['value'],
'expire_at' => now(),
'withdraw_id' => $Withdraw->id,
'active' => true
], 4, false, false);
$otp = (string) ($request->otp ?? '');
if ($otp === '' || (string) ($customer->token_sms ?? '') !== $otp) {
return response()->json(['message' => 'رمز دوم اشتباه میباشد'], 422);
}
$lock = Cache::lock('customer-rial-withdraw:'.$customer->id, 20);
if (!$lock->get()) {
return response()->json(['message' => 'درخواست برداشت در حال انجام است'], 429);
}
try {
$customer->refresh();
if ((string) ($customer->token_sms ?? '') !== $otp) {
return response()->json(['message' => 'رمز دوم اشتباه میباشد'], 422);
}
$this->validateWithdrawAmount($amount);
$Withdraw = DB::transaction(function () use ($customer, $data, $amount, $extraNote) {
$withdraw = Withdraw::create([
'customer_id' => $customer->id,
'cash' => $amount,
'note' => 'برداشت ریالی',
'extra_note' => $extraNote !== '' ? $extraNote : null,
'bank_account_id' => $data['card_id'],
'sts' => 1,
'type' => 2,
]);
$this->debitCustomerRialWithdrawOnce($withdraw, $amount, $customer);
$customer->token_sms = null;
$customer->save();
return $withdraw;
});
} finally {
$lock->release();
}
try {
app(WalletBroadcastService::class)->broadcastForCustomer(
(int) $customer->id,
(int) $customer->type
);
} catch (\Throwable $e) {
report($e);
}
try {
app(\App\Services\PargarSyncService::class)->queueWalletDocument(
$customer,
'cash',
-1 * (float) $data['value'],
-1 * (float) $amount,
[
'withdraw_id' => $Withdraw->id,
'note' => 'برداشت ریالی',
@@ -421,18 +453,18 @@ class WalletController extends Controller
$userMobile = $customer->mobile; // User's mobile number
$userName = $customer->name; // User's name
$withdrawAmount = $data['value']; // amount
$withdrawAmount = $amount; // amount
if (app(ConfigService::class)->isTahesabAccounting()) {
try {
$result = app(TahesabController::class)->wallet([
"customer_id" => auth('customer')->id(),
"value" => $data['value'],
"value" => $amount,
"direction" => 1,
]);
\Log::info('Tahesab withdraw result', [
'customer_id' => auth('customer')->id(),
'value' => $data['value'],
'value' => $amount,
'result' => $result,
]);
} catch (\Exception $e) {
@@ -444,8 +476,10 @@ class WalletController extends Controller
try {
app(\App\Http\Controllers\Admin\ZarinController::class)->wallet([
"customer_id" => auth('customer')->id(),
"value" => $data['value'],
"value" => $amount,
"direction" => 1,
"withdraw_id" => $Withdraw->id,
"note" => 'برداشت ریالی',
]);
} catch (\Exception $e) {
report($e);
@@ -454,11 +488,6 @@ class WalletController extends Controller
$companyName = $this->Config('name_fa'); // Company name from configuration
$message = "کاربر $userName با شماره موبایل $userMobile درخواست برداشت مبلغ " . number_format($withdrawAmount) . " ریال را دارد. با تشکر، $companyName";
if (isset($request->otp)) {
if (Customer::where("id", $customer->id)->first()->token_sms != $request->otp) {
return response()->json(['message' => 'رمز دوم اشتباه میباشد'], 422);
}
}
User::where('send_sms', 1)->get()->each(function ($admin) use ($message) {
if (self::isSmsNotifEnabled('sms_notif_withdraw_admin')) {
$this->Send($admin->mobile, $message . ' ' . now()->format('H:i:s'));
@@ -675,6 +704,25 @@ class WalletController extends Controller
return response()->json(true);
}
private function debitCustomerRialWithdrawOnce(Withdraw $withdraw, int $amount, Customer $customer): void
{
$alreadyDebited = Transaction::query()
->where('withdraw_id', $withdraw->id)
->where('cash', '<', 0)
->exists();
if ($alreadyDebited) {
return;
}
$wallet = new WalletInterface((int) $customer->id, (int) $customer->type);
new TransactionInterface($wallet, [
'cash' => $amount,
'expire_at' => now(),
'withdraw_id' => $withdraw->id,
'active' => true,
], 4, false, false);
}
private function validateWithdrawAmount(int $value): void
{
$wallet_interface_fund = new WalletInterface(auth('customer')->id(), auth('customer')->user()->type);