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:
parent
3295f47556
commit
aae456954a
14 changed files with 418 additions and 106 deletions
|
|
@ -156,6 +156,22 @@ test('agreements with a future period end are not due', function () {
|
|||
expect(app(ManualInvoiceService::class)->duePaymentOptions())->toHaveCount(0);
|
||||
});
|
||||
|
||||
test('a stale due agreement instance cannot be invoiced twice', function () {
|
||||
Carbon::setTestNow('2026-06-12 08:00:00');
|
||||
$agreement = manualAgreement(['current_period_end' => '2026-06-11']);
|
||||
$service = app(ManualInvoiceService::class);
|
||||
|
||||
$firstInvoice = $service->invoiceFor($agreement);
|
||||
$replayedInvoice = $service->invoiceFor($agreement);
|
||||
|
||||
expect($firstInvoice)->not->toBeNull();
|
||||
expect($replayedInvoice)->toBeNull();
|
||||
expect(Invoice::count())->toBe(1);
|
||||
expect($agreement->fresh()->current_period_end->toDateString())->toBe('2026-07-11');
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
test('the command invoices all due agreements', function () {
|
||||
manualAgreement();
|
||||
manualAgreement();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue