duly / web/lib/money.js · raw
// Exact money arithmetic. Amounts are integers of 10^-6 USDC, never floats.export const UNIT = 1_000_000n;/** "1,234.5" -> 1234500000n. Refuses anything that is not a plain amount. */export function parseAmount(text) {  const s = String(text).trim().replace(/,/g, '');  if (!/^\d{1,12}(\.\d{1,6})?$/.test(s)) throw new Error('Enter an amount like 120.50');  const [whole, frac = ''] = s.split('.');  return BigInt(whole) * UNIT + BigInt(frac.padEnd(6, '0'));}export function formatAmount(units, { min = 2 } = {}) {  const n = BigInt(units);  const neg = n < 0n;  const abs = neg ? -n : n;  const whole = (abs / UNIT).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');  let frac = (abs % UNIT).toString().padStart(6, '0').replace(/0+$/, '');  frac = frac.padEnd(min, '0');  return `${neg ? '-' : ''}${whole}${frac ? `.${frac}` : ''}`;}/** Quantity times unit price, rounded half up to a unit. Quantity allows 3 decimals. */export function lineTotal(qtyText, priceUnits) {  const s = String(qtyText).trim();  if (!/^\d{1,7}(\.\d{1,3})?$/.test(s)) throw new Error('Enter a quantity like 2 or 1.5');  const [w, f = ''] = s.split('.');  const milli = BigInt(w) * 1000n + BigInt(f.padEnd(3, '0'));  return (milli * BigInt(priceUnits) + 500n) / 1000n;}/** Tax as basis points of the subtotal, rounded half up. */export function taxOn(subtotal, taxBps) {  return (BigInt(subtotal) * BigInt(taxBps) + 5000n) / 10000n;}export function percentToBps(text) {  const s = String(text).trim();  if (s === '') return 0;  if (!/^\d{1,3}(\.\d{1,2})?$/.test(s)) throw new Error('Enter a percentage like 18 or 7.5');  const [w, f = ''] = s.split('.');  const bps = Number(w) * 100 + Number(f.padEnd(2, '0'));  if (bps > 10000) throw new Error('A percentage cannot be above 100');  return bps;}export function invoiceTotals(doc) {  const lines = doc.items.map((it) => lineTotal(it.q, parseAmount(it.p)));  const subtotal = lines.reduce((a, b) => a + b, 0n);  const tax = taxOn(subtotal, doc.taxBps || 0);  return { lines, subtotal, tax, total: subtotal + tax };}