firstOrCreate( ['user_id' => $user->id], ['balance' => 0, 'is_active' => true], ); } public function credit( Wallet $wallet, int $amount, ?string $description = null, ?User $performedBy = null, ): WalletTransaction { return $this->apply($wallet, WalletTransactionType::Credit, $amount, $description, $performedBy); } public function debit( Wallet $wallet, int $amount, ?string $description = null, ?User $performedBy = null, ): WalletTransaction { return $this->apply($wallet, WalletTransactionType::Debit, $amount, $description, $performedBy); } private function apply( Wallet $wallet, WalletTransactionType $type, int $amount, ?string $description, ?User $performedBy, ): WalletTransaction { if ($amount <= 0) { throw new InvalidArgumentException('مبلغ باید بزرگ‌تر از صفر باشد.'); } return DB::transaction(function () use ($wallet, $type, $amount, $description, $performedBy): WalletTransaction { $wallet = Wallet::query()->lockForUpdate()->findOrFail($wallet->id); if (! $wallet->is_active) { throw new RuntimeException('کیف پول غیرفعال است.'); } $balanceBefore = $wallet->balance; $balanceAfter = $type === WalletTransactionType::Credit ? $balanceBefore + $amount : $balanceBefore - $amount; if ($balanceAfter < 0) { throw new RuntimeException('موجودی کیف پول کافی نیست.'); } $wallet->update(['balance' => $balanceAfter]); return WalletTransaction::query()->create([ 'wallet_id' => $wallet->id, 'type' => $type, 'amount' => $amount, 'balance_before' => $balanceBefore, 'balance_after' => $balanceAfter, 'description' => $description, 'created_by' => $performedBy?->id, ]); }); } }