From 885e51a79b988a0675bb67701a4c2cb902f4a682 Mon Sep 17 00:00:00 2001 From: safavimohadeseh78 Date: Sun, 8 Nov 2026 13:26:28 +0330 Subject: [PATCH] feat: add deposit receipt upload and admin approval/rejection workflow with SMS --- .../Controllers/Admin/WalletController.php | 163 ++++++++++++++++++ .../Controllers/Customer/WalletController.php | 90 ++++++++++ app/Models/Withdraw.php | 18 +- ...osit_receipt_fields_to_withdraws_table.php | 51 ++++++ routes/admin.php | 2 + routes/customer.php | 1 + 6 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 database/migrations/2026_11_08_000001_add_deposit_receipt_fields_to_withdraws_table.php diff --git a/app/Http/Controllers/Admin/WalletController.php b/app/Http/Controllers/Admin/WalletController.php index ecd3ff9..02557b5 100644 --- a/app/Http/Controllers/Admin/WalletController.php +++ b/app/Http/Controllers/Admin/WalletController.php @@ -1535,4 +1535,167 @@ class WalletController extends Controller ); } + public function approveDeposit(Request $request) + { + $request->validate([ + 'id' => 'required|integer|exists:withdraws,id', + 'tracked_number' => 'nullable|string|max:100', + ]); + + $withdraw = Withdraw::with(['customer', 'Transaction'])->findOrFail($request->id); + + if ((int) $withdraw->sts === 2) { + return response()->json(['message' => 'این تراکنش قبلاً تایید شده است.'], 422); + } + + $customer = $withdraw->customer ?: Customer::find($withdraw->customer_id); + if (!$customer) { + return response()->json(['message' => 'مشتری مربوطه یافت نشد.'], 404); + } + + $adminId = auth('user')->id() ?: auth()->id(); + $amount = (float) $withdraw->cash; + + DB::transaction(function () use ($withdraw, $customer, $adminId, $amount, $request) { + $withdraw->sts = 2; // واریز شد + $withdraw->user_id = $adminId; + if ($request->filled('tracked_number')) { + $withdraw->tracked_number = trim((string) $request->tracked_number); + } + if (empty($withdraw->date_at)) { + $withdraw->date_at = now(); + } + $withdraw->save(); + + $hasExistingCredit = Transaction::where('withdraw_id', $withdraw->id) + ->where('cash', '>', 0) + ->exists(); + + if (!$hasExistingCredit && $amount > 0) { + $walletInterface = new WalletInterface((int) $customer->id, (int) $customer->type); + new TransactionInterface($walletInterface, [ + 'gold' => 0, + 'cash' => $amount, + 'expire_at' => now(), + 'note' => $withdraw->note ?: 'واریز نقدی (فیش بانکی)', + 'withdraw_id' => $withdraw->id, + 'active' => true, + ], 3, true, true); + + $this->recordAccountingTransaction( + $customer, + $withdraw, + (int) $adminId, + $amount, + 0, + $withdraw->note ?: 'واریز نقدی (فیش بانکی)' + ); + + if (app(ConfigService::class)->isTahesabAccounting()) { + try { + app(TahesabController::class)->wallet([ + 'customer_id' => $customer->id, + 'value' => $amount, + 'direction' => 0, + 'withdraw_id' => $withdraw->id, + 'note' => $withdraw->note ?: 'شارژ حساب (فیش بانکی)', + ]); + } catch (\Throwable $e) { report($e); } + } + + if (app(ConfigService::class)->isZarinAccounting()) { + try { + app(\App\Http\Controllers\Admin\ZarinController::class)->wallet([ + 'customer_id' => $customer->id, + 'value' => $amount, + 'direction' => 0, + 'withdraw_id' => $withdraw->id, + 'note' => $withdraw->note ?: 'شارژ حساب (فیش بانکی)', + ]); + } catch (\Throwable $e) { report($e); } + } + + if (app(ConfigService::class)->isShayeganAccounting()) { + try { + app(\App\Http\Controllers\Admin\ShayeganController::class)->wallet([ + 'customer_id' => $customer->id, + 'value' => $amount, + 'direction' => 0, + 'withdraw_id' => $withdraw->id, + 'note' => $withdraw->note ?: 'شارژ حساب (فیش بانکی)', + ]); + } catch (\Throwable $e) { report($e); } + } + + try { + app(PargarSyncService::class)->queueWalletDocument( + $customer, + 'cash', + $amount, + [ + 'withdraw_id' => $withdraw->id, + 'note' => $withdraw->note ?: 'واریز نقدی', + 'source' => 'deposit_receipt', + ] + ); + } catch (\Throwable $e) { report($e); } + } + }); + + $this->broadcastCustomerWalletAssets($customer); + + try { + $appName = app(ConfigService::class)->getString('name_fa', 'سامانه'); + $amountFormatted = number_format((int) $amount); + $trackedInfo = $withdraw->tracked_number ? "\nکد پیگیری: {$withdraw->tracked_number}" : ''; + $smsMsg = "مشتری گرامی {$customer->name}\nدرخواست واریز شما به مبلغ {$amountFormatted} ریال تایید و به موجودی کیف پول شما افزوده شد.{$trackedInfo}\n{$appName}"; + Controller::SendMessage($customer->mobile, $smsMsg, true, 'sms'); + } catch (\Throwable $e) { + report($e); + } + + return response()->json([ + 'message' => 'درخواست واریز با موفقیت تایید و مبلغ به کیف پول مشتری واریز شد.', + 'withdraw' => $withdraw->fresh(['customer', 'Transaction']), + ]); + } + + public function rejectDeposit(Request $request) + { + $request->validate([ + 'id' => 'required|integer|exists:withdraws,id', + 'reject_reason' => 'nullable|string|max:500', + ]); + + $withdraw = Withdraw::with('customer')->findOrFail($request->id); + + if ((int) $withdraw->sts === 2) { + return response()->json(['message' => 'این تراکنش قبلاً تایید شده و امکان رد آن وجود ندارد.'], 422); + } + + $adminId = auth('user')->id() ?: auth()->id(); + $withdraw->sts = 3; // رد شده + $withdraw->user_id = $adminId; + $withdraw->reject_reason = $request->reject_reason ? trim((string) $request->reject_reason) : null; + $withdraw->save(); + + $customer = $withdraw->customer ?: Customer::find($withdraw->customer_id); + if ($customer) { + try { + $appName = app(ConfigService::class)->getString('name_fa', 'سامانه'); + $amountFormatted = number_format((int) $withdraw->cash); + $reasonText = $withdraw->reject_reason ? "\nعلت رد: {$withdraw->reject_reason}" : ''; + $smsMsg = "مشتری گرامی {$customer->name}\nدرخواست واریز شما به مبلغ {$amountFormatted} ریال متأسفانه رد شد.{$reasonText}\n{$appName}"; + Controller::SendMessage($customer->mobile, $smsMsg, true, 'sms'); + } catch (\Throwable $e) { + report($e); + } + } + + return response()->json([ + 'message' => 'درخواست واریز رد شد و پیامک به مشتری ارسال گردید.', + 'withdraw' => $withdraw->fresh(['customer']), + ]); + } + } diff --git a/app/Http/Controllers/Customer/WalletController.php b/app/Http/Controllers/Customer/WalletController.php index cac5300..308e775 100644 --- a/app/Http/Controllers/Customer/WalletController.php +++ b/app/Http/Controllers/Customer/WalletController.php @@ -524,6 +524,96 @@ class WalletController extends Controller return response()->json($Withdraw); } + public function depositRequest(Request $request) + { + $customer = auth('customer')->user(); + if (!$customer) { + throw new UnprocessableEntityHttpException('کاربر یافت نشد.'); + } + + $request->validate([ + 'cash' => 'required|numeric|min:1000', + 'receipt' => 'required|file|mimes:jpeg,jpg,png,webp,pdf|max:10240', + 'tracked_number' => 'nullable|string|max:100', + 'extra_note' => 'nullable|string|max:500', + ], [ + 'cash.required' => 'مبلغ واریزی الزامی است.', + 'cash.numeric' => 'مبلغ واریزی باید به صورت عددی باشد.', + 'cash.min' => 'حداقل مبلغ واریزی معتبر نیست.', + 'receipt.required' => 'بارگذاری تصویر فیش واریزی الزامی است.', + 'receipt.file' => 'فایل فیش ارسالی نامعتبر است.', + 'receipt.mimes' => 'فرمت فیش واریزی باید یکی از فرمت‌های jpg, png, webp یا pdf باشد.', + 'receipt.max' => 'حجم فایل فیش واریزی نباید بیش از ۱۰ مگابایت باشد.', + ]); + + $uploaded = $request->file('receipt'); + $now = verta(); + $dirPath = 'receipts/' . $now->year . '/' . sprintf('%02d', $now->month); + $disk = \Illuminate\Support\Facades\Storage::disk('public'); + if (! $disk->exists($dirPath)) { + $disk->makeDirectory($dirPath); + } + + $ext = $uploaded->getClientOriginalExtension() ?: 'jpg'; + $fileName = md5(time() . '_' . $uploaded->getClientOriginalName() . '_' . $customer->id) . '.' . $ext; + $stored = $disk->putFileAs($dirPath, $uploaded, $fileName); + if (! $stored) { + throw new UnprocessableEntityHttpException('خطا در ذخیره تصویر فیش واریزی.'); + } + + $relativePath = 'storage/' . $stored; + + try { + \App\Models\File::create([ + 'original_name' => $uploaded->getClientOriginalName(), + 'ext' => $ext, + 'title' => 'فیش واریز ' . ($customer->name ?? ''), + 'has_thumb' => false, + 'size' => $uploaded->getSize(), + 'src' => $relativePath, + 'user_id' => $customer->id, + ]); + } catch (\Throwable $e) { + report($e); + } + + $nationalCode = trim((string) ($customer->n_code ?? '')); + + $withdraw = Withdraw::create([ + 'customer_id' => $customer->id, + 'cash' => (float) $request->cash, + 'gold' => 0, + 'type' => 1, + 'sts' => 1, + 'tracked_number' => $request->tracked_number ? trim((string) $request->tracked_number) : null, + 'deposit_identifier' => $nationalCode ?: null, + 'receipt' => $relativePath, + 'extra_note' => $request->extra_note ? trim((string) $request->extra_note) : null, + 'note' => 'درخواست واریز نقدی (فیش بانکی)', + 'date_at' => now(), + ]); + + try { + $companyName = $this->Config('name_fa'); + $amountFormatted = number_format((int) $request->cash); + $userName = $customer->name ?? 'مشتری'; + $userMobile = $customer->mobile ?? ''; + $message = "کاربر $userName با شماره موبایل $userMobile درخواست واریز مبلغ {$amountFormatted} ریال با شناسه واریز {$nationalCode} ثبت نمود. با تشکر، $companyName"; + 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')); + } + }); + } catch (\Throwable $e) { + report($e); + } + + return response()->json([ + 'message' => 'درخواست واریز شما با موفقیت ثبت شد و پس از بررسی تایید خواهد شد.', + 'withdraw' => $withdraw->loadMissing('customer'), + ]); + } + private function formatDeliveryQuantity(float $value): string { $rounded = round($value, 3); diff --git a/app/Models/Withdraw.php b/app/Models/Withdraw.php index b8a28c7..1f668cb 100644 --- a/app/Models/Withdraw.php +++ b/app/Models/Withdraw.php @@ -23,15 +23,27 @@ class Withdraw extends Model 'date_at', 'sts', 'type', + 'receipt', + 'deposit_identifier', + 'reject_reason', ]; + protected $appends = [ + 'receipt_url', + ]; + + public function getReceiptUrlAttribute(): ?string + { + return File::publicUrl($this->receipt); + } + static $STS = [ - 1 => 'در انتظار واریز', + 1 => 'در انتظار تایید', 2 => 'واریز شد', 4 => 'واریز توسط ادمین', 5 => 'پاداش معرفی', 9 => 'سود زیر مجموعه', - 3 => 'واریز ناموفق', + 3 => 'رد شده', 50 => 'در انتظار ارسال', 51 => 'ارسال شده', 52 => 'لغو شده', @@ -40,7 +52,7 @@ class Withdraw extends Model public function customer() { - return $this->belongsTo('App\Models\Customer')->select(['id', 'name', 'mobile']); + return $this->belongsTo('App\Models\Customer')->select(['id', 'name', 'mobile', 'n_code']); } public function address() diff --git a/database/migrations/2026_11_08_000001_add_deposit_receipt_fields_to_withdraws_table.php b/database/migrations/2026_11_08_000001_add_deposit_receipt_fields_to_withdraws_table.php new file mode 100644 index 0000000..45fcd7d --- /dev/null +++ b/database/migrations/2026_11_08_000001_add_deposit_receipt_fields_to_withdraws_table.php @@ -0,0 +1,51 @@ +string('receipt', 500)->nullable()->after('tracked_number'); + } + if (! Schema::hasColumn('withdraws', 'deposit_identifier')) { + $table->string('deposit_identifier', 50)->nullable()->after('receipt'); + } + if (! Schema::hasColumn('withdraws', 'reject_reason')) { + $table->text('reject_reason')->nullable()->after('deposit_identifier'); + } + }); + } + + public function down(): void + { + if (! Schema::hasTable('withdraws')) { + return; + } + + Schema::table('withdraws', function (Blueprint $table) { + $cols = []; + if (Schema::hasColumn('withdraws', 'reject_reason')) { + $cols[] = 'reject_reason'; + } + if (Schema::hasColumn('withdraws', 'deposit_identifier')) { + $cols[] = 'deposit_identifier'; + } + if (Schema::hasColumn('withdraws', 'receipt')) { + $cols[] = 'receipt'; + } + if ($cols !== []) { + $table->dropColumn($cols); + } + }); + } +}; + diff --git a/routes/admin.php b/routes/admin.php index 5edda57..02b9742 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -199,6 +199,8 @@ Route::middleware(['auth:user', 'admin.page'])->group(function (){ Route::put('/wallet/withdraw/{id}',[WalletController::class,'updateWithdraw']); Route::get('/wallet/withdraw/{id}',[WalletController::class,'getWithdraw']); Route::post('/wallet/withdraw/complete',[WalletController::class,'completeWithdraw']); + Route::post('/wallet/deposit/approve', [WalletController::class, 'approveDeposit']); + Route::post('/wallet/deposit/reject', [WalletController::class, 'rejectDeposit']); Route::post('/wallet/withdraw_physical/table',[WalletController::class,'indexPhysicalWithdraw']); Route::get('/wallet/withdraw_physical/options/{customerId}',[WalletController::class,'physicalWithdrawOptions']); Route::post('/wallet/withdraw_physical',[WalletController::class,'createPhysicalWithdraw']); diff --git a/routes/customer.php b/routes/customer.php index e5210da..91a3769 100644 --- a/routes/customer.php +++ b/routes/customer.php @@ -119,6 +119,7 @@ Route::middleware(['auth:customer', 'customer.active'])->group(function () use ( Route::post('/wallet/withdraw/table', [WalletController::class, 'indexWithdraw']); Route::post('/wallet/withdraw_physical/table', [WalletController::class, 'index_withdraw_physical']); Route::post('wallet/withdraw/otp', [WalletController::class, 'withdrawOtp']); + Route::post('wallet/deposit_request', [WalletController::class, 'depositRequest']); Route::post('/withdraw/table', [WalletController::class, 'withdrawTable']);