avatar

Guilherme Caraciolo

November 22, 2023

Free geographical location and currency of website visitors

Many of the applications I have worked with wanted to know the location of it's users. Until know, my way to go to gather this information was using some geoip api wrapped by laravel-geoip. It's a simple to use this lib: just setup the environment variables of the api you choose and start collecting user location based on their ips.

geoip(request()->ip());

It's a nice solution and works fine for small websites. But it has downsides for production workloads: rate limit and latency.

So, recently I was wondering.. Cloudflare shows the location of every request made to a website. Wouldn't be nice if they just provide that information straight in the request?? Indeed, they do!

I discovered the cf-ipcountry header in the Cloudflare docs . It gives the iso code of the country where the request was made. It's really nice information, comes for free with a Cloudflare setup and has a really good accuracy (except if a request was made under VPN).

The docs also mention that if you need more geolocation data, you can just enable a feature on you domain called managed transformations. It provides: "cf-region-code", "cf-region", "cf-postal-code", "cf-iplongitude", "cf-iplatitude", "cf-ipcountry", "cf-ipcontinent", "cf-ipcity", "cf-timezone"! All of that with zero latency on your request and, I believe, a really good accuracy. Only currency is not provided. But currency is directly related to a country so a simple dictionary with country iso code => currency solves the problem. The same applies if you need the country name.

<?php
 
namespace App;
 
use Spatie\LaravelData\Data;
 
class GuestId extends Data
{
public string $country;
public string $currency;
 
public function __construct(
public string $ip,
public string $iso_code,
public string $city,
public string $state,
public string $state_name,
public string $postal_code,
public string $lat,
public string $lon,
public string $timezone,
public string $continent,
) {
 
$this->country = $this->defineCountry();
$this->currency = $this->defineCurrency();
}
 
public static function fromRequest(): self
{
// although cloudflare always send these headers, I really like to have defaults in place.
// just in case..
return self::from([
'ip' => request()->ip(),
'iso_code' => request()->header('cf-ipcountry', 'BR'),
'city' => request()->header('cf-ipcity', 'São Paulo'),
'state' => request()->header('cf-region-code', 'SP'),
'state_name' => request()->header('cf-region', 'São Paulo'),
'postal_code' => request()->header('cf-postal-code', '20000-000'),
'lat' => request()->header('cf-iplatitude', '-22.8777'),
'lon' => request()->header('cf-iplongitude', '-43.3078'),
'timezone' => request()->header('cf-timezone', 'America/Sao_Paulo'),
'continent' => request()->header('cf-ipcontinent', 'SA'),
]);
}
 
protected function defineCountry()
{
return config('countries.iso_code.' . strtoupper($this->iso_code), 'Brazil');
}
 
protected function defineCurrency()
{
return strtolower(config(
'countries.currency.' . strtoupper($this->iso_code),
config('stripe.foreign_location')
));
}
}

After this solution, I no longer use geoip apis! A few of the benefits were:
1. No more rate limits. If my site suffer an DDoS's, I don't loose track of my real customers.
2. No more extra latency. In fact, I didn't have this before because I offload the operation in a Laravel Job.
3. No more inaccuracy. Many requests failed and I loose track of the visitors location.

There's one downside to this approach. If you have multiple domains in your application you need to enable this feature in all of them.

Guilherme Caraciolo

Software Engineer

Reasoning about Laravel, VueJS and programming in general.