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.