Rutba

Blog · How we build

One unit, one rater: the rule that stops us billing you twice

When two systems can both price the same thing, eventually both of them do. Here is the single rule we hold to instead, and the one line of SQL that enforces it.

· 3 min read

A diagram: envelopes, emails and stored gigabytes passing through one rater and out as a single invoice line.
Three units, one rater, one place a price can come from.

Being charged twice for one month is not a rounding error. It is the kind of mistake that ends a relationship with a supplier, because it is not really about the money — it is about whether the numbers on your bill are the product of a system or a coincidence.

Our billing has two things that can price a subscription. Stripe holds the price and the seat count, and raises an invoice when a period rolls over. Our own renewal sweep also advances periods and raises invoices — it has to, because plenty of customers pay by bank transfer and never touch a card processor at all.

Two billers. One period. That is the whole problem.

The rule

One unit, one rater, per invoice line.

Every chargeable thing has exactly one system allowed to put a price on it. Not a preference, not a default — the other system is structurally prevented from pricing it at all. Stripe rates the subscription line, because Stripe is holding the price and the quantity. We rate metered usage, because Stripe is not holding the metering: mailboxes in use, envelopes sent, gigabytes stored.

They meet on one document. Our usage is pushed onto Stripe’s draft invoice as line items in the hour between Stripe creating it and finalising it, so the customer gets one bill with both halves on it — computed in two places, added up in one.

The line that enforces it

A rule you state in a design document is a rule until somebody who has not read it adds a feature. So the guard is in the query that selects work, not in a comment:

SELECT ... FROM subscriptions
 WHERE status = ANY($1::text[])
   AND current_period_end <= $2
   AND stripe_sub_id IS NULL   -- Stripe already bills these
 ORDER BY current_period_end

If Stripe holds the subscription, our sweep does not see it. Not "skips it after a check" — does not select it. Stripe owns the clock for those, and their period is advanced by the webhook that says Stripe moved it. Ours owns the clock for everything else.

The same rule, one level down

Metered usage has its own version of the problem. A measurement is rated once, handed to Stripe once, and invoiced once — and the gap between "handed over" and "invoiced" is real, because the invoice it lands on does not exist yet when we push it.

So there are two flags rather than one clever one. stripe_item_id records that a measurement has been given to Stripe; invoiced_on records that the invoice carrying it has come back to us. Two facts, two columns. Collapsing them into a single "done" flag is how a month of usage gets offered twice.

1

system may price any given line

2

flags, because there are two facts

0

periods invoiced twice

And the third place it bites

Payment notifications arrive more than once. Stripe retries for three days on any failure and replays on request, so every handler has to be safe to run twice — and the naive fix, recording the event id before doing the work, is worse than nothing: if the work then fails, the retry that would have fixed it is discarded as a duplicate.

So a handler claims the event, does the work, and releases the claim if it throws. The retry finds the job still to do. It is four extra lines and it is the difference between a transient database blip being invisible and a payment notification being lost for good.

None of this is clever. It is a set of small refusals to be clever, in the one part of the product where being clever costs somebody money.

What it costs, in full

Every price, every unit, and what happens at the edges. No "contact us" on the numbers.

See the pricing

Read next

  • Ask for your money back, any time, and we will not ask why

    If you have paid for time you are not using, there is a button that gives it back. No window, no reason, no email to support. Here is exactly how the number is worked out, and where we round.

  • Every rounding goes to the customer

    Software is full of places where the arithmetic is not exact. Each one is a decision about who absorbs the fraction, and we made that decision once, in advance, for all of them.

  • We wrote an office suite and gave it away

    Seven desktop applications, our own document engine, and the Outlook archive formats almost nothing else opens. Free under the AGPL, no account, no telemetry — and a commercial reason it is not charity.