Multi-Currency Support
TimberCloud provides comprehensive multi-currency support powered by Stripe, enabling you to process payments in 135+ currencies worldwide with automatic decimal handling and proper currency formatting.
Supported Currencies
TimberCloud supports all currencies available through Stripe's payment processing. Currency data is sourced from the ISO 4217 standard and the currency-codes npm package for maximum accuracy.
Major World Currencies
| Currency | Code | Symbol | Decimals |
|---|---|---|---|
| US Dollar | USD | $ | 2 |
| Euro | EUR | € | 2 |
| British Pound | GBP | £ | 2 |
| Japanese Yen | JPY | ¥ | 0 |
| Canadian Dollar | CAD | C$ | 2 |
| Australian Dollar | AUD | A$ | 2 |
| Swiss Franc | CHF | CHF | 2 |
| Chinese Yuan | CNY | ¥ | 2 |
Regional Currencies
| Currency | Code | Symbol | Decimals |
|---|---|---|---|
| Indian Rupee | INR | ₹ | 2 |
| Brazilian Real | BRL | R$ | 2 |
| Mexican Peso | MXN | $ | 2 |
| Singapore Dollar | SGD | S$ | 2 |
| Hong Kong Dollar | HKD | HK$ | 2 |
| New Zealand Dollar | NZD | NZ$ | 2 |
| South African Rand | ZAR | R | 2 |
| UAE Dirham | AED | د.إ | 2 |
| Saudi Riyal | SAR | ﷼ | 2 |
| Thai Baht | THB | ฿ | 2 |
Zero-Decimal Currencies
These currencies do not use decimal places. Amounts are stored and processed as whole units:
| Currency | Code | Symbol | Region |
|---|---|---|---|
| Japanese Yen | JPY | ¥ | Japan |
| South Korean Won | KRW | ₩ | South Korea |
| Vietnamese Dong | VND | ₫ | Vietnam |
| Indonesian Rupiah | IDR | Rp | Indonesia |
| Chilean Peso | CLP | $ | Chile |
| Hungarian Forint | HUF | Ft | Hungary |
| Icelandic Króna | ISK | kr | Iceland |
| New Taiwan Dollar | TWD | NT$ | Taiwan |
| Burundian Franc | BIF | FBu | Burundi |
| Djiboutian Franc | DJF | Fdj | Djibouti |
| Guinean Franc | GNF | FG | Guinea |
| Comorian Franc | KMF | CF | Comoros |
| Malagasy Ariary | MGA | Ar | Madagascar |
| Paraguayan Guaraní | PYG | ₲ | Paraguay |
| Rwandan Franc | RWF | FRw | Rwanda |
| Ugandan Shilling | UGX | USh | Uganda |
| Vanuatu Vatu | VUV | VT | Vanuatu |
| CFA Franc (BEAC) | XAF | FCFA | Central Africa |
| CFA Franc (BCEAO) | XOF | CFA | West Africa |
| CFP Franc | XPF | ₣ | Pacific |
Three-Decimal Currencies
These currencies use three decimal places:
| Currency | Code | Symbol | Region |
|---|---|---|---|
| Bahraini Dinar | BHD | BD | Bahrain |
| Iraqi Dinar | IQD | ع.د | Iraq |
| Jordanian Dinar | JOD | JD | Jordan |
| Kuwaiti Dinar | KWD | KD | Kuwait |
| Libyan Dinar | LYD | LD | Libya |
| Omani Rial | OMR | OMR | Oman |
| Tunisian Dinar | TND | DT | Tunisia |
Setting Up Currency
Company Currency Configuration
- Navigate to Settings → Company → Locale & Currency Settings
- Select your country from the dropdown
- By default, currency is automatically detected from country:
- United States → USD
- United Kingdom → GBP
- Canada → CAD
- European countries → EUR
- Japan → JPY
- To override, uncheck "Lock to country currency"
- Select your preferred currency from the dropdown
- Click Save Changes
Currency Fields in Company Settings
| Field | Description |
|---|---|
currency | ISO 4217 currency code (e.g., USD, EUR, GBP) |
currency_symbol | Display symbol (e.g., $, €, £) |
The currency symbol is automatically set when you select a currency, but can be customized if needed.
Currency Formatting
Using the Currency Formatter Hook
The useCurrencyFormatter hook provides comprehensive currency formatting:
import { useCurrencyFormatter } from '@/hooks/useCurrencyFormatter';
function OrderTotal({ company, subtotal, tax, total }) {
const { format, formatNumber, config } = useCurrencyFormatter(company);
return (
<div>
<p>Subtotal: {format(subtotal)}</p> {/* $1,234.56 */}
<p>Tax: {format(tax)}</p> {/* $98.76 */}
<p>Total: {format(total)}</p> {/* $1,333.32 */}
{/* Access configuration */}
<p>Currency: {config.currency}</p> {/* USD */}
<p>Symbol: {config.symbol}</p> {/* $ */}
<p>Decimals: {config.decimals}</p> {/* 2 */}
</div>
);
}Formatter Methods
| Method | Description | Example |
|---|---|---|
format(amount) | Format with currency symbol | $1,234.56 |
formatNumber(amount) | Format without symbol | 1,234.56 |
formatForInput(amount) | Plain number for inputs | 1234.56 |
parse(formatted) | Parse formatted string to number | 1234.56 |
toSmallestUnit(amount) | Convert to Stripe cents/smallest unit | 123456 |
fromSmallestUnit(amount) | Convert from Stripe to normal | 1234.56 |
isValidAmount(value) | Validate currency input | true/false |
Locale-Specific Formatting
Currency display adapts to the company's locale:
// US locale (en-US)
format(1234.56); // "$1,234.56"
// German locale (de-DE)
format(1234.56); // "1.234,56 €"
// Japanese locale (ja-JP, zero decimal)
format(1234); // "¥1,234"
// Indian locale (en-IN, lakh system)
format(1234567.89); // "₹12,34,567.89"Stripe Integration
Currency Unit Conversion
Stripe processes amounts in the smallest currency unit. TimberCloud handles this automatically:
import i18n from '@/util/i18n';
// USD: $100.00 → 10000 cents
const stripeAmount = i18n.toStripeCurrencyUnit(100, 'USD');
// Result: 10000
// JPY: ¥100 → 100 (no conversion, zero-decimal)
const stripeAmountJPY = i18n.toStripeCurrencyUnit(100, 'JPY');
// Result: 100
// KWD: KD 1.500 → 1500 fils (3 decimals)
const stripeAmountKWD = i18n.toStripeCurrencyUnit(1.5, 'KWD');
// Result: 1500
// Converting back from Stripe
const amount = i18n.fromStripeCurrencyUnit(10000, 'USD');
// Result: 100.00Getting Currency Multiplier
import i18n from '@/util/i18n';
i18n.getCurrencyMultiplier('USD'); // 100 (2 decimals)
i18n.getCurrencyMultiplier('JPY'); // 1 (0 decimals)
i18n.getCurrencyMultiplier('KWD'); // 1000 (3 decimals)Decimal Places
import i18n from '@/util/i18n';
i18n.getDecimalPlaces('USD'); // 2
i18n.getDecimalPlaces('EUR'); // 2
i18n.getDecimalPlaces('JPY'); // 0
i18n.getDecimalPlaces('KRW'); // 0
i18n.getDecimalPlaces('BHD'); // 3
i18n.getDecimalPlaces('KWD'); // 3Country to Currency Mapping
TimberCloud automatically maps countries to their primary currency:
import i18n from '@/util/i18n';
// Get currency by country code
const usCurrency = i18n.getCurrencyByCountry('US');
// { code: 'USD', symbol: '$', name: 'US Dollar', digits: 2 }
const ukCurrency = i18n.getCurrencyByCountry('GB');
// { code: 'GBP', symbol: '£', name: 'Pound Sterling', digits: 2 }
const jpCurrency = i18n.getCurrencyByCountry('JP');
// { code: 'JPY', symbol: '¥', name: 'Yen', digits: 0 }
// Works with various input formats
i18n.getCurrencyByCountry('usa'); // USD
i18n.getCurrencyByCountry('UK'); // GBP (normalized)
i18n.getCurrencyByCountry('GBR'); // GBP (alpha-3 supported)European Union
All EU member states default to EUR:
- Germany, France, Spain, Italy, Netherlands
- Belgium, Austria, Ireland, Portugal, Finland
- Greece, Luxembourg, Slovakia, Slovenia, Malta
- Cyprus, Estonia, Latvia, Lithuania
Currency Validation
Check Currency Support
import i18n from '@/util/i18n';
// Check if currency code is valid ISO 4217
i18n.isSupportedCurrency('USD'); // true
i18n.isSupportedCurrency('EUR'); // true
i18n.isSupportedCurrency('XXX'); // false (not a real currency)
// Check if currency is supported by Stripe
i18n.isStripeSupportedCurrency('USD'); // true
i18n.isStripeSupportedCurrency('CNY'); // true
i18n.isStripeSupportedCurrency('IRR'); // false (not supported by Stripe)Get Currency Details
import i18n from '@/util/i18n';
const currency = i18n.getCurrencyByCode('GBP');
// {
// code: 'GBP',
// symbol: '£',
// name: 'Pound Sterling',
// digits: 2,
// number: '826'
// }Currency Symbols
TimberCloud provides proper currency symbols for all supported currencies:
| Symbol | Currencies |
|---|---|
| $ | USD, CAD, AUD, NZD, SGD, HKD, MXN, CLP, COP, ARS |
| € | EUR |
| £ | GBP |
| ¥ | JPY, CNY |
| ₹ | INR |
| ₩ | KRW |
| ₫ | VND |
| ₱ | PHP |
| ฿ | THB |
| ₽ | RUB |
| ₺ | TRY |
| ₪ | ILS |
| R$ | BRL |
| R | ZAR |
| kr | SEK, NOK, DKK, ISK |
| zł | PLN |
| Kč | CZK |
Best Practices
1. Store Raw Values
Always store monetary amounts as raw numbers in the database, without formatting:
// ✓ Good: Store raw number
order.total = 1234.56;
// ✗ Bad: Don't store formatted strings
order.total = "$1,234.56";2. Format Only for Display
Apply formatting only when displaying to users:
// In database/calculations: raw numbers
const subtotal = 100.00;
const tax = 8.25;
const total = subtotal + tax; // 108.25
// In UI: formatted display
return <span>{formatter.format(total)}</span>; // "$108.25"3. Use Proper Stripe Conversion
Always convert to smallest unit before Stripe API calls:
// ✓ Correct
const stripeAmount = i18n.toStripeCurrencyUnit(order.total, order.currency);
await stripe.paymentIntents.create({
amount: stripeAmount,
currency: order.currency.toLowerCase(),
});
// ✗ Wrong: Passing decimal amount directly
await stripe.paymentIntents.create({
amount: order.total, // This will be wrong!
currency: order.currency,
});4. Handle Zero-Decimal Currencies
Be aware of zero-decimal currencies in calculations:
const currency = 'JPY';
const decimals = i18n.getDecimalPlaces(currency);
if (decimals === 0) {
// Round to whole number for JPY
total = Math.round(total);
}5. Validate Currency on Input
Always validate currency codes before processing:
if (!i18n.isSupportedCurrency(currencyCode)) {
throw new Error(`Unsupported currency: ${currencyCode}`);
}
if (!i18n.isStripeSupportedCurrency(currencyCode)) {
throw new Error(`Currency not supported for payment: ${currencyCode}`);
}Troubleshooting
"Currency not supported" Error
- Verify the currency code is valid ISO 4217
- Check that Stripe supports the currency for your account type
- Some currencies are restricted by region or account type
Incorrect Decimal Display
- Check that
decimalsis correctly set for the currency - Verify locale formatting isn't adding/removing decimals
- For zero-decimal currencies, ensure amounts are whole numbers
Symbol Display Issues
- Currency symbols are derived from Intl.NumberFormat when available
- Fallback to static symbol map for unsupported browsers
- Some symbols may display differently across fonts
Stripe Amount Mismatch
- Always use
toStripeCurrencyUnit()before Stripe API calls - Verify you're using the correct multiplier (100, 1, or 1000)
- Double-check zero-decimal currency handling
Parsing Formatted Values
- Use
formatter.parse()to correctly parse locale-formatted numbers - European locales use
,as decimal separator (1.234,56) - Some locales use space as thousands separator (1 234,56)