Letting the payment provider own the money
- Product
- Billing
- API design
The billing design for our virtual landline product is one sentence: Stripe owns the money, and our service listens.
Stripe holds the product and its price. It holds the subscription, the trial, the renewal, the discount codes and the tax treatment. It runs the cancellation, and the hosted portal where a customer changes their card. Our side does not model any of that. It subscribes to the events, and reacts.
That sounds like laziness, or at least like the path of least resistance under a deadline. It was partly that. It also turned out to be the right call for a reason that has nothing to do with engineering effort.
The people who change prices are not engineers
Pricing does not belong to the engineering team. Neither do grace periods, discount campaigns, trial lengths, or the wording of the email that goes out when a payment fails. Those belong to marketing, product and finance, and they change on their timescale, which is not the release timescale.
If price lives in our configuration, every one of those changes is a ticket, a pull request, a review and a deploy. Not because the change is hard, but because we put it somewhere that requires us. The team that owns the decision has to queue behind the team that owns the field.
Putting it in Stripe means finance changes the price in Stripe. Marketing runs a discount in Stripe. Nobody asks us, and — this is the part that matters — the backend does not change behaviour when they do. It carries out the same process against whatever the subscription now says. There is no deploy, because there is nothing to deploy.
The general form of this: when you model something a provider already models, you are not just duplicating data. You are moving the authority for a decision into your release cycle, and handing yourself the job of keeping two versions of the truth in step forever.
The price is not what they pay
There is one assumption this design makes tempting, and it is wrong.
Having put the price in Stripe, the obvious way to show a customer what they will pay is to read the price back out. It is right there on the product, it is a single field, and for a plain subscription with no complications it is even correct.
It is not the amount. Tax is applied to an invoice, not to a price. A discount code exists as something applied when the invoice is drawn. Proration on a mid-cycle change is calculated at the moment of the change. None of that lives on the price, because none of it is a property of the product — it is a property of this customer buying it, now.
So anything quoting a real figure has to ask Stripe to draw the invoice first and read the total off that, rather than read the price. In Stripe's API that is a preview: you ask what the next invoice would look like, and use the number it gives you.
This shows up in more places than you would expect. The dashboard telling someone what their next payment will be. The cancellation flow telling them what they are giving up and when it lapses. Any email with a figure in it. Every one of those has to go and ask, and none of them can shortcut to the price — including, particularly, the one where the customer is already unhappy and about to check your arithmetic.
What you still have to own
Delegating the money does not delegate everything, and the boundary is worth being explicit about.
Stripe knows a subscription is active. It does not know whether the customer actually has a working phone number, because that is our side of the transaction. So the thing our service owns is the mapping between the two: this subscription corresponds to this provisioned service, and those two facts should agree.
They can stop agreeing. A payment can succeed while provisioning fails. A cancellation can be processed while the number is still live. Neither system is wrong on its own terms — they just disagree, and nothing notices unless something is specifically looking. So there is a report that looks: accounts where the billing state and the telephony state have drifted apart. It is the piece of the design that exists precisely because the rest of it is delegated.
That is the honest trade. Handing the money to a provider removes an enormous amount of state you would otherwise own, and adds exactly one new problem: you now have two sources of truth that are each authoritative about different halves of the same customer, and keeping them consistent is a job nobody else will do for you.
What I took from it
Ask who changes this, and how often. If the answer is a team that is not yours, on a cadence that is not your release cadence, storing it yourself makes you a bottleneck for a decision you do not own.
A price is not an amount. Tax, discounts and proration only exist once an invoice is drawn. Any figure shown to a customer has to come from the invoice, and the shortcut is correct just often enough in testing to survive to production.
Delegating state creates exactly one new obligation. Whatever you hand over, you now own the correspondence between their view and yours — and you should build the thing that checks it, because the failure is silent by construction.