GeoIP-based currency auto-detection for website

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1 servicesAll 2065 services
GeoIP-based currency auto-detection for website
Medium
~1 business day
FAQ
Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Implementation of GeoIP-Based Currency Detection on Website

GeoIP-based currency detection is a supplement to multi-currency system that removes the need for users to manually select currency on first visit. Visitor from Belarus sees prices in BYN, from Germany in EUR, from US in USD. Implementation is simpler than it seems, but has nuances with database accuracy, caching, and respecting user choice.

How GeoIP Works

IP address → country → currency. Chain of two steps.

Step 1: IP → country. Geolocation databases used. Main options:

Database Type Country Accuracy Cost
MaxMind GeoLite2 Local MMDB ~95–99% Free (registration)
MaxMind GeoIP2 Local + API ~99%+ Paid
ip-api.com HTTP API ~98% Free (1000/min)
ipinfo.io HTTP API ~99% Freemium
DB-IP Local ~95% Freemium

For most projects MaxMind GeoLite2 is optimal: local database doesn't depend on external services and doesn't slow requests.

Step 2: country → currency. Static correspondence table ISO 3166-1 → ISO 4217.

Installing MaxMind GeoLite2

composer require geoip2/geoip2

Database updated by MaxMind every Tuesday and Friday. For automatic updates use geoipupdate utility:

# /etc/GeoIP.conf
AccountID 123456
LicenseKey your_license_key
EditionIDs GeoLite2-Country
DatabaseDirectory /var/lib/GeoIP

# cron every Wednesday and Saturday
0 3 * * 3,6 /usr/local/bin/geoipupdate

Currency Detection Service

class GeoIpCurrencyDetector
{
    private Reader $geoIpReader;

    public function __construct()
    {
        $this->geoIpReader = new Reader(
            storage_path('app/geoip/GeoLite2-Country.mmdb')
        );
    }

    public function detect(string $ip): ?string
    {
        // Skip private and reserved ranges
        if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
            return null;
        }

        try {
            $record = $this->geoIpReader->country($ip);
            $countryCode = $record->country->isoCode; // 'BY', 'RU', 'DE' etc.

            return $this->mapCountryToCurrency($countryCode);
        } catch (AddressNotFoundException) {
            return null;
        }
    }

    private function mapCountryToCurrency(string $countryCode): ?string
    {
        $map = config('geoip.country_currency_map');
        return $map[$countryCode] ?? null;
    }
}

Config country_currency_map is array of ~250 countries. Key entries:

// config/geoip.php
return [
    'country_currency_map' => [
        'BY' => 'BYN',
        'RU' => 'RUB',
        'UA' => 'UAH',
        'KZ' => 'KZT',
        'US' => 'USD',
        'CA' => 'CAD',
        'GB' => 'GBP',
        'DE' => 'EUR', 'FR' => 'EUR', 'IT' => 'EUR', 'ES' => 'EUR',
        'PL' => 'PLN',
        'CZ' => 'CZK',
        'CN' => 'CNY',
        'JP' => 'JPY',
        // ...and ~200 more countries
    ],
    'fallback_currency' => 'USD',
];

Caching Results

IP-based detection is fast (local database, ~0.5 ms), but cache anyway: protection against repeated file reads on each request.

public function detectCached(string $ip): ?string
{
    $cacheKey = 'geoip:' . md5($ip);

    return Cache::remember($cacheKey, now()->addDay(), function () use ($ip) {
        return $this->detect($ip);
    });
}

TTL 24 hours — balance between freshness (user doesn't change country hourly) and accuracy (VPN switch picked up next day).

Integration in Middleware

class ResolveCurrencyFromGeoIp
{
    public function handle(Request $request, Closure $next): Response
    {
        // If user already made explicit choice — don't override
        if ($this->hasExplicitChoice($request)) {
            return $next($request);
        }

        $ip = $request->ip();

        // Account for proxies and load balancers
        if ($request->header('CF-Connecting-IP')) {
            $ip = $request->header('CF-Connecting-IP'); // Cloudflare
        } elseif ($request->header('X-Real-IP')) {
            $ip = $request->header('X-Real-IP'); // nginx proxy_pass
        }

        $currency = $this->detector->detectCached($ip);

        if ($currency && $this->isSupportedCurrency($currency)) {
            session(['auto_currency' => $currency]);
            Cookie::queue('preferred_currency', $currency, 60 * 24 * 90);
        }

        return $next($request);
    }

    private function hasExplicitChoice(Request $request): bool
    {
        // User explicitly switched currency
        return session()->has('explicit_currency_choice')
            || $request->user()?->preferred_currency;
    }
}

Handling IP Behind Proxy

IP issues arise from:

  • Cloudflare — real IP in CF-Connecting-IP
  • nginx reverse proxy — real IP in X-Real-IP or X-Forwarded-For
  • AWS ELB — X-Forwarded-For (first in list)

Proper Laravel config via TrustProxies middleware:

// app/Http/Middleware/TrustProxies.php
protected $proxies = '*'; // or specific load balancer IPs
protected $headers = Request::HEADER_X_FORWARDED_FOR
    | Request::HEADER_X_FORWARDED_HOST
    | Request::HEADER_X_FORWARDED_PORT
    | Request::HEADER_X_FORWARDED_PROTO;

After this $request->ip() returns correct client IP.

UX: Auto-Detection Notification

Good practice — show user toast/banner on first auto-detection:

We detected you're from Belarus. Prices shown in BYN. Change currency →

Banner shown once (sessionStorage flag) and contains quick link to currency switch. This is respect for user: automation helps, not imposes.

When GeoIP Fails

  • VPN/proxy: user from RU looks like US → shown USD. Solution: "Change currency" link always available.
  • Corporate networks: IP registered in different country. Same.
  • Tor: exit node in random country. Fallback to default currency.
  • IPv6: GeoLite2-Country supports IPv6 since 2020+. Ensure you have current version.

Testing

For local testing with fake IPs:

// In tests or local environment
if (app()->environment('local')) {
    $ip = config('geoip.test_ip', '178.124.0.1'); // Belarusian IP
}

For automated tests — mock service:

$this->mock(GeoIpCurrencyDetector::class, function ($mock) {
    $mock->shouldReceive('detectCached')->andReturn('BYN');
});

Timeline

  • Install GeoLite2 + basic detection + country mapping: 1 day
  • Middleware + caching + integration with multi-currency system: 1 day
  • UX notification + respecting explicit choice: 0.5 day
  • Auto-update base (cron + geoipupdate): 0.5 day

Total: 2–3 days assuming multi-currency system already implemented.