initial commit

This commit is contained in:
bastien
2026-06-28 12:29:53 +02:00
parent 03e85d2f9d
commit b2443b654c
220 changed files with 24969 additions and 1 deletions
+126
View File
@@ -0,0 +1,126 @@
<?php
namespace App\Services;
use App\Exceptions\WalletInsufficientFundsException;
use App\Models\User;
use App\Models\Wallet;
use App\Models\WalletTransaction;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class WalletService
{
public function getBalance(User $user): float
{
$wallet = $this->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',
]);
}
}