Wallet-Topup ueber Stripe (Credit-Pakete mit Bonus-Staffel)

End-to-end-Aufladung der Credit-Wallet, spiegelt den Einzel-PM-Fluss:

- Paket-Staffel mit Bonus in config/credits.php (10->10, 25->27, 50->55,
  100->115 Credits; price_cents netto, Stripe Tax ergaenzt USt.)
- credit_topups (Pending->Paid) + CreditTopupService: startTopup legt den
  Pending-Kauf an, fulfill() schreibt die Wallet idempotent gut (credited_at)
- StripeCheckoutService::forCreditTopup via checkoutCharge + credit_topup_id
  Metadata; ProcessStripeWebhook schreibt bei checkout.session.completed gut
- Checkout-Route /admin/me/checkout/credits/{pack} mit Rechnungsadress-Gate

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-06-17 14:56:23 +00:00
parent 69411b4c87
commit 344aac0740
10 changed files with 382 additions and 1 deletions

View file

@ -6,6 +6,7 @@ use App\Enums\SinglePurchaseStatus;
use App\Enums\SinglePurchaseType;
use App\Models\Plan;
use App\Models\SinglePurchase;
use App\Services\Billing\CreditTopupService;
use App\Services\Billing\StripeCheckoutService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@ -68,6 +69,26 @@ class CheckoutController extends Controller
return $this->checkout->forSinglePurchase($request->user(), $purchase);
}
/**
* Wallet-Aufladung über ein Credit-Paket. Wie jeder Checkout setzt das
* eine vollständige Rechnungsadresse voraus; die Erfüllung (Gutschrift)
* übernimmt der Webhook über die `credit_topup_id`-Metadaten.
*/
public function creditTopup(Request $request, string $pack, CreditTopupService $topups): Checkout|RedirectResponse
{
if (! $request->user()->hasCompleteBillingAddress()) {
return $this->backToProfile();
}
if (! $topups->findPack($pack)) {
return $this->backToBookings(__('Dieses Credit-Paket ist nicht verfügbar.'));
}
$topup = $topups->startTopup($request->user(), $pack);
return $this->checkout->forCreditTopup($request->user(), $topup);
}
/**
* Stripe Billing Portal: Selbstverwaltung des Abos (Zahlungsmethode,
* Rechnungen, Kündigung). Nur mit aktivem Abo sinnvoll.