advanced
Payments and subscriptions
Model checkout, billing state, subscription lifecycle, webhook idempotency, reconciliation, refunds, fraud checks, and failure recovery.
Payments and subscriptions are **distributed workflows** between your app, payment provider (Stripe, Adyen, etc.), and internal entitlement state. Correctness depends on idempotent handlers, durable ledger-like state, webhook verification, reconciliation, and user-visible recovery—not on the browser redirect alone.
| Concern | Pattern | |---------|---------| | Checkout | Server-created session/intent; never trust client amounts | | Billing state | Explicit machine: trialing, active, past_due, canceled, paused | | Webhooks | Verify signatures; process idempotently by provider event ID | | Idempotency | Keys on create/charge APIs; dedupe table for webhook delivery | | Reconciliation | Nightly jobs compare provider reports vs internal ledger | | Refunds & disputes | Audit trail; partial refunds; access revocation rules | | Fraud | Velocity checks, 3DS where required, risk signals from provider |
Happy path: Checkout Session → webhook payment_intent.succeeded →
idempotent entitlement grant → email receipt
Retry path: duplicate webhook → same event_id → no double grant
Never use client-side success pages as source of truth—users close tabs; webhooks retry; networks fail. Store provider customer/subscription IDs and map to your user records.
On interviews: subscription renews but webhook arrives twice. Explain idempotency storage, how you detect drift, and how support can see audit history without manual DB edits.
Common pitfalls: updating a single `subscriptions` row without events; missing past_due handling; no grace period policy; charging without strong customer authentication where regulated; secrets in frontend.
The trade-off is building on provider primitives versus owning more billing logic in-house (invoicing, tax, dunning).
Checklist:
- Webhook handler idempotent and fast-ack with async work queue if needed.
- Internal state derivable from event log.
- Reconciliation alerts on mismatch.
- Support tooling reads event history, not raw tables only.