Currency Support
How to support multiple display currencies while settling in one base currency, without FX risk.
Why
Customers want to see prices in their local currency — a German customer sees €29 and knows what it costs, vs $29 which requires a mental FX conversion that reduces conversion. But accepting payment in 30 currencies creates FX risk: if you display €29 and the customer pays in EUR, you receive EUR which you must convert to USD (your base currency), and the conversion rate varies. The solution is display-only multi-currency: show prices in the customer's local currency (converted from your USD base price at a locked rate), but charge in USD — the customer's card does the conversion at their bank's rate, and you always receive USD. This gives the display benefit (customer sees a familiar number) without the FX risk (you always settle in USD). The most common mistake is charging in the display currency — you take FX risk, and if the EUR drops 10% against USD, your revenue drops 10% on those customers. Display in local currency, charge in base currency.
When
When you have international customers (> 20% of revenue from outside your base currency region). For most SaaS, this is within 6 months of launch.
How
1. Pick a base currency (USD for most SaaS) — this is the currency you settle in, report revenue in, and set prices in 2. Add display-currency conversion on the pricing page: detect the visitor's location (from billing address or, if not available, IP geolocation as a fallback), and show the converted price next to the base price 3. Use a daily-locked FX rate from your payment processor (Stripe's currency conversion API) — do not use live rates, which change during the checkout session and confuse the user 4. Charge in the base currency (USD) — the customer's card issuer does the conversion at their rate, and you receive USD; the customer may see a slightly different amount than the display price (their bank's rate vs your display rate), so add a note: 'charged in USD — your bank may convert at a different rate' 5. For customers who insist on paying in their local currency (some EU businesses require EUR invoicing for accounting), use Stripe's multi-currency settlement: charge in EUR and let Stripe auto-convert to USD at the daily rate 6. Do not offer currency choice on the pricing page — this creates decision friction; detect and display, but let the customer override in their billing settings if they care 7. Update display prices when the FX rate moves > 5% — if you display €29 and the rate moves so the USD price is now €32, update the display; stale display prices create confusion at checkout 8. Report revenue in your base currency only — multi-currency revenue reporting creates FX gains/losses that complicate accounting; let your payment processor handle the conversion and report the settled USD amount
Example
# Display-currency conversion (display only, charge in USD)
const BASE_PRICE_USD = 29;
const FX_RATES = { USD: 1.0, EUR: 0.92, GBP: 0.79, JPY: 150 }; // daily-locked
function displayPrice(countryCode: string) {
const currency = COUNTRY_CURRENCY[countryCode]; // DE → EUR
const rate = FX_RATES[currency];
const localPrice = BASE_PRICE_USD * rate;
return { amount: localPrice, currency, base: { amount: BASE_PRICE_USD, currency: "USD" } };
}
// German visitor sees: "€26.68 (charged in USD — $29)"
// They pay $29, their bank converts to EUR at the bank's rate
# Stripe charge (always in USD)
await stripe.paymentIntents.create({
amount: 2900, // $29.00 USD
currency: "usd", // always USD
customer: customerId,
description: "EZ4Code Pro — charged in USD",
});
// Customer's bank statement: "$29.00 USD" or "€27.20 EUR" (bank's conversion)
# For customers who require EUR invoicing (B2B)
await stripe.paymentIntents.create({
amount: 2668, // €26.68 EUR
currency: "eur",
customer: customerId,
});
// Stripe auto-converts to USD at the daily rate and settles in USDCommon Problems
- ⚠Charging in the display currency — you take FX risk, and if the local currency drops 10% against your base, your revenue drops 10%. Display in local, charge in base.
- ⚠Using live FX rates — the rate changes during the checkout session, so the displayed price and the charged amount differ, confusing the user. Use daily-locked rates.
- ⚠Offering a currency selector on the pricing page — this creates decision friction and most users do not care. Auto-detect and display, with an override in billing settings.
Best Practices
- ✓Display prices in the visitor's local currency (auto-detected) but charge in your base currency — this gives the display benefit without FX risk.
- ✓Use daily-locked FX rates from your payment processor — live rates change during checkout and confuse the user; locked rates are stable for the session.
- ✓Add a 'charged in USD' note next to the display price — this sets expectations and prevents support tickets when the customer's bank statement shows a slightly different amount.