ifnex/04_Laravel/app/Services/WalletService.php
Kazem Alghasi 78f5919b69 feat(wallet): complete wallet system with admin adjust, freeze/unfreeze, activity logs
- 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
2026-08-07 22:51:42 +03:30

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();
}
}