docs: document adding a new currency (#681)
## Summary Adds `docs/adding-a-currency.md`, mirroring `docs/adding-a-banking-provider.md` but for currencies. Distilled from the NGN (#642) and GHS (#644) additions. The gist: adding a currency is a config change, not a feature. - **Required**: one entry in `config/currencies.php`. Validation (Form Requests), the Inertia dropdown props, and conversion all auto-derive from it via `CurrencyOptions` / `CurrencyConversionService`. - **Translation**: add the name to `lang/es.json` (enforced), `lang/fr.json` optional. Flagged that `LocalizationTest` won't auto-catch a missing one, since the name is translated in PHP, not scanned from TS source. - **Optional**: a custom short symbol in `resources/js/utils/currency.ts`. - **Two gotchas**: use the current ISO 4217 code (the GHC→GHS lesson from #644), and verify the provider covers the code at the right rate via the CDN URL. ## Testing Docs-only change; no code touched.
This commit is contained in:
parent
6490729fcc
commit
c7719fe5ad
|
|
@ -0,0 +1,107 @@
|
|||
# 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
|
||||
```
|
||||
Loading…
Reference in New Issue