The Price That Changed by a Cent in Every Other Market

Share
The Price That Changed by a Cent in Every Other Market. Abstract shopify illustration in orange and dark grey on debugly.dev

A merchant selling into several markets noticed that orders in the secondary currencies were off by a cent, sometimes two, from the number the pricing sheet predicted. Not randomly. Consistently, per market, in the direction that accumulates. The product page, the cart and the invoice each showed a defensible number, and the three defensible numbers disagreed, because currency conversion is not one rounding but several, and they are applied at different moments by different code.

This is the arithmetic of multi market pricing, and the cent is not a bug in any step. It is the sum of the steps not agreeing, which is the composition defect this site keeps returning to, in timeouts, in discounts and in caches, here wearing a currency costume.

This was a Shopify store with Markets enabled, fixed price adjustments and per market rounding rules, observed across checkout and invoices.

Where the roundings live

A price shown in a secondary market passes through several transformations, and each one rounds.

The conversion itself, from the home currency amount to the market currency, produces a value with more precision than the market can display or charge, so it is rounded to the market's minor unit, by a rule, commonly half up, but sometimes to a charm ending, a .99 or a .95, which is a second, commercial rounding on top of the arithmetic one.

The line rounding, when a quantity multiplies the unit price, can round the unit first and then multiply, or multiply and then round the line, and those differ by up to half a cent per line, times the quantity.

The cart rounding, when several lines and a discount and a tax compose, rounds each component and then sums, or sums and then rounds, and again the two differ, and the tax authority usually cares about one specific order of operations, which may not be the one the display used.

So the cent is the visible edge of a stack of roundings, each locally correct, globally inconsistent, and the merchant's pricing sheet, which did one rounding on a spreadsheet, is a fourth member of the disagreement.

Why the page and the invoice disagree

The product page shows the converted, charmed unit price, rounded for display. The cart may recompute from the home currency at checkout time, applying the conversion and the rounding again, and if the charm rule or the rounding mode differs between the display path and the charge path, the customer sees one cent on the page and is charged another.

The invoice, generated later, may recompute once more, from stored home currency amounts, with the tax rounding applied in the order the jurisdiction requires, producing the third number. Each recomputation is defensible. The customer experiences them as a store that cannot add.

This is the money version of 0.1 plus 0.2 is not 0.3, except the representation is fine and the policy is what differs, which is harder, because no amount of integer arithmetic fixes two roundings that were chosen separately.

The fixes

One source of truth per market price. Compute the market price once, at the moment the pricing decision is made, store it as the market's minor unit integer, and serve the page, the cart and the charge from that stored value. The conversion then happens once, and the rounding happens once, and the three surfaces agree because they are reading one number, not recomputing three.

Make the rounding order a policy and test it. Where recomputation is unavoidable, write down the order of operations, convert then round the unit, multiply then round the line, sum then round the cart, or the jurisdiction's required order, and apply the same order everywhere, and test that the page, cart and invoice produce identical values on a corpus of real carts. The test corpus is the pricing sheet, promoted from a prediction to an assertion.

Bound the charm pricing's blast radius. Charming to .99 is a marketing decision that overrides the arithmetic rounding, and it is fine, but it must be applied at the stored price, once, so that the charming is part of the source of truth rather than a display layer surprise. A charm applied at display and not at charge is the cent, by construction.

Reconcile on the money, not the page. Alert on the difference between the charged amount and the displayed amount per order, which should be zero by construction once the source of truth is single, and which catches any future recomputation that sneaks back in. The alert is the regression test running in production, on the only number that matters.

The review checklist

For any multi market store, ask: where is the market price computed, how many times, and do the page, cart and invoice read the same stored value. If the answer is "computed at each surface", the cent is present whether or not anyone has reported it, and the fix is the single stored price, because agreement between surfaces is a property of reading one number, not of rounding more carefully in three places.

The rule

Currency conversion is several roundings at several moments, and surfaces that recompute independently will disagree by the accumulated cent. Store the market price once as an integer minor unit, apply charm and tax rounding at that one moment as written policy, and alert on any gap between displayed and charged, because the cent is not an arithmetic error. It is a composition error, and composition errors are fixed by having one thing to compose.

The discount version of independently correct rules composing into a wrong total is two discounts stacked and the merchant ate the margin, and the integer representation half is the floating point post in this series.