All articles

How to build a subscription billing system

Billing looks like a form and a payment. It is actually dozens of edge cases where money is unforgiving — and most of them are already solved for you.

Every subscription business starts with billing that fits on an index card: one plan, one price, charge the card every month. It works right up until it does not. Someone asks for annual billing, then a discount, then to upgrade mid-cycle, then a refund, then an invoice with their VAT number on it — and suddenly the code that charged a card every month is making decisions about money it was never designed to make. Billing is the part of a SaaS that looks trivial and turns out to be one of the hardest things you will build, because money is unforgiving and the edge cases are the product.

Billing is deceptively hard

The demo version of billing — take a card, charge it monthly — is a weekend of work. The real version is not. Consider what a growing business actually asks of it: multiple plans and tiers, monthly and annual terms, mid-cycle upgrades and downgrades that must be prorated to the day, free trials that convert or expire, coupons and percentage discounts and credits, seat-based or usage-based pricing that changes within a period, pauses, cancellations that take effect now or at period end, and reactivations. Each of these is a rule about money, and money has no rounding you can wave away.

Then come the failures. Cards decline — for insufficient funds, for expiry, for a bank's fraud check — and a serious fraction of your churn is not customers leaving but payments quietly failing. Handling that gracefully, retrying on a sensible schedule, and emailing the customer before you cut off their service is called dunning, and it directly recovers revenue you would otherwise lose. None of this appears in the index-card version, and all of it is load-bearing.

Buy the engine, build around it

Here is the strong recommendation: do not build the billing engine. Stripe Billing, Chargebee, Recurly and their peers have spent years encoding proration maths, dunning logic, tax integration, invoice generation, and the payment-provider relationships behind all of it. Rebuilding that is not a feature; it is a second company you did not mean to start. The correct architecture for almost everyone is to buy the engine for what it is excellent at — subscriptions, invoices, retries, the money itself — and build only the parts that are specific to you around it.

What you legitimately build is the layer that connects billing to your product: what a plan actually unlocks, how usage is metered and reported to the billing engine, how entitlements are enforced in your application, and how your internal tools and reports read the billing state. That glue is genuinely yours and worth doing well. The proration arithmetic underneath it is not, and every hour spent reimplementing it is an hour spent recreating bugs other companies already found and fixed.

There is a narrow exception worth naming honestly. If your pricing is so unusual that no engine models it — a metered product with dozens of dimensions, contract-driven enterprise deals with bespoke terms, a settlement model between multiple parties — you may outgrow even the best billing platform. But that threshold is far higher than most teams assume, and the right move when you approach it is usually to push the engine to its limits and layer your peculiarities on top, not to replace it. Reach for a fully custom billing engine only when you can demonstrate that the platform genuinely cannot represent your model, not merely that it represents it awkwardly.

Money is unforgiving — design for correctness

The defining constraint of billing is that a small bug is not a small problem. Charge a customer twice and you have a refund, an apology, and a dented reputation. Charge them zero when you meant to charge them and you have silently given away revenue you will never notice is missing. So the engineering standard for billing is higher than for the rest of your product, and two ideas carry most of the weight.

The first is idempotency. Every operation that moves money must be safe to retry, because networks fail halfway and you will retry. A charge request carries a unique key so that if it is sent twice — a timeout, a webhook redelivery, a user double-click — it happens exactly once. The second is treating billing events as an auditable log rather than a mutable state you overwrite. You want to be able to answer, months later, exactly why a customer was charged a particular amount on a particular day. Reconciling your own records against the payment provider's, on a schedule, is not paranoia; it is how you find the discrepancy before the customer does.

A worked example shows why this matters. A customer on a monthly plan upgrades on the fifteenth. Your application, the billing engine, and the payment provider each have to agree on the prorated charge, the new renewal date, and the entitlement change — and each step can fail independently. If the charge succeeds but the entitlement update does not, the customer has paid for a plan they cannot use; if the entitlement updates but the charge fails, you are giving away the upgrade. The only safe design treats the whole change as one recorded intent that is driven to completion and can be replayed if any step drops. Get this pattern right once and every future pricing change inherits it; get it wrong and you debug money by hand, forever.

Tax, invoicing, and compliance

For a company selling across the EU, tax turns billing from hard into genuinely specialised. VAT rates vary by country; the rules differ for B2B versus B2C; the reverse-charge mechanism applies when a business customer in another member state provides a valid VAT ID; and cross-border digital sales carry their own reporting obligations. An invoice is not a receipt you style yourself — it is a legal document with required fields, sequential numbering, and retention rules. This is a second strong reason to buy the engine: a good billing platform, paired with a tax service, handles the parts that would otherwise need a specialist and an auditor to keep you safe.

Revenue recognition is the quieter cousin of tax. Cash arriving is not the same as revenue earned: an annual plan paid up front is earned month by month, and your finance team will eventually need the numbers reported that way. You do not have to solve this on day one, but you should know it is coming and keep your billing data clean enough that the answer is a report rather than a reconstruction.

Metrics: MRR, churn, and the truth

Billing is also where your most important business metrics are born, and they are only as trustworthy as the system underneath them. Monthly recurring revenue, expansion and contraction, gross and net churn, the difference between voluntary churn and payments that merely failed — these come directly from billing events. If those events are messy, every dashboard built on them lies confidently. A clean billing model, where every change in what a customer pays is a recorded event with a reason, is what lets you trust the graph the board is looking at.

This is another argument for buying the engine and owning the glue: mature billing platforms expose these events cleanly, and the work worth doing on your side is turning them into metrics that match how you actually think about the business, rather than reverse-engineering revenue from raw charges.

It is worth being precise about what the numbers mean, because a metric everyone quotes but no one defines causes quiet arguments later. Does a customer who downgrades count as churn or as contraction? When an annual plan is paid up front, does its revenue show up all at once or spread across the year? Is a failed payment that recovers three days later churn at all? None of these has a single correct answer, but your business needs one consistent answer, encoded once in the layer that turns billing events into metrics, so that the dashboard the board sees and the number your finance team reports are the same number told the same way.

Start with the billing model, not the code

The most common billing failure is starting with the code. The right first artifact is not a schema; it is a clear statement of your billing model. What are the plans, and what does each unlock? Is pricing per seat, per usage, or flat? When someone upgrades mid-cycle, what exactly happens to their money, and when? What is your policy on refunds, pauses, and failed payments? These are business decisions, and if they are vague, no engine and no code will make them precise — it will just encode the vagueness where you cannot see it.

It helps to pressure-test the model against the awkward cases before anyone writes code. What happens to an annual subscriber who downgrades in month three — refund, credit, or nothing until renewal? If a customer disputes a charge, how does that flow back through your records and your metrics? When a trial ends and the card fails, do you suspend immediately or grant a grace period? Each answer is a policy, and each policy has a cost in either revenue or goodwill. Deciding them deliberately, up front, is far cheaper than discovering that your code silently chose one for you.

Once the model is clear, most of it maps onto a billing platform you configure rather than build, and the remainder is the small, correct layer you write yourself. A short assessment is the cheapest way to get there: it pins down the billing model, decides which engine fits, identifies the tax and invoicing services you should integrate, and scopes the glue — so you build a system that keeps the money right from the first invoice instead of discovering the edge cases in production.

Has your billing outgrown a simple setup?

A fixed-fee assessment pins down your billing model, decides which engine to buy, and scopes the correct layer to build around it — before a bug touches real money.

Book a billing assessment