Globaprom.
Blog

Multi-Currency Support: Pricing, Payments, Rounding & Display Done Right

Date Published

Multi-currency support means a customer in Tokyo, Frankfurt, and Chicago each sees a price in their own currency, formatted the way their market expects, calculated without rounding errors. It sounds like a display problem. It's actually a data-storage and business-logic problem wearing a display problem's clothes, and most of the bugs in this space come from skipping the first two and jumping straight to formatting.

We build multilingual software development with currency handled correctly from the first commit, because pricing bugs are the kind customers notice immediately and trust slowly. This article covers what "multi-currency" actually requires: why live exchange-rate conversion isn't real multi-currency pricing, how to store money without floating-point errors, why decimal places vary by currency, and how rounding rules differ by market.

Multi-Currency Pricing Is Not Live Exchange-Rate Conversion

The fastest way to get multi-currency wrong is to keep one base price and convert it with a live exchange rate at checkout. It looks like multi-currency support. It isn't, and customers feel the difference within a few purchases.

Live conversion means your price moves every time the exchange rate does, so the same product costs a different amount to the same customer on two different days. It also means every fee, discount, and rounding decision inherits the volatility of the currency market, which is not a system you want driving your revenue. Real multi-currency pricing sets a controlled price per market, reviewed and adjusted deliberately, not recalculated on every page load. The exchange rate informs that decision. It doesn't make it automatically, in production, on every request.

This distinction matters most in custom ecommerce development, where a customer comparing your price against a competitor's needs it to hold still, and where payment gateways settle in specific currencies with their own rules about what they'll accept.

Store Money as Integers, Never as Floating-Point Decimals

The single most common multi-currency bug has nothing to do with currency. It's storing money as a floating-point decimal, which introduces rounding errors that compound across a shopping cart, a tax calculation, and a payment gateway that doesn't tolerate a fractional cent showing up where it shouldn't.

The fix is a decades-old convention that still gets skipped by teams new to the problem: store every amount as an integer in the currency's smallest unit, cents for US dollars, and convert to a decimal display value only at render time. Stripe's own API works this way for exactly this reason: amounts are represented in the smallest currency unit, an integer, never a float (Stripe, Working with currencies). Get this right once, at the database and API layer, and an entire category of off-by-one-cent bugs simply doesn't exist.

Minor Units Aren't Always Two Decimals

"Round to two decimal places" is correct for US dollars and euros, and wrong for a meaningful slice of the world's currencies. The number of decimal places, formally the minor unit, is defined per currency in the ISO 4217 standard, and it varies more than most engineers expect (ISO 4217):

  • Zero decimal places: Japanese yen (JPY) and Korean won (KRW) have no subdivision in practice. A price of ¥1,000.50 is not a real amount; it's a bug.
  • Two decimal places: the majority of currencies, including USD, EUR, and GBP.
  • Three decimal places: Bahraini dinar, Kuwaiti dinar, Omani rial, and Jordanian dinar all subdivide into a thousandth, not a hundredth.

A pricing engine hardcoded to two decimals silently corrupts every yen price it touches and truncates every dinar price. Currency-aware rounding, driven by the ISO 4217 minor-unit table rather than a global constant, is not an edge case. It's the baseline for supporting more than a handful of markets correctly.

Rounding Rules Differ by Market, Not Just by Currency

Even within a single currency, rounding conventions vary by market and payment method. Switzerland is the clearest example: Swiss francs subdivide into 100 centimes on paper, but cash transactions round to the nearest 0.05 CHF, a convention often called Swiss rounding or Rappenrundung, because five-centime coins are the smallest denomination still in circulation. A checkout that displays a price of 19.98 CHF and then charges exactly that in cash is technically wrong in a market that expects 20.00.

The lesson generalizes past this one example: rounding is a market-specific business rule, not a universal math function. A multi-currency system needs a rounding policy per market, not a single global .toFixed(2) call, or it produces prices that are numerically close and locally wrong.

Displaying Currency the Way Each Market Expects

Formatting is the part users actually see, and it's where locale, not just currency, decides the output. The same amount in the same currency renders differently by convention: $1,234.56 in the US, 1.234,56 € in much of continental Europe, and 1 234,56 € with a space as the thousands separator in France. Symbol position, decimal separator, and grouping separator all vary independently of which currency is being shown.

Modern platforms don't need to hand-build this logic. The Intl.NumberFormat API, backed by the same CLDR locale data that drives correct date and number formatting, produces the right output for a given currency-and-locale pair without a maintained lookup table. Hardcoding a $ prefix and a comma-separated thousands rule is the retrofit version of this problem: it works for one market and breaks quietly for every other one.

Frequently Asked Questions About Multi-Currency Support

What does multi-currency support actually mean?

It means customers see prices in their own currency, calculated with controlled per-market pricing rather than a live exchange-rate conversion, stored without floating-point rounding errors, and formatted the way their market expects.

Should I convert prices using a live exchange rate?

Not for the price a customer pays. Live rates are useful for internal reporting and for informing what a market's price should be, but the price itself should be a controlled, deliberately set value per market, not a number that changes with currency markets on every page load.

Why do some currencies have three decimal places?

Currencies like the Bahraini and Kuwaiti dinar subdivide into a thousandth rather than a hundredth, per the ISO 4217 standard. Software that assumes two decimal places everywhere truncates or misrounds these currencies.

What's the safest way to store prices in a database?

As an integer in the currency's smallest unit, cents for a two-decimal currency, with no subdivision at all for a zero-decimal currency like Japanese yen. Convert to a decimal only when displaying the amount, never for storage or calculation.

How do I get multi-currency pricing built correctly for my store?

Scope it in from the start: pricing storage, per-market rounding rules, and locale-aware display are architecture decisions, not a checkout plugin. Request a fixed-price quote and we build the currency layer as part of the core system, not a retrofit.

Get Currency Handling That Doesn't Break at Scale

Tell us which markets and currencies your software needs to support. We architect pricing, rounding, and display correctly from the first commit, and reply with a fixed price and a delivery date.

Request a fixed-price quote →