mgcodeur/laravel-currency-converter
A Laravel package for converting currencies and fetching live exchange rates. It offers a simple API, supports multiple exchange rate providers, and integrates seamlessly with your Laravel app for pricing, payments, and multi-currency features.
Install and let Laravel's package discovery wire it up:
composer require mgcodeur/laravel-currency-converter
Publish the config so you can pin the base currency, cache TTL, and rate source:
php artisan vendor:publish --tag=currency-converter-config
# results in config/currency-converter.php
The package exposes a facade (typically Mgcodeur\CurrencyConverter\Facades\CurrencyConverter) and a helper. Verify the exact method chain against the README once, then standardize on one style in your codebase. A typical first call looks like:
use Mgcodeur\CurrencyConverter\Facades\CurrencyConverter;
$eur = CurrencyConverter::convert(100)
->from('USD')
->to('EUR')
->get();
Where to look first, in order:
config/currency-converter.php — base currency, cache settings, rate source URL.vendor/mgcodeur/laravel-currency-converter/src/Facades — fastest way to see the actual public API rather than trusting blog posts.First real use case: converting a stored base price into the visitor's display currency. Wrap the call in a small service (see below) from day one so you can swap the driver or add caching later without touching views.
Never call the facade directly from controllers or Blade. One thin service gives you caching, logging, and a single place to change providers:
namespace App\Services;
use Mgcodeur\CurrencyConverter\Facades\CurrencyConverter;
class ExchangeRates
{
public function rate(string $from, string $to): float
{
if ($from === $to) return 1.0;
return cache()->remember(
"fx:{$from}:{$to}",
now()->addHours(12),
fn () => CurrencyConverter::convert(1)->from($from)->to($to)->get(),
);
}
public function convert(float $amount, string $from, string $to): float
{
return round($amount * $this->rate($from, $to), 2);
}
}
For orders, invoices, and anything auditable, persist the rate and the converted amount. Rates drift; a customer who paid yesterday should not see a different total today.
$rate = $rates->rate('USD', $order->currency);
$order->update([
'fx_rate' => $rate,
'total_converted' => round($order->total_usd * $rate, 2),
'fx_rate_at' => now(),
]);
Use a middleware to settle the user's currency (session, geo, or profile) into a container binding, then inject it in Blade components:
// app/Http/Middleware/ResolveCurrency.php
public function handle($request, Closure $next)
{
$currency = $request->user()?->currency
?? session('currency', config('currency-converter.default'));
app()->instance('display.currency', $currency);
return $next($request);
}
Then a Blade component formats it consistently:
<x-price :amount="49.00" /> {{-- renders €45.12 depending on session --}}
A product grid with 24 items should not trigger 24 HTTP lookups. Fetch the rate matrix once, or rely on the package's cache, then multiply locally:
$rate = $rates->rate('USD', $display);
$products->each(fn ($p) => $p->display_price = round($p->price_usd * $rate, 2));
Free rate sources are usually daily-updated. Warm the cache on a schedule so no user request ever pays the latency cost:
// app/Console/Kernel.php
$schedule->call(fn () => app(ExchangeRates::class)->rate('USD', 'EUR'))
->dailyAt('06:00');
Bind a fake into the container for HTTP tests, or Http::fake() the underlying endpoint. Never let unit tests hit the network:
$this->mock(ExchangeRates::class, function ($mock) {
$mock->shouldReceive('rate')->with('USD', 'EUR')->andReturn(0.92);
});
Rates are not real-time. These are free, keyless, typically daily-refreshed sources. Fine for displaying prices; not acceptable for FX trading, settlement, or anything where a stale rate costs money. Label displayed prices with an "indicative" disclaimer if the amount matters.
Never call the converter in a hot path without caching. The first uncached call is an outbound HTTP request. In a product listing or an API endpoint, that's a latency cliff and a failure mode when the upstream is down. Always wrap in cache()->remember() with a TTL aligned to the source's refresh cadence (often 12–24h).
Float precision will bite you. 0.1 + 0.2 problems compound when you sum converted line items. For money, either store integer minor units and convert at the end, or use bcmath/brick/money. The package returns floats; treat that as an input, not a final value.
Handle unsupported currencies gracefully. Free sources don't cover every ISO code, and crypto pairs are usually absent. Validate the currency against the config's supported list before calling, and catch exceptions to fall back to the base currency rather than 500-ing a product page.
Config caching is mandatory in production. php artisan config:cache — but remember that means env-driven config values are frozen at deploy. If you key rate TTLs off env(), they will not reflect runtime overrides.
Small package, small surface. With ~53 stars and a single maintainer, expect the source to be the documentation. Before adopting, read src/ (it's small) to understand the HTTP client, the exception types, and whether the rate source is configurable. If the driver isn't swappable, plan your ExchangeRates service wrapper to be the seam you own.
Extension points to watch for: a configurable base currency, a custom rate-source/driver contract, and cache key prefixing. If the package exposes a driver interface, implement it for your own provider (ECB feed, exchangerate.host, etc.) instead of forking. If it doesn't, your wrapper service from the first section is the extension point — that's the reason to write it early.
Debugging tip: when conversions look wrong, log both the raw rate and the timestamp it was cached. Nine times out of ten the bug is "we're reading yesterday's cached rate" or "the base currency in config isn't what we assumed," not the arithmetic.
How can I help you explore Laravel packages today?