The invoice was paid in full and our own check said it was fifty short

by•

We store every amount in minor units because that is what an invoice's arithmetic needs. Stripe agrees for almost every currency and does not for sixteen of them: a charge of one thousand yen is 1000, not 100000, and sending the second bills a hundred times the price with nothing raised anywhere.

The interesting failure is not the charge, though, it is the check afterwards. Take a yen invoice totalling 108250 minor units. That is 1,082.50 yen, which the currency cannot express. We ask Stripe for 1082, the buyer pays 1082, the webhook reports 1082, and converting that back answers 108200. Fifty short, every time, by construction. Compared as stored, the payment is refused, the invoice stays unpaid, and the buyer has already been charged. Nothing throws and no log says anything is wrong.

So the comparison has to be made in the units the charge was made in, not the units we happen to store. That is one function now, used by all three places that settle money: the webhook, the page a buyer lands on coming back from Stripe, and a credit purchase. Truncation rather than rounding is deliberate: it is what we already asked Stripe for, and rounding the other way charges somebody more than the document in front of them says, which is a pricing decision wearing a rounding decision's clothes.

What convinced me to give it a home of its own rather than leave it in the payment service: one request was already building two figures and only one was scaled. A subscription's recurring price read the stored amount raw while a discount in the same session was converted properly, so in a zero-decimal currency the two would have disagreed inside one checkout. Latent, since our catalogue is priced in dollars, and the first price that is not is exactly the change that would not think to look here.

If you take money in more than one currency: where does that conversion live, one function or every call site that touches an amount?

3 views

Add a comment

Replies

Be the first to comment