Skip to content

Money & paisa

Trile is a payments system, so money handling is strict and non-negotiable.

  1. Smallest unit is paisa. 1 NPR = 100 paisa.
  2. Amounts are integers in paisa, never rupees, never decimals.
  3. Amounts are serialized as strings, not JSON numbers — to survive values larger than Number.MAX_SAFE_INTEGER and to forbid float arithmetic.
  4. Currency is NPR for v1. The field is always present.

Most money fields appear either as a flat *_paisa string or as a Money object:

{ "amount_paisa": "49900", "currency": "NPR" }

"49900" paisa = NPR 499.00.

Do the conversion at the edge (UI), using integer math — never store or compute in rupees.

// paisa string → display rupees
function formatNpr(paisa: string): string {
const n = BigInt(paisa); // BigInt, not Number
const rupees = n / 100n;
const sub = (n % 100n).toString().padStart(2, '0');
return `NPR ${rupees}.${sub}`;
}
formatNpr("49900"); // "NPR 499.00"
formatNpr("100"); // "NPR 1.00"

To go the other way for a request body, multiply by 100 with integer math and send a string:

function toPaisa(rupees: number): string {
// round to avoid 4.999999 float drift, then to paisa
return String(Math.round(rupees * 100));
}
toPaisa(499); // "49900"
  • Don’t send amount_paisa: 49900 as a JSON number — send the string "49900".
  • Don’t send rupees (499 or 499.00) anywhere. The API expects paisa.
  • Don’t sum amounts with + on numbers; use BigInt (or your language’s decimal/bigint type).

Internally every amount is a PostgreSQL bigint and a typed Money value object with no escape hatch to number. The wire format mirrors that on purpose — so a careless float can’t enter the system through the API either.