fix: harden invoice reconciliation

This commit is contained in:
sirily
2026-03-11 12:29:42 +03:00
parent 55383deaf4
commit 7793fc3887
4 changed files with 226 additions and 106 deletions

View File

@@ -117,6 +117,7 @@ Current rule:
- The system must treat an invoice as `paid` only after the payment provider reports a final successful status, meaning the funds are accepted strongly enough for access activation.
- Worker reconciliation now polls provider status for stored `pending` invoices.
- If the provider reports final `paid`, the worker activates access from provider `paidAt` when that timestamp is available.
- If the provider reports final `paid` after a local fallback expiry, provider `paid` still wins and access is activated.
- If the provider reports final `expired` or `canceled`, the worker finalizes the local invoice to the same terminal status.
## Worker reconciliation flow
@@ -128,6 +129,9 @@ Current rule:
6. After provider polling, the worker also expires any still-pending invoices whose local `expiresAt` has elapsed.
7. If a manual admin action or another worker already finalized the invoice, reconciliation degrades to replay/no-op behavior instead of duplicating side effects.
Implementation note:
- invoice creation paths take a per-subscription database lock so manual invoice creation and worker renewal creation do not create duplicate invoices for the same subscription at the same time.
## Invoice listing flow
- `GET /api/billing/invoices` returns the user's invoices ordered by newest first.
- This is a read-only view over persisted `PaymentInvoice` rows.