Payments & ledgers
Subscription billing - the invoice is computed, not typed
How a recurring charge is driven by a billing clock, proration and dunning, and why plan changes are the hard part.
What this board gets wrong on purpose
The tension
A PLAN CHANGE AND A RENEWAL CAN LAND ON THE SAME SUBSCRIPTION IN THE SAME SECOND, AND THE BOARD ONLY STOPS THEM BOTH WINNING. The subscription row carries a version and both writers compare-and-swap on it, so one of them is rejected - but the loser simply retries. If the loser was the upgrade, its retry re-reads a subscription whose period has just rolled over and prorates against the NEW period: the customer is credited for unused days of a period that has already been invoiced in full, and charged a proration for days the renewal invoice already covered at the old price. Nothing double-charges, the invoice totals are internally consistent, and the customer is wrong by a few dollars that nobody notices until support does. The second weakness is the anchor: 35% of monthly subscriptions renew at 00:00 UTC on the 1st, so 1,050,000 invoices are due in the same minute against a payment provider that accepts 100 charges a second - collection for the 1st takes three hours, and a provider incident in that window lands entirely on one day of revenue. The clock is safe to rerun; it is not spread out, and the board does not spread it.
Practise this as an interview
Take Subscription billing - the invoice is computed, not typed as a timed system design interview on a blank board - on your own, with the requirements to check yourself against, or with a candidate you invite.
Start here
Why this design
Every SaaS product, streaming service, gym and newspaper bills on a schedule, and the naive design - a cron job per customer that charges a stored amount - works until the first customer upgrades on the 17th. Interviewers ask this question to see whether you model the invoice as something computed from versioned prices and a period, whether you can do proration without floating point or double-counting a day, and whether you separate the invoice (a debt) from the charge (an attempt to settle it). Teams that run billing in production care most about the last part: failed payments, not plan design, decide how much revenue quietly walks away.
What you will learn
- Why a price is a versioned row that is never edited, and why an invoice references the version, not the plan name.
- Why the billing clock is a sweep over
current_period_end <= now()with an idempotency key, rather than a scheduled job per subscription. - How proration splits a period at the instant of a change, and the worked arithmetic of a $30 to $90 upgrade on day 10.
- Why an invoice is immutable once finalised, and corrections are credit notes.
- How dunning retries on a schedule, why a provider error must never enter dunning, and why the recovery rate is the most valuable number on the board.
- The honest weakness: a plan change racing a renewal at the boundary, and 1,050,000 renewals due in the same minute on the 1st.
How to read this board
- Diagram, left to right: CHANGE (a customer edits the future), THE CLOCK (renewal is a query), COLLECTION (an invoice is a debt, a charge is an attempt), DUNNING (failure is the normal case). Press play for the walkthrough.
- Below it, ACCESS: the only thing the customer actually feels.
- Then the DATA MODEL: six tables. Look at what is unique on invoices.
- Page 1 · Requirements, then Pages 2 and 3 · Capacity and Storage - 3.1 M renewals a month and why storage is not the problem.
- Page 4 · Availability - what happens if the clock stops for six hours.
- Page 5 · How it works - one upgrade and one renewal end to end, then Page 6 · Deployment, where a billing change is shadowed for a whole cycle.
1 · Requirements
A billing system turns a promise ("$30 a month for Pro") into a sequence of invoices and a sequence of attempts to collect them. It is not a payments system - it calls one - and it is not a pricing tool. Its job is to compute the right amount, for the right period, exactly once.
Ask these before designing
- Anchor on signup date, or on the 1st? Signup anchoring spreads renewals across the month. "Bill everyone on the 1st" is what finance asks for and it puts a third of the month into one minute.
- Proration on upgrade, on downgrade, or both? The common answer is: upgrade immediately and prorate, downgrade at period end with no credit. That asymmetry is a product decision that halves the proration code.
- Charge proration now or on the next invoice? Charging now means an extra card attempt (and an extra decline). Deferring means the next invoice carries pending items.
- Who owns tax? Sales tax and VAT depend on the customer’s location at the time of the invoice, not at signup. If tax is external, the invoice engine must call it inside finalisation.
- What does "past due" switch off? Read-only access, full suspension, or nothing for 14 days? This is the lever that decides whether dunning recovers 40% or 70%.
Functional
- Create, upgrade, downgrade, pause and cancel subscriptions; cancel at period end by default.
- At each period end, generate exactly one invoice per subscription per period.
- Prorate mid-period changes to the second, in integer minor units.
- Collect each invoice; on failure retry on a schedule and notify; suspend after the final retry.
- Issue credit notes; never edit a finalised invoice.
- Recompute any historical invoice from its inputs and get the same total.
Non-functional
- Exactly one invoice per (subscription, period), enforced by a unique key, not by the scheduler behaving.
- Renewal lateness under 5 minutes on a normal day; under 3 hours for collection on the 1st.
- Plan change p99 under 400 ms, including the proration preview.
- Never charge a card twice for one invoice, across retries, timeouts and provider redeliveries.
- Ten-year retention of invoices and lines - they are tax records.
Explicitly out of scope
Usage metering (it feeds this as line items), revenue recognition (it reads finalised invoices), the card network, and pricing experiments.
The rest of this board
Available on Tier Slate
This page publishes the question. The answer — 5 more written pages, an 8-step narrated walkthrough and a 6-table schema — is the board itself, and it opens in Tierslate.
- 5written pages
- 8walkthrough steps
- 6tables48 columns
5 pages behind this one
- Capacity estimation
- Storage estimation
- Availability
- How it works
- Deployment plan
More boards
Systems where losing a write is losing money.
- Double-entry ledger - the balance is not a column
- Fraud detection - a decision that must fit in 120 milliseconds
- Payment gateway - what a timeout means when the card may already be charged
- Matching engine - price-time priority, and why the clock cannot decide who was first
- Wallet transfers - the state machine, the hold that expires, and the reversal that cannot
- OTP verification - six digits and a clock
- Currency exchange - a rate that is only true for a few seconds
- Loyalty points - a currency you print yourself