findOrCreateWallet($user); return (float) $wallet->current_balance; } public function credit( User $user, float $amount, string $sourceType, ?int $sourceId = null, ?string $idempotencyKey = null, ?array $metadata = null, ): WalletTransaction { return $this->applyMovement($user, 'credit', $amount, $sourceType, $sourceId, $idempotencyKey, $metadata); } public function debit( User $user, float $amount, string $sourceType, ?int $sourceId = null, ?string $idempotencyKey = null, ?array $metadata = null, ): WalletTransaction { return $this->applyMovement($user, 'debit', $amount, $sourceType, $sourceId, $idempotencyKey, $metadata); } private function applyMovement( User $user, string $type, float $amount, string $sourceType, ?int $sourceId, ?string $idempotencyKey, ?array $metadata, ): WalletTransaction { if ($amount <= 0) { throw new \InvalidArgumentException('Le montant doit ĂȘtre strictement positif.'); } return DB::transaction(function () use ($user, $type, $amount, $sourceType, $sourceId, $idempotencyKey, $metadata) { if ($idempotencyKey !== null) { $existing = WalletTransaction::query() ->where('idempotency_key', $idempotencyKey) ->first(); if ($existing !== null) { return $existing; } } $wallet = Wallet::query() ->where('user_id', $user->id) ->lockForUpdate() ->first(); if ($wallet === null) { $wallet = $this->createWallet($user); $wallet = Wallet::query()->whereKey($wallet->id)->lockForUpdate()->firstOrFail(); } if (! $wallet->isActive()) { throw new \RuntimeException('Le porte-monnaie n\'est pas actif.'); } $balanceBefore = (float) $wallet->current_balance; if ($type === 'debit' && $balanceBefore < $amount) { throw new WalletInsufficientFundsException($amount, $balanceBefore); } $balanceAfter = $type === 'credit' ? round($balanceBefore + $amount, 2) : round($balanceBefore - $amount, 2); $wallet->update(['current_balance' => $balanceAfter]); return WalletTransaction::query()->create([ 'uuid' => (string) Str::uuid(), 'wallet_id' => $wallet->id, 'type' => $type, 'amount' => $amount, 'balance_before' => $balanceBefore, 'balance_after' => $balanceAfter, 'source_type' => $sourceType, 'source_id' => $sourceId, 'idempotency_key' => $idempotencyKey, 'metadata' => $metadata, 'created_at' => now(), ]); }); } private function findOrCreateWallet(User $user): Wallet { return Wallet::query()->firstOrCreate( ['user_id' => $user->id], ['currency' => 'EUR', 'current_balance' => 0, 'status' => 'active'], ); } private function createWallet(User $user): Wallet { return Wallet::query()->create([ 'user_id' => $user->id, 'currency' => 'EUR', 'current_balance' => 0, 'status' => 'active', ]); } }