Every amount on the wire is integer micro-USD encoded as a JSON string. Never a JSON number, never a decimal, never a float.
{
"balance_micro": "10000000", // $10.00
"price_micro": "50000", // $0.05
"currency": "USD"
}Why a string#
Reading amounts correctly#
const { balance_micro } = await response.json();
// CORRECT: BigInt, or keep the string and format it for display.
const micro = BigInt(balance_micro);
const whole = micro / 1000000n;
const cents = String(micro % 1000000n).padStart(6, "0").slice(0, 2);
const display = `$${whole}.${cents}`;
// WRONG: silently lossy above 2^53, and lossy in a way no test
// with small numbers will ever catch.
const broken = Number(balance_micro);Sending amounts#
The same rule applies in reverse. A top-up amount or a price you publish is a string of integer micro-USD. We refuse an amount with more precision than micro rather than rounding it, because rounding would charge or credit a different number than the one you sent.
Arithmetic#
- Add and subtract integers. Never convert to a float for intermediate maths.
- Format only at the edge, for display. Keep the integer everywhere else.
- Beware your database. A
FLOATorDOUBLEcolumn for money has the same problem one layer down; use a 64-bit integer.