4.1 KiB
Adding a currency
Adding a currency is a config change, not a feature. In the common case it is a
single entry in config/currencies.php plus one translation string. Everything
else — validation, the Inertia props that feed the dropdowns, and conversion —
derives from that entry automatically.
Reference PRs: #642 (NGN), #644 (GHS).
The one required change
Add an entry to the options array in config/currencies.php:
[
'code' => 'NGN', // ISO 4217 code, stored on users/accounts
'name' => 'Nigerian Naira', // English name; the __() key for translations
'allows_primary' => true, // can be a user's primary/display currency
'allows_account' => true, // can be an individual account's currency
],
allows_primary / allows_account are independent. Assets you can hold but not
display everything in (e.g. BTC) are allows_account => true,
allows_primary => false.
That's the whole wiring. App\Services\CurrencyOptions reads this config and
feeds:
- Validation —
in:rules inProfileUpdateRequest,StoreAccountRequest,UpdateAccountRequestall pull fromCurrencyOptions, so the new code is accepted the moment it's in config. - The UI dropdowns —
HandleInertiaRequestssharesprimaryOptions()/accountOptions()as Inertia props; the React selects render whatever is there. - Conversion —
CurrencyConversionServicefetches{date}/v1/currencies/{code}.min.jsonfrom the@fawazahmed0/currency-apiCDN andExchangeRateServicestores rates as JSON. Both lowercase the code and are currency-agnostic, so any code the provider covers just works. No code change.
The translation (do this too)
CurrencyOptions runs the name through __() in PHP before handing it to the
frontend, so add the Spanish name to lang/es.json:
"Nigerian Naira": "Naira Nigeriano",
Spanish (es) is the enforced locale. French (lang/fr.json) is optional and
warning-only — add it if you can (#644 did), skip it otherwise.
⚠️
LocalizationTestscans TS/TSX source for__()keys. Currency names are translated in PHP and never appear literally in the frontend, so a missing Spanish name is not auto-caught by CI. Add it by hand or the UI shows the English name.
Optional: a custom symbol
resources/js/utils/currency.ts has a small getCurrencySymbol map for short
symbols ($, €, ₦). It falls back to the currency code, and formatCurrency
uses Intl.NumberFormat which renders its own symbol regardless — so only add an
entry if you want a specific short glyph:
NGN: '₦',
#644 (GHS) skipped this; #642 (NGN) added it. Both are fine.
Two things to verify before you add a code
-
Use the current ISO 4217 code. #644 was requested as
GHC— the deprecated pre-2007 Ghanaian code. The provider still servesghc, but at a rate scaled ×10 000 (Ghana redenominated: 1 GHS = 10 000 GHC), which would inflate every balance 10 000×. The correct modern code isGHS. Deprecated codes existing in the provider does not mean they're correct. -
Confirm the provider actually covers it, at the right rate. Open
https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/{code}.min.json(lowercase) and check the code is present and the rate is sane (e.g.EUR→NGN ≈ 1567). If it's missing, conversion silently returns the unconverted amount (logged as a warning) — the currency is still selectable, it just won't convert.
Test & checklist
- entry in
config/currencies.php(code,name,allows_primary,allows_account) - Spanish name in
lang/es.json(French inlang/fr.jsonif you can) - symbol in
resources/js/utils/currency.ts(only if you want a custom glyph) - provider covers the code at the correct rate (verified via the CDN URL)
php artisan test --compact tests/Feature/CurrencyConversionServiceTest.php \
tests/Feature/LocalizationTest.php
vendor/bin/pint --dirty