NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Money for Laravel

The Exchange & Currencies facades

Money and Currency are plain value objects you create directly (see The Money object). The services around them sit behind two facades: Exchange for conversion and the stored rates table, Currencies for the currency registry.

Exchange

use RoundlyConsulting\Money\Currency;
use RoundlyConsulting\Money\Facades\Exchange;

Exchange::convert($money, 'USD');                          // Money — default driver, latest rate
Exchange::convert($money, 'USD', now()->subDay());         // historical
Exchange::convertWithRate($money, 'CZK');                  // Conversion: original, converted, rate
Exchange::rate(Currency::of('EUR'), Currency::of('USD'));  // ExchangeRate
Exchange::driver('database')->rate(Currency::of('EUR'), Currency::of('USD'));
Exchange::getDefaultDriver();                              // "ecb"
Exchange::extend('fixer', fn ($app) => new FixerProvider(/* ... */));
Exchange::source('ecb')->fetch($from, $to);                // the raw, un-cached source
Exchange::Returns / does
rate($from, $to, ?$on)An ExchangeRate from the default driver (null = the latest).
convert($money, $to, ?$on, ?$rounding)The converted Money, rounded once (money.exchange.rounding).
convertWithRate($money, $to, ?$on, ?$rounding)A Conversion: original, converted and the rate used.
driver(?$name) / provider(?$name)A cached driver — config, database, ecb, chain or your own.
getDefaultDriver()money.exchange.default, else ecb.
source($name)The raw, un-cached driver the refresh pipeline fetches from.
extend($driver, Closure $callback)Register a host driver — cached automatically.
forgetDrivers()Drop the resolved (cached) driver instances.
rates()The RateStore that manages the stored rates table.
fake($rates = [], $pivot = 'EUR')Swap in static rates for tests — see Testing.

Exchange::rates() — the stored rates table

rates() manages the money_exchange_rates table. Every write goes through one path, and a manual row is never overwritten by a refresh:

use RoundlyConsulting\Money\Enums\EcbFeed;
use RoundlyConsulting\Money\Facades\Exchange;

Exchange::rates()->refresh();                                        // RefreshResult: fetch the ECB daily feed now
Exchange::rates()->refresh(source: 'ecb', feed: EcbFeed::Recent, from: $from, to: $to);
Exchange::rates()->refreshLater(feed: EcbFeed::History);             // queue it (unique per source + feed)
Exchange::rates()->manual('EUR', 'CZK', '25.10', $date);             // a manual rate, today if undated
Exchange::rates()->store(...$rates);                                 // ExchangeRate ...$rates
Exchange::rates()->prune(before: now()->subYear(), includeManual: false, pretend: true); // int
Exchange::rates()->DoesReturns
refresh($source = 'ecb', $feed = EcbFeed::Daily, ?$from, ?$to)Fetch a source into the table now; fires ExchangeRatesRefreshed, or ExchangeRatesRefreshFailed and rethrows.RefreshResult
refreshLater(same arguments)Queue the same refresh — unique per source and feed for ten minutes.void
store(ExchangeRate ...$rates)Upsert rates; a stored manual row is only ever overwritten by another manual rate.RefreshResult
manual($from, $to, $rate, ?$on)One manual rate (1 from = rate × to), effective today in money.exchange.timezone unless dated.RefreshResult
prune($before, $includeManual = false, $pretend = false)Delete — or with pretend, count — rows effective before a date; manual rows stay unless included.int

The money:rates:refresh and money:rates:prune commands call the same methods, so everything they do is recorded under Exchange::fake() too.

Currencies

Currencies proxies the currency registry singleton — lookups, registration and listings (details under Currencies):

Currencies::Returns / does
get($code)The Currency — throws UnknownCurrency for an unknown code.
find($code)The Currency, or null.
has($code)Whether the code is registered.
findByNumericCode($numeric)Look up by ISO numeric code (978 or "978"), or null.
register(Currency $currency, bool $replace = false)Add a custom currency — in a service provider’s boot().
all() / iso() / custom()list<Currency> — everything, the bundled ISO list, or your own.

Currencies has no fake: the registry is in-memory and deterministic. Exchange::fake() is covered under Testing.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.