All articles Engineering

Pricing in one currency, charging in another

We quote in dollars and charge in naira. Almost every bug we shipped this year lived in that gap.

Photo: National Bank Note Company, producer ; United States. Post Office., associated name ; Washington, George (1732-1799, American) associated name (Public domain) /

Our prices are USD. Our payment processor, for now, settles in NGN. So every charge involves a conversion, and that conversion has been the single richest source of bugs in the product.

Worth writing down, because the shape of the mistake generalises well beyond currency.

The bugs, in order of embarrassment

A naira sign in front of a dollar figure. Eleven times, in eleven files. A page prints ₦39 for a $39 price and the number is wrong by a factor of about 1,400. Fixed locally each time, which is exactly why it happened eleven times.

A stored total mixing currencies. A revenue figure that summed dollar invoices and naira-era rows from a retired table and displayed the result as one number. Not a display fault — an arithmetic one, on the number an operator uses to decide things.

A quote that did not match the charge. The confirmation dialog computed the price in JavaScript by multiplying the monthly price by an interval multiplier. Correct until yearly prices started rounding down to the whole dollar, at which point it quoted $89.64 and charged $89.

The worst one: crypto quotes converted USD to NGN at our manual rate, then NGN to coin at the market rate. Those two rates are never equal. A $9 plan asked for 10.73 USDT — nineteen per cent over, in a coin that *is* the dollar.

What actually fixed it

Not another local fix. A test that fails if any file types a currency symbol at all.

There are three helpers: one for a USD price, one for what the card is debited, one for an amount in the currency its own row names. Everything else is a guess about a currency, and the guess was wrong every single time.

Three files are exempt, and each states why in the test — they print figures that genuinely are naira, like the exchange rate itself. Everything else is caught.

It found a twelfth instance the same afternoon it was written.

The general lesson

When a class of bug recurs, the recurrence is the signal. Fixing the instance is not fixing the problem — the problem is that the mistake was available to make.

The question to ask after the third occurrence is not "where else is this wrong?" but "what would make this impossible to write?" Sometimes that is a type. Sometimes it is a lint rule. Here it was a test that reads the source of every file and fails on a character.

Any of those beats vigilance. Vigilance is what you are relying on when you fix the eleventh instance by hand.

Start building

Lock a key to your domain in about five minutes.

Get started