A paid invoice that stays unpaid, by construction

by

If you take money in a zero-decimal currency through Stripe, there is a way to lose a payment that looks like nothing is wrong. I lost weeks to it, so here it is written down.

Stripe has sixteen currencies with no minor unit, yen among them. The amount you send is the amount, not hundredths of it. That part is documented and everybody reads it.

Here is the part that is not. Take an invoice totalling 1,082.50 yen. The currency cannot express half a yen, so you ask Stripe for 1082. The buyer pays 1082. The webhook reports 1082. Convert that back into the minor units your own record is stored in and you get 108200, which is fifty short of the 108250 on the invoice. Compare the two and the payment is refused. The invoice stays unpaid. The buyer has been charged.

Nothing is broken. Every number is correct. The comparison is simply being made in different units from the charge, and the gap is exactly the rounding the currency forced on you in the first place.

The fix is one line and it is the boring one: make the settlement comparison in the units you charged in, not the units you store. What made it expensive is that it fails only in currencies you probably do not test in, only on totals that do not divide evenly, and it fails in the direction that looks exactly like a customer who simply did not pay. Nobody reports it, because from the outside it is indistinguishable from an unpaid invoice.

If you charge in more than one currency, it is worth checking which side of that conversion your comparison sits on. Has anyone hit the same thing somewhere other than invoicing? I assume subscriptions and marketplaces have their own version of it.

6 views

Add a comment

Replies

Be the first to comment