Payments & Money APIs · Money & Payments Fundamentals
Representing Money Correctly
Integer minor units or decimals, ISO 4217 currencies and rounding rules.
Never use binary floating point for money (0.1 + 0.2 != 0.3). Store amounts as integers in minor units (cents) with an ISO 4217 currency code, or as fixed-precision decimals (NUMERIC(19,4)) when you need sub-cent precision (FX, unit prices, tax). Currencies have different exponents: USD 2, JPY 0, KWD/BHD 3. Always carry the currency with the amount, define rounding rules (banker's rounding vs half-up — follow your tax/legal requirements) and allocate remainders explicitly when splitting.
Money representations
| Representation | Precision | Pros | Cons |
|---|---|---|---|
| float / double | Binary approximation | None for money | Rounding errors — never use |
| Integer minor units + currency | Exact to minor unit | Simple, fast, PSP-compatible | Need exponent per currency; no sub-cent |
| Decimal / NUMERIC | Exact to chosen scale | Sub-cent math (FX, tax) | Slower; define scale and rounding |
| Money object (amount + currency) | Depends on inner type | Prevents mixing currencies | Library/convention needed |
from decimal import Decimal, ROUND_HALF_EVEN
EXPONENT = {"USD": 2, "EUR": 2, "JPY": 0, "KWD": 3}
def to_minor(amount: Decimal, currency: str) -> int:
q = Decimal(1).scaleb(-EXPONENT[currency]) # 0.01 for USD
return int((amount.quantize(q, rounding=ROUND_HALF_EVEN) / q))
def allocate(total_minor: int, parts: int) -> list[int]:
base, rem = divmod(total_minor, parts)
return [base + (1 if i < rem else 0) for i in range(parts)] # 1000 / 3 → [334, 333, 333]
// Use integers (or BigInt for very large values) — never Number arithmetic on decimals.
type Money = { amountMinor: bigint; currency: "USD" | "EUR" | "JPY" | "KWD" };
const add = (a: Money, b: Money): Money => {
if (a.currency !== b.currency) throw new Error("currency mismatch");
return { amountMinor: a.amountMinor + b.amountMinor, currency: a.currency };
};
const format = (m: Money, locale = "en-US") =>
new Intl.NumberFormat(locale, { style: "currency", currency: m.currency })
.format(Number(m.amountMinor) / 10 ** EXPONENT[m.currency]);
// BigDecimal with explicit scale and rounding; or JSR 354 (Moneta) MonetaryAmount.
BigDecimal price = new BigDecimal("19.99");
BigDecimal tax = price.multiply(new BigDecimal("0.0825")).setScale(2, RoundingMode.HALF_EVEN);
long minor = price.add(tax).movePointRight(Currency.getInstance("USD").getDefaultFractionDigits()).longValueExact();
Common pitfalls
- Floats in code or JSON for money.
- Assuming every currency has 2 decimals.
- Splitting amounts and losing a cent.
Key points
- Minor-unit integers or fixed decimals
- Amount always travels with currency
- Explicit rounding and allocation
Further reading: ISO 4217 currency codes
Practice questions
Why store money as integer minor units?
Answer
Integers are exact, fast and match most PSP APIs; floating point can't represent many decimal fractions exactly.
How do you split $10.00 three ways without losing a cent?
Answer
Work in minor units: 1000 / 3 = 333 remainder 1, so allocate [334, 333, 333] — distribute the remainder explicitly.
Open in the app to mark lessons complete, take notes and track your progress.