- Add Enums: TransactionType, TransactionStatus, PaymentGateway - Migrations: wallets, wallet_transactions, wallet_activity_logs - Models: Wallet, WalletTransaction, WalletActivityLog - Services: WalletService with complete business logic - Controllers: WalletController, PaymentController - Filament Resources: WalletResource, WalletTransactionResource - Artisan Command: ifnex:token for API token generation - Sanctum integration with statefulApi and exception handling - Full API endpoints: balance, transactions, admin-adjust, freeze/unfreeze
326 lines
12 KiB
PHP
326 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Enums\PaymentGateway;
|
|
use App\Enums\TransactionStatus;
|
|
use App\Enums\TransactionType;
|
|
use App\Models\User;
|
|
use App\Models\Wallet;
|
|
use App\Models\WalletActivityLog;
|
|
use App\Models\WalletTransaction;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class WalletService
|
|
{
|
|
/**
|
|
* شارژ کیف پول توسط کاربر (آنلاین از طریق درگاه)
|
|
*/
|
|
public function requestDeposit(
|
|
Wallet $wallet,
|
|
float $amount,
|
|
string $description = 'شارژ آنلاین کیف پول',
|
|
?User $user = null,
|
|
PaymentGateway $gateway = PaymentGateway::ZARINPAL
|
|
): WalletTransaction {
|
|
if ($wallet->isFrozen()) {
|
|
throw new \Exception('کیف پول شما مسدود است. لطفاً با پشتیبانی تماس بگیرید.');
|
|
}
|
|
|
|
$transaction = DB::transaction(function () use ($wallet, $amount, $description, $user, $gateway) {
|
|
$transaction = WalletTransaction::create([
|
|
'wallet_id' => $wallet->id,
|
|
'amount' => $amount,
|
|
'balance_before' => $wallet->balance,
|
|
'balance_after' => $wallet->balance, // هنوز تغییر نکرده
|
|
'type' => TransactionType::DEPOSIT,
|
|
'status' => TransactionStatus::PENDING,
|
|
'gateway' => $gateway,
|
|
'description' => $description,
|
|
'created_by' => $user?->id ?? auth()->id(),
|
|
'ip_address' => request()->ip(),
|
|
'user_agent' => request()->userAgent(),
|
|
]);
|
|
|
|
// لاگ فعالیت
|
|
WalletActivityLog::log(
|
|
$wallet,
|
|
'deposit_requested',
|
|
"درخواست شارژ {$amount} ریال از طریق {$gateway->label()}",
|
|
null,
|
|
['transaction_id' => $transaction->id, 'amount' => $amount],
|
|
$user
|
|
);
|
|
|
|
return $transaction;
|
|
});
|
|
|
|
Log::info('Wallet deposit requested', [
|
|
'wallet_id' => $wallet->id,
|
|
'transaction_id' => $transaction->id,
|
|
'amount' => $amount,
|
|
'gateway' => $gateway->value,
|
|
]);
|
|
|
|
return $transaction;
|
|
}
|
|
|
|
/**
|
|
* تأیید و تکمیل تراکنش شارژ (بعد از callback درگاه)
|
|
*/
|
|
public function completeDeposit(
|
|
WalletTransaction $transaction,
|
|
string $gatewayReferenceId,
|
|
?User $admin = null
|
|
): WalletTransaction {
|
|
if ($transaction->status !== TransactionStatus::PENDING) {
|
|
throw new \Exception('این تراکنش قابل تأیید نیست.');
|
|
}
|
|
|
|
return DB::transaction(function () use ($transaction, $gatewayReferenceId, $admin) {
|
|
$wallet = $transaction->wallet;
|
|
$oldBalance = $wallet->balance;
|
|
|
|
// بهروزرسانی موجودی کیف پول
|
|
$wallet->increment('balance', $transaction->amount);
|
|
$wallet->increment('total_deposited', $transaction->amount);
|
|
|
|
// تأیید تراکنش
|
|
$transaction->update([
|
|
'status' => TransactionStatus::COMPLETED,
|
|
'gateway_reference_id' => $gatewayReferenceId,
|
|
'balance_after' => $wallet->balance,
|
|
'approved_by' => $admin?->id,
|
|
'approved_at' => now(),
|
|
]);
|
|
|
|
// لاگ فعالیت
|
|
WalletActivityLog::log(
|
|
$wallet,
|
|
'deposit_completed',
|
|
"شارژ {$transaction->amount} ریال با موفقیت تکمیل شد. کد پیگیری: {$gatewayReferenceId}",
|
|
['balance' => $oldBalance],
|
|
['balance' => $wallet->balance, 'reference_id' => $gatewayReferenceId],
|
|
$admin
|
|
);
|
|
|
|
Log::info('Wallet deposit completed', [
|
|
'wallet_id' => $wallet->id,
|
|
'transaction_id' => $transaction->id,
|
|
'amount' => $transaction->amount,
|
|
'reference_id' => $gatewayReferenceId,
|
|
]);
|
|
|
|
return $transaction->fresh();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* شارژ دستی توسط ادمین (پرداخت تلفنی یا کارت به کارت)
|
|
*/
|
|
public function manualDeposit(
|
|
Wallet $wallet,
|
|
float $amount,
|
|
string $description,
|
|
User $admin,
|
|
?string $adminNotes = null
|
|
): WalletTransaction {
|
|
if ($wallet->isFrozen()) {
|
|
throw new \Exception('این کیف پول مسدود است.');
|
|
}
|
|
|
|
return DB::transaction(function () use ($wallet, $amount, $description, $admin, $adminNotes) {
|
|
$oldBalance = $wallet->balance;
|
|
|
|
// بهروزرسانی موجودی
|
|
$wallet->increment('balance', $amount);
|
|
$wallet->increment('total_deposited', $amount);
|
|
|
|
// ایجاد تراکنش
|
|
$transaction = WalletTransaction::create([
|
|
'wallet_id' => $wallet->id,
|
|
'amount' => $amount,
|
|
'balance_before' => $oldBalance,
|
|
'balance_after' => $wallet->balance,
|
|
'type' => TransactionType::DEPOSIT,
|
|
'status' => TransactionStatus::COMPLETED,
|
|
'gateway' => PaymentGateway::MANUAL,
|
|
'description' => $description,
|
|
'admin_notes' => $adminNotes,
|
|
'created_by' => $admin->id,
|
|
'approved_by' => $admin->id,
|
|
'approved_at' => now(),
|
|
'ip_address' => request()->ip(),
|
|
'user_agent' => request()->userAgent(),
|
|
]);
|
|
|
|
// لاگ فعالیت
|
|
WalletActivityLog::log(
|
|
$wallet,
|
|
'manual_adjustment',
|
|
"شارژ دستی {$amount} ریال توسط {$admin->name}. توضیحات: {$description}",
|
|
['balance' => $oldBalance],
|
|
['balance' => $wallet->balance, 'transaction_id' => $transaction->id],
|
|
$admin
|
|
);
|
|
|
|
Log::info('Manual wallet deposit', [
|
|
'wallet_id' => $wallet->id,
|
|
'transaction_id' => $transaction->id,
|
|
'amount' => $amount,
|
|
'admin_id' => $admin->id,
|
|
'description' => $description,
|
|
]);
|
|
|
|
return $transaction;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* کسر دستی توسط ادمین (بدهی مشتری یا جریمه)
|
|
*/
|
|
public function manualWithdrawal(
|
|
Wallet $wallet,
|
|
float $amount,
|
|
string $description,
|
|
User $admin,
|
|
?string $adminNotes = null
|
|
): WalletTransaction {
|
|
if ($wallet->isFrozen()) {
|
|
throw new \Exception('این کیف پول مسدود است.');
|
|
}
|
|
|
|
if ($wallet->balance < $amount) {
|
|
throw new \Exception('موجودی کیف پول کافی نیست.');
|
|
}
|
|
|
|
return DB::transaction(function () use ($wallet, $amount, $description, $admin, $adminNotes) {
|
|
$oldBalance = $wallet->balance;
|
|
|
|
// بهروزرسانی موجودی
|
|
$wallet->decrement('balance', $amount);
|
|
$wallet->increment('total_withdrawn', $amount);
|
|
|
|
// ایجاد تراکنش (مبلغ منفی)
|
|
$transaction = WalletTransaction::create([
|
|
'wallet_id' => $wallet->id,
|
|
'amount' => -$amount,
|
|
'balance_before' => $oldBalance,
|
|
'balance_after' => $wallet->balance,
|
|
'type' => TransactionType::WITHDRAWAL,
|
|
'status' => TransactionStatus::COMPLETED,
|
|
'gateway' => PaymentGateway::MANUAL,
|
|
'description' => $description,
|
|
'admin_notes' => $adminNotes,
|
|
'created_by' => $admin->id,
|
|
'approved_by' => $admin->id,
|
|
'approved_at' => now(),
|
|
'ip_address' => request()->ip(),
|
|
'user_agent' => request()->userAgent(),
|
|
]);
|
|
|
|
// لاگ فعالیت
|
|
WalletActivityLog::log(
|
|
$wallet,
|
|
'manual_adjustment',
|
|
"کسر دستی {$amount} ریال توسط {$admin->name}. توضیحات: {$description}",
|
|
['balance' => $oldBalance],
|
|
['balance' => $wallet->balance, 'transaction_id' => $transaction->id],
|
|
$admin
|
|
);
|
|
|
|
Log::info('Manual wallet withdrawal', [
|
|
'wallet_id' => $wallet->id,
|
|
'transaction_id' => $transaction->id,
|
|
'amount' => $amount,
|
|
'admin_id' => $admin->id,
|
|
'description' => $description,
|
|
]);
|
|
|
|
return $transaction;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* پرداخت سفارش از کیف پول
|
|
*/
|
|
public function payOrder(
|
|
Wallet $wallet,
|
|
float $amount,
|
|
string $description,
|
|
$order = null
|
|
): WalletTransaction {
|
|
if ($wallet->isFrozen()) {
|
|
throw new \Exception('کیف پول شما مسدود است.');
|
|
}
|
|
|
|
if (!$wallet->hasSufficientBalance($amount)) {
|
|
throw new \Exception('موجودی کیف پول کافی نیست.');
|
|
}
|
|
|
|
return DB::transaction(function () use ($wallet, $amount, $description, $order) {
|
|
$oldBalance = $wallet->balance;
|
|
|
|
// بهروزرسانی موجودی
|
|
$wallet->decrement('balance', $amount);
|
|
$wallet->increment('total_withdrawn', $amount);
|
|
|
|
// ایجاد تراکنش
|
|
$transaction = WalletTransaction::create([
|
|
'wallet_id' => $wallet->id,
|
|
'amount' => -$amount,
|
|
'balance_before' => $oldBalance,
|
|
'balance_after' => $wallet->balance,
|
|
'type' => TransactionType::ORDER_PAYMENT,
|
|
'status' => TransactionStatus::COMPLETED,
|
|
'gateway' => PaymentGateway::SYSTEM,
|
|
'description' => $description,
|
|
'created_by' => auth()->id(),
|
|
'approved_by' => auth()->id(),
|
|
'approved_at' => now(),
|
|
'transactionable_type' => $order ? get_class($order) : null,
|
|
'transactionable_id' => $order?->id,
|
|
'ip_address' => request()->ip(),
|
|
'user_agent' => request()->userAgent(),
|
|
]);
|
|
|
|
// لاگ فعالیت
|
|
WalletActivityLog::log(
|
|
$wallet,
|
|
'withdrawal_completed',
|
|
"پرداخت سفارش {$amount} ریال",
|
|
['balance' => $oldBalance],
|
|
['balance' => $wallet->balance, 'transaction_id' => $transaction->id]
|
|
);
|
|
|
|
return $transaction;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* شکست تراکنش
|
|
*/
|
|
public function failTransaction(
|
|
WalletTransaction $transaction,
|
|
string $reason
|
|
): WalletTransaction {
|
|
$transaction->fail($reason);
|
|
|
|
// لاگ فعالیت
|
|
WalletActivityLog::log(
|
|
$transaction->wallet,
|
|
$transaction->type === TransactionType::DEPOSIT ? 'deposit_failed' : 'withdrawal_failed',
|
|
"تراکنش {$transaction->id} شکست خورد. دلیل: {$reason}",
|
|
null,
|
|
['transaction_id' => $transaction->id, 'reason' => $reason]
|
|
);
|
|
|
|
Log::warning('Wallet transaction failed', [
|
|
'transaction_id' => $transaction->id,
|
|
'reason' => $reason,
|
|
]);
|
|
|
|
return $transaction->fresh();
|
|
}
|
|
} |