firstOrCreate(['user_id' => $user->id]); } public function balance(User $user): int { return (int) ($user->creditWallet()->value('balance_credits') ?? 0); } /** * Schreibt dem User Credits gut (Aufladung, Erstattung oder Gutschrift). */ public function credit( User $user, int $credits, CreditTransactionType $type = CreditTransactionType::Topup, ?string $description = null, ?Model $reference = null, ): CreditTransaction { if ($credits <= 0) { throw new \InvalidArgumentException('Gutschrift muss positiv sein.'); } if (! $type->isCredit()) { throw new \InvalidArgumentException("Buchungsart {$type->value} ist keine Gutschrift."); } return $this->post($user, $credits, $type, $description, $reference); } /** * Belastet die Wallet. Wirft InsufficientCreditsException, wenn das * Guthaben nicht reicht (Mini-Checkout-Signal). */ public function debit( User $user, int $credits, ?string $description = null, ?Model $reference = null, ): CreditTransaction { if ($credits <= 0) { throw new \InvalidArgumentException('Belastung muss positiv sein.'); } return $this->post($user, -$credits, CreditTransactionType::Spend, $description, $reference); } /** * Reicht das Guthaben für eine Belastung? */ public function canAfford(User $user, int $credits): bool { return $this->balance($user) >= $credits; } private function post( User $user, int $signedAmount, CreditTransactionType $type, ?string $description, ?Model $reference, ): CreditTransaction { return DB::transaction(function () use ($user, $signedAmount, $type, $description, $reference): CreditTransaction { $wallet = CreditWallet::query()->firstOrCreate(['user_id' => $user->id]); $wallet = CreditWallet::query()->lockForUpdate()->find($wallet->id); $newBalance = $wallet->balance_credits + $signedAmount; if ($newBalance < 0) { throw new InsufficientCreditsException(abs($signedAmount), $wallet->balance_credits); } $wallet->update(['balance_credits' => $newBalance]); return $wallet->transactions()->create([ 'user_id' => $user->id, 'amount_credits' => $signedAmount, 'balance_after' => $newBalance, 'type' => $type, 'description' => $description, 'reference_type' => $reference?->getMorphClass(), 'reference_id' => $reference?->getKey(), ]); }); } }