Every SaaS pitch deck has a pricing slide and every SaaS codebase has a billing module that somebody is quietly afraid of. The gap between those two artefacts is where a lot of otherwise good products stall.
The reason is that pricing is a marketing decision made in an afternoon and billing is a systems problem that has to hold that decision, plus every previous one, forever. Customers who signed up on a plan you no longer offer still need charging correctly.
One card, one charge
A product that sells a base subscription, extra storage and metered AI usage has three natural billing lines. Implemented naively, that is three subscriptions, three charges on the statement, and three chances for one of them to fail while the others succeed — leaving an account in a state your code has no name for.
Consolidating them into a single subscription, priced as a unit quantity against a one-cent plan, collapses the whole problem. The customer enters a card once. Plan changes resize the quantity rather than creating a new agreement, so no re-authorisation is needed. And the account is either paid or not paid — there is no partial state to model.
The cost is that your invoice line is a number rather than an itemised list, so you owe the customer a clear in-app breakdown. That is a much easier problem than reconciling three payment states.
Metered usage needs a wallet, not an invoice
Usage-based AI spend does not fit a subscription cycle. A customer who runs a large batch on the 3rd should not get a surprise on the 30th, and you should not be extending them a month of credit for compute you already paid for.
A prepaid wallet with auto-recharge solves both directions. Balance falls below a floor, a top-up is charged against the card on file, work continues. The customer sees the balance draining in real time and can set the thresholds. Nobody is surprised, and nobody is financing anybody.
- Charge against a stored token off-session so no one has to be present
- Make the floor and the top-up amount customer-configurable, with sane defaults
- Fail closed on a declined top-up, but tell the user before the balance hits zero
- Log every debit against the operation that caused it, or support cannot answer "what did I pay for"
The rules nobody writes down
A few things we now treat as non-negotiable, each learned the tedious way:
- A refund is not a negative payment. Model it as its own record with its own purpose, or every "money in" total you ever write will double-count it.
- Never delete a billing record. Void, reverse, supersede — but the row stays, because someone will ask about it in eighteen months.
- Test the downgrade path as hard as the upgrade path. Upgrades are exercised constantly in development; downgrades are exercised by angry customers.
- Currency and card-network behaviour differ by market in ways that will surprise you. Verify with a real card in the real market before announcing a price.
Billing bugs are not like other bugs. They are visible to the customer, they are about money, and they arrive with a screenshot.
Why we bring it up early
When we scope a product, billing goes in the first phase alongside auth and tenancy — not because it is glamorous, but because retrofitting it means rewriting assumptions that have spread through the codebase. A product that cannot charge for itself is a demo, however good the rest of it looks.