whisper-money/docs/adding-a-currency.md

108 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)](https://github.com/whisper-money/whisper-money/pull/642),
[#644 (GHS)](https://github.com/whisper-money/whisper-money/pull/644).
---
## The one required change
Add an entry to the `options` array in `config/currencies.php`:
```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 in `ProfileUpdateRequest`, `StoreAccountRequest`,
`UpdateAccountRequest` all pull from `CurrencyOptions`, so the new code is
accepted the moment it's in config.
- **The UI dropdowns** — `HandleInertiaRequests` shares `primaryOptions()` /
`accountOptions()` as Inertia props; the React selects render whatever is there.
- **Conversion** — `CurrencyConversionService` fetches
`{date}/v1/currencies/{code}.min.json` from the `@fawazahmed0/currency-api`
CDN and `ExchangeRateService` stores 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`:
```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.
> ⚠️ `LocalizationTest` scans **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:
```ts
NGN: '₦',
```
`#644` (GHS) skipped this; `#642` (NGN) added it. Both are fine.
---
## Two things to verify before you add a code
1. **Use the current ISO 4217 code.** #644 was requested as `GHC` — the
deprecated pre-2007 Ghanaian code. The provider still serves `ghc`, 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 is `GHS`. Deprecated
codes existing in the provider does not mean they're correct.
2. **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 in `lang/fr.json` if 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)
```bash
php artisan test --compact tests/Feature/CurrencyConversionServiceTest.php \
tests/Feature/LocalizationTest.php
vendor/bin/pint --dirty
```