Showing Local Currency Based on IP: Pricing Localization Done Right

Display prices in the visitor's local currency. The good and bad patterns, exchange rates, regulatory issues, and the pricing model that scales.

Showing Local Currency Based on IP: Pricing Localization Done Right

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:

  1. Detect user’s country from IP.
  2. Look up currency for that country.
  3. Convert your base-currency price using current exchange rate.
  4. 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:

  1. Display in local currency based on IP (or stored user preference).
  2. At checkout, show the exact amount that will be charged (in the user’s currency or yours, whichever you bill in).
  3. Charge in 2-5 major currencies if you have meaningful traffic from those markets (USD, EUR, GBP, often AUD/CAD/JPY).
  4. 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.

Get Started

Convert IPs into accurate location data in milliseconds.

Sign up today and get 1,000 free monthly stored conversions, and discover why developers trust us for fast, reliable, and affordable IP conversions.