Multi-Currency & Exchange Rates
v0.1.0ISO 4217 registry, base currency immutability, daily exchange rate feeds, integer minor unit conversions, and functional GL denomination.
Multi-Currency & Exchange Rates
International businesses must transact in foreign currencies while maintaining an unambiguous, compliant General Ledger denominated in their primary statutory currency.
Obelawium Accounting provides a built-in Multi-Currency Engine exposed through ium()->accounting()->currencies().
1. Architectural Principles
- Integer Minor Units: All monetary calculations and database columns are stored as 64-bit integers in minor currency units (e.g., $100.50 USD is stored as
10050cents; 100.500 KWD is stored as100500fils). - Base Functional Currency: One currency serves as the organization’s legal ledger denominator. Its exchange rate is locked to
1.000000and cannot be altered. - Immutable Daily FX Snapshots: Exchange rates are recorded by date and provider source. Historical rates are permanently preserved.
- Dual Denomination on Postings: Vouchers capture transactions in the original transaction currency, while General Ledger postings persist both transaction units and converted base functional units (
amount_functional).
2. Managing Currencies
Registering an ISO 4217 Currency
use Obelaw\Ium\Accounting\Data\CreateCurrencyDto;
$eur = ium()->accounting()->currencies()->create(
new CreateCurrencyDto(
code: 'EUR',
name: 'Euro',
symbol: '€',
subUnit: 'Cent',
subUnitFactor: 100, // 2 decimals: 1 EUR = 100 Cents
isBase: false
)
);
Seeding Standard Currencies
To seed standard currencies (USD, EUR, GBP, JPY, SAR, AED, CAD, CHF, AUD):
$currencies = ium()->accounting()->currencies()->seedDefaultCurrencies();
3. Recording Exchange Rates
Foreign currencies quote their rate relative to the base functional currency:
ium()->accounting()->currencies()->setRate(
currencyCode: 'EUR',
rateToBase: 1.085000, // 1 EUR = 1.085 USD
effectiveDate: '2026-03-31',
source: 'ecb_daily_feed'
);
Base Rate Invariant: Calling
setRate()on the base currency throwsBaseCurrencyRateImmutableException.
4. Currency Conversion Utilities
Minor Unit Conversion (convertMinor)
Performs high-precision conversion between two currencies, taking differing sub-unit factors into account:
// Convert 10,000 EUR cents (100.00 EUR) into USD cents on 2026-03-31
$usdCents = ium()->accounting()->currencies()->convertMinor(
amountMinor: 10000,
fromCode: 'EUR',
toCode: 'USD',
date: '2026-03-31'
);
echo $usdCents; // 10850 ($108.50)
Conversion with Detailed Audit Breakdown
$result = ium()->accounting()->currencies()->convertDetailed(
amount: 1500.00,
fromCode: 'GBP',
toCode: 'USD',
date: '2026-03-31'
);
echo "From: {$result->fromCode} {$result->fromAmountMajor}\n";
echo "Rate Applied: {$result->rate}\n";
echo "To: {$result->toCode} {$result->toAmountMajor}\n";
echo "Minor Result: {$result->toAmountMinor}\n";
5. Sub-Unit Formatting & Helpers
Format minor units for presentation layers:
// Format minor units: "$ 108.50"
echo ium()->accounting()->currencies()->formatMinor(10850, 'USD');
// Format sub-units only: "50 Cents"
echo ium()->accounting()->currencies()->formatSubUnit(10850, 'USD');
// Split into major and minor components:
$split = ium()->accounting()->currencies()->splitUnits(108.50, 'USD');
// ['major' => 108, 'minor' => 50, 'formatted' => '$ 108.50']