Subscriptions and recurring billing
Designing SaaS and consumer subscription billing: plans, prices and entitlements, billing cycles and invoices, proration on upgrades and downgrades, usage-based billing and metering, payment retries and dunning, trials, cancellations, taxes, and keeping billing and access in sync.
Reading is half of it. See this used in a real interview: walk through Design a Payment System →
Charging a card once is a payment problem. Charging the right amount every month, through plan changes, failed cards, trials, usage spikes and cancellations, while making sure access matches what the customer paid for, is a billing system. Subscription products (SaaS, streaming, AI APIs) depend on it, and interviewers use it to test state machines, scheduling, idempotency and money correctness together.
The core model
| Entity | Holds |
|---|---|
| Product and plan | what is sold and its features |
| Price | amount, currency, interval (monthly, yearly), or usage-based tiers; versioned, never edited in place |
| Customer | billing details, payment methods, tax information |
| Subscription | customer, price(s), status, current period start and end, trial and cancellation settings |
| Invoice | the amount due for a period, with line items |
| Payment | attempts to collect an invoice |
| Entitlement | what the customer may use right now |
Prices are immutable versions: changing a price creates a new one, and existing subscriptions keep theirs unless migrated deliberately.
Subscription lifecycle
A state machine, for example: trialing to active; active to past_due when payment fails; past_due back to active on successful retry or to canceled or unpaid after the dunning period; active to canceled at period end if the customer cancels. Persist states and transitions, and drive side effects (emails, access changes) from transition events. See event-driven architecture.
Billing cycles
At each period boundary:
- A scheduled job finds subscriptions whose period ends (bucketed by time, run continuously rather than all at midnight). See delayed jobs and distributed cron.
- It creates an invoice for the next period (and usage for the last), with line items, discounts and tax.
- It attempts payment with the default payment method, using an idempotency key derived from the invoice id so retries never double charge. See idempotency keys in practice.
- It advances the subscription period.
Each step must be safe to rerun; the job will crash at some point mid-run.
Proration
When a customer upgrades mid-cycle, charge for the remaining time at the new price and credit the unused time at the old price. Downgrades are often applied at the next renewal to avoid refunds. Represent prorations as explicit invoice line items so customers can see them, and use exact integer arithmetic in minor units. See payments and ledgers.
Usage-based billing
API calls, tokens, storage, seats: metered usage needs a pipeline:
- Record usage events with idempotency (event ids), from services at the point of use.
- Aggregate per customer, meter and period in a stream or batch job, deduplicating. See counting at scale.
- Rate the totals with tiered or volume pricing at invoice time.
- Show near-real-time usage to customers, and enforce limits or alerts on budgets.
- Reconcile aggregates against raw events before invoicing. See data quality and data contracts.
AI APIs bill by tokens this way; see Design LLM Inference.
Failed payments and dunning
Cards expire and get declined. Dunning recovers revenue:
- Retry on a schedule (for example after 1, 3, 5 and 7 days), at smart times, with network card updater services to refresh expired cards.
- Email and in-app prompts to update the payment method. See sending email at scale.
- A grace period during which access continues, then restricted access, then cancellation.
Entitlements and access
The product must answer "can this customer use feature X now?" quickly and correctly:
- Maintain an entitlements record per customer, updated from subscription events (plan change, payment failure after grace, cancellation).
- Services check entitlements from a cache, not by calling the billing system on every request. See caching.
- Reconcile entitlements against subscriptions regularly so a missed event does not leave free access or block paying users.
Trials, coupons and cancellations
- Trials with or without a card; convert automatically at the end, with reminders before.
- Coupons as discounts with durations (once, repeating for N months, forever), applied at invoice time.
- Cancel immediately (with optional prorated refund) or at period end; allow reactivation before the end.
Taxes and compliance
Sales tax and VAT depend on customer location and product type; use a tax service, store tax details on invoices, and keep invoices immutable once finalised (issue credit notes for corrections). Strong customer authentication rules in some regions require re-authentication for some charges.
Build or buy
Billing platforms (Stripe Billing and others) handle much of this. Interviews may still ask you to design it; in practice, the integration still needs webhooks, entitlements and reconciliation on your side. See webhooks.
In the interview
"Subscriptions are a persisted state machine; a continuous scheduler picks up subscriptions at period end, generates an invoice with prorations, usage and tax, and charges it with an invoice-derived idempotency key. Failures enter dunning with scheduled retries and emails; transitions emit events that update a cached entitlements record that services check. Usage events are deduplicated and aggregated per period, and reconciled before invoicing." See Design a Payment System.
Checklist
- Immutable, versioned prices; explicit subscription state machine.
- Idempotent, rerunnable billing jobs spread over time.
- Proration as visible line items in integer minor units.
- Metered usage with deduplication, aggregation and reconciliation.
- Dunning with retries, card updates, grace periods and notifications.
- Cached entitlements driven by events and reconciled.
- Immutable invoices, credit notes and tax handling.