feat: add deposit receipt upload and admin approval/rejection workflow with SMS
This commit is contained in:
@@ -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']),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -524,6 +524,96 @@ class WalletController extends Controller
|
|||||||
return response()->json($Withdraw);
|
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
|
private function formatDeliveryQuantity(float $value): string
|
||||||
{
|
{
|
||||||
$rounded = round($value, 3);
|
$rounded = round($value, 3);
|
||||||
|
|||||||
+15
-3
@@ -23,15 +23,27 @@ class Withdraw extends Model
|
|||||||
'date_at',
|
'date_at',
|
||||||
'sts',
|
'sts',
|
||||||
'type',
|
'type',
|
||||||
|
'receipt',
|
||||||
|
'deposit_identifier',
|
||||||
|
'reject_reason',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected $appends = [
|
||||||
|
'receipt_url',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function getReceiptUrlAttribute(): ?string
|
||||||
|
{
|
||||||
|
return File::publicUrl($this->receipt);
|
||||||
|
}
|
||||||
|
|
||||||
static $STS = [
|
static $STS = [
|
||||||
1 => 'در انتظار واریز',
|
1 => 'در انتظار تایید',
|
||||||
2 => 'واریز شد',
|
2 => 'واریز شد',
|
||||||
4 => 'واریز توسط ادمین',
|
4 => 'واریز توسط ادمین',
|
||||||
5 => 'پاداش معرفی',
|
5 => 'پاداش معرفی',
|
||||||
9 => 'سود زیر مجموعه',
|
9 => 'سود زیر مجموعه',
|
||||||
3 => 'واریز ناموفق',
|
3 => 'رد شده',
|
||||||
50 => 'در انتظار ارسال',
|
50 => 'در انتظار ارسال',
|
||||||
51 => 'ارسال شده',
|
51 => 'ارسال شده',
|
||||||
52 => 'لغو شده',
|
52 => 'لغو شده',
|
||||||
@@ -40,7 +52,7 @@ class Withdraw extends Model
|
|||||||
|
|
||||||
public function customer()
|
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()
|
public function address()
|
||||||
|
|||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (! Schema::hasTable('withdraws')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::table('withdraws', function (Blueprint $table) {
|
||||||
|
if (! Schema::hasColumn('withdraws', 'receipt')) {
|
||||||
|
$table->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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@@ -199,6 +199,8 @@ Route::middleware(['auth:user', 'admin.page'])->group(function (){
|
|||||||
Route::put('/wallet/withdraw/{id}',[WalletController::class,'updateWithdraw']);
|
Route::put('/wallet/withdraw/{id}',[WalletController::class,'updateWithdraw']);
|
||||||
Route::get('/wallet/withdraw/{id}',[WalletController::class,'getWithdraw']);
|
Route::get('/wallet/withdraw/{id}',[WalletController::class,'getWithdraw']);
|
||||||
Route::post('/wallet/withdraw/complete',[WalletController::class,'completeWithdraw']);
|
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::post('/wallet/withdraw_physical/table',[WalletController::class,'indexPhysicalWithdraw']);
|
||||||
Route::get('/wallet/withdraw_physical/options/{customerId}',[WalletController::class,'physicalWithdrawOptions']);
|
Route::get('/wallet/withdraw_physical/options/{customerId}',[WalletController::class,'physicalWithdrawOptions']);
|
||||||
Route::post('/wallet/withdraw_physical',[WalletController::class,'createPhysicalWithdraw']);
|
Route::post('/wallet/withdraw_physical',[WalletController::class,'createPhysicalWithdraw']);
|
||||||
|
|||||||
@@ -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/table', [WalletController::class, 'indexWithdraw']);
|
||||||
Route::post('/wallet/withdraw_physical/table', [WalletController::class, 'index_withdraw_physical']);
|
Route::post('/wallet/withdraw_physical/table', [WalletController::class, 'index_withdraw_physical']);
|
||||||
Route::post('wallet/withdraw/otp', [WalletController::class, 'withdrawOtp']);
|
Route::post('wallet/withdraw/otp', [WalletController::class, 'withdrawOtp']);
|
||||||
|
Route::post('wallet/deposit_request', [WalletController::class, 'depositRequest']);
|
||||||
|
|
||||||
Route::post('/withdraw/table', [WalletController::class, 'withdrawTable']);
|
Route::post('/withdraw/table', [WalletController::class, 'withdrawTable']);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user