*/ public function packs(): array { return array_values(config('credits.packs', [])); } /** * @return array{key: string, price_cents: int, credits: int}|null */ public function findPack(string $key): ?array { foreach ($this->packs() as $pack) { if ($pack['key'] === $key) { return $pack; } } return null; } /** * Legt einen Pending-Topup für ein Paket an (vor dem Stripe-Checkout). */ public function startTopup(User $user, string $packKey): CreditTopup { $pack = $this->findPack($packKey); if (! $pack) { throw new \InvalidArgumentException("Unbekanntes Credit-Paket: {$packKey}."); } return $user->creditTopups()->create([ 'pack_key' => $pack['key'], 'credits' => $pack['credits'], 'price_cents' => $pack['price_cents'], 'currency' => config('billing.default_currency'), 'status' => SinglePurchaseStatus::Pending, ]); } /** * Schreibt einen bezahlten Topup der Wallet gut. Idempotent: bereits * gutgeschriebene Topups (credited_at gesetzt) werden übersprungen. */ public function fulfill(CreditTopup $topup, ?string $sessionId = null, ?string $paymentIntentId = null): void { if ($topup->credited_at !== null) { return; } DB::transaction(function () use ($topup, $sessionId, $paymentIntentId): void { $topup->update([ 'status' => SinglePurchaseStatus::Paid, 'paid_at' => $topup->paid_at ?? now(), 'credited_at' => now(), 'stripe_checkout_session_id' => $sessionId ?? $topup->stripe_checkout_session_id, 'stripe_payment_intent_id' => $paymentIntentId ?? $topup->stripe_payment_intent_id, ]); $this->wallet->credit( $topup->user, $topup->credits, CreditTransactionType::Topup, "Credit-Paket {$topup->pack_key}", $topup, ); }); } }