A US-based SaaS shows pricing in dollars by default. A visitor from Germany sees $50/month and has to mentally convert. They might bounce. Currency localization — showing prices in the visitor’s local currency — measurably improves conversion in international markets.
This post walks through detecting currency from IP, the practical patterns, and the regulatory and accounting issues that come with multi-currency pricing.
What Currency Localization Actually Is
Two distinct concerns:
Display localization
Show the price in the user’s currency. Same underlying price; different presentation.
$50.00 USD → €46.50 EUR (auto-converted)
True multi-currency pricing
Charge the user in their currency. The price isn’t a conversion; it’s a local market price.
USD: $50/mo
EUR: €45/mo (intentionally rounded, not auto-converted)
INR: ₹3500/mo (local market pricing, much cheaper)
The first is easier and a UX improvement. The second requires accounting and tax infrastructure but matches local market expectations.
Display-Only Localization
The simple version:
- Detect user’s country from IP.
- Look up currency for that country.
- Convert your base-currency price using current exchange rate.
- Display with proper formatting (symbol, decimals, separators).
const result = await convertIP(req.ip)
const countryCode = result.success ? result.data.continent.country.code : 'US'
const currencyCode = countryCurrency(countryCode) // 'DE' → 'EUR'
const rate = await getExchangeRate('USD', currencyCode)
const localPrice = basePrice * rate
const formatted = new Intl.NumberFormat(getLocale(countryCode), {
style: 'currency',
currency: currencyCode
}).format(localPrice)
// "€46,50" for Germany
The Ip2Geo API returns the currency code with every lookup; you can skip the country-to-currency mapping step.
Display Formatting
Currency formatting varies wildly by locale:
USD in US: $1,234.56
EUR in DE: 1.234,56 €
EUR in FR: 1 234,56 €
JPY in JP: ¥1,235 (no decimal places)
INR in IN: ₹1,23,456.78 (lakh notation)
Don’t manually format. Intl.NumberFormat does this correctly:
new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(1234.56)
// '1.234,56 €'
new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }).format(1235)
// '¥1,235'
Use the user’s locale, not just country code. Locale combines language + region.
Exchange Rates
For display-only localization, you need current exchange rates:
Sources
- Open Exchange Rates — Popular, paid.
- CurrencyAPI — Multi-tier pricing.
- European Central Bank — Free, daily, EUR base.
- Frankfurter — Free, ECB-backed, ~daily updates.
- Fixer.io, ExchangeRate-API — Various tiers.
Update frequency
Hourly or daily is typical for display purposes. Real-time rates aren’t needed unless you’re trading.
Caching
Cache rates locally for at least 5-15 minutes. They don’t change often; per-request API calls would be wasteful.
Fallback
If your rate provider is down, use a recent cached rate. Don’t show wrong currency or fail the page.
When Display-Only Isn’t Enough
Display-only works for “I’m browsing pricing” but breaks down at checkout:
- You charge in your base currency ($50 USD).
- User saw €46.50 EUR.
- User’s credit card converts at the bank’s rate, which might be different.
- User’s bank might add a foreign-transaction fee.
Result: user expected ~€46.50, gets billed €48.20 with €2 in fees. Disappointing.
For true localization, you charge in the displayed currency.
True Multi-Currency Pricing
Charging in local currencies requires:
Stripe / payment processor multi-currency
Stripe supports presenting and charging in any major currency. You’d:
- Set up product prices in multiple currencies (Stripe stores each as separate price IDs).
- Charge customers in the appropriate currency.
- Receive payouts in your base currency (or in each currency, if you have local bank accounts).
Pricing strategy
You can’t just convert your USD price to EUR — markets have different price elasticity. A SaaS that charges $50/mo in the US might rationally charge €45/mo in Germany, ₹2000/mo in India, ¥6000/mo in Japan.
Discuss with your finance and marketing teams. Local market pricing is a strategic decision.
Tax handling
EU VAT, India GST, Australia GST, etc. — each market has its own consumption tax rules. Stripe Tax (or similar) handles the calculation; you handle whether the displayed price is tax-inclusive or tax-exclusive.
Accounting
Multi-currency revenue needs proper accounting treatment. Your accountant or finance team needs visibility into per-currency revenue.
Regulatory Considerations
EU VAT
For B2C sales to EU customers, you must charge the customer’s country’s VAT rate. The IP gives a hint about which country, but you need additional evidence (billing address, credit card country) for compliance.
Currency restrictions
Some currencies have legal restrictions (Argentina, Venezuela, Iran). You may not be able to charge in those currencies; charge in USD instead.
Display requirements
Some jurisdictions require prices to be displayed inclusive of taxes. Others require exclusive. Check requirements per market.
A Pragmatic Pattern
For most SaaS in 2026:
- Display in local currency based on IP (or stored user preference).
- At checkout, show the exact amount that will be charged (in the user’s currency or yours, whichever you bill in).
- Charge in 2-5 major currencies if you have meaningful traffic from those markets (USD, EUR, GBP, often AUD/CAD/JPY).
- For other markets, charge in USD with clear notation. Most users in less-supported markets are used to USD pricing.
This balances UX improvement against accounting/compliance overhead.
Edge Cases
Multiple currencies in a country
Eurozone vs non-Eurozone Europe is straightforward (currency = country’s currency). But:
- Switzerland uses CHF, geographically in Europe.
- Some countries informally use USD (Cambodia, Lebanon at times).
- Multi-currency users: someone in Argentina paying in USD.
Detection of currency from IP isn’t perfect. Provide a manual override.
Crypto pricing
Some services price in crypto (BTC, ETH). Display patterns are similar but with higher volatility, so cache exchange rates much shorter (minutes, not hours).
Discounts and coupons
A 20% discount on $50 USD should be 20% on €46.50 EUR, not 20% on a hardcoded €40. Apply discounts to the base price; convert.
Combining With Other Localization
Currency is one signal among several for international UX:
- Language (Accept-Language, browser locale).
- Currency (IP-derived).
- Timezone (IP + browser).
- Tax inclusion (regulated per market).
- Imperial vs metric units.
- Date/number formats.
For a comprehensive guide, see geo personalization.
Caching Per-User Pricing
Tricky: you can’t fully cache the pricing page if it shows different currencies. Options:
Cache the page; replace prices client-side
Cache the HTML. Use JS to fetch the user’s currency and rewrite prices. Slight FOUC but cache-friendly.
Cache per-currency
Different cache entries per currency. Use Vary header. CDN does most of the work.
Edge-side personalization
Use Cloudflare Workers or similar to inject the right prices at the edge.
For dynamic SaaS pricing pages, edge-side is often the best fit.
Implementation Checklist
For minimum-viable currency localization:
- Detect IP → country → currency.
- Fetch exchange rates (cached).
- Format with
Intl.NumberFormat. - Show user’s currency on browse pages.
- Show actual charged amount + currency clearly at checkout.
- Provide manual currency switcher.
- Test for traveler / VPN scenarios.
- Add Stripe multi-currency for top markets (if charging in local currency).
TL;DR
- Display-only currency localization is a UX win; relatively easy.
- True multi-currency pricing is a strategic + accounting effort.
- Use Intl.NumberFormat for formatting; don’t roll your own.
- Cache exchange rates (5-15 min minimum).
- Show actual charged amount at checkout — don’t let bank conversion surprise users.
- Stripe / payment processors support multi-currency natively.
- Regulatory considerations (VAT, tax inclusion) per market.
- Provide manual override for travelers, VPN users, and mismatches.
Currency localization is high-leverage. Many SaaS companies see 20-30% improvement in international conversion after implementing it. The Ip2Geo API returns currency code with every geo lookup, making the detection part one HTTP call. For the broader personalization picture, see geo personalization; for the related timezone topic, detecting timezone from IP.