billing: Nebenläufigkeit & Idempotenz härten (H4, Stripe, Boost-Slots)

- CreditTopup-Gutschrift, MAN-Rechnung, Proof-PDF und Stripe-Webhook-
  Spiegelung sperren die betroffene Zeile innerhalb der Transaktion und
  prüfen die Idempotenz erst danach (verhindert Doppelgutschrift bei
  at-least-once-Webhooks, H4).
- Gemeinsamer LocksModelForUpdate-Trait für die häufige Lock-by-Key-Form
  (CreditTopup, ManualInvoice).
- Unique-Index auf invoices.stripe_invoice_id; Boost-Inventar über
  boost_slot_locks serialisiert, verwaiste Kategorie-Locks werden beim
  Löschen der Rubrik entfernt.

Deployment: vor migrate prüfen, ob invoices.stripe_invoice_id bereits
Duplikate enthält (die Unique-Migration bricht sonst bewusst ab).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-14 13:47:21 +00:00
parent b3fdd6f522
commit 7043ea8ad5
14 changed files with 418 additions and 106 deletions

View file

@ -227,7 +227,7 @@ test('the paid webhook depublishes the release and the public page shows a tombs
// „Eingelöste Käufe" auf der Buchungs-Seite.
expect($purchase->fresh()->status)->toBe(SinglePurchaseStatus::Consumed);
expect($purchase->fresh()->consumed_at)->not->toBeNull();
expect($release->statusLogs()->count())->toBeGreaterThan(0);
expect($release->statusLogs()->where('source', 'depublication')->count())->toBe(1);
// Der Käufer erhält genau eine Bestätigung (Replay löst keine zweite aus).
Mail::assertQueued(PressReleaseDepublished::class, function (PressReleaseDepublished $mail) use ($user, $release) {