A small TypeScript money library for amount boundaries, minor units, rounding, allocation, formatting, and serialized money payloads.
Use decimal strings at product and API boundaries, store integer minor units in infrastructure, and move JSON payloads between services without passing money through JavaScript floating point numbers.
money-safe focuses on amount shape. It does not own exchange rates, accounting ledgers, tax policy, provider orchestration, or product pricing rules.
pnpm add @crapthings/money-safeimport { amount } from '@crapthings/money-safe';
const subtotal = amount('19.99', 'CNY').times(3);
const total = subtotal.minus('5.00').plusMinor(1200);
total.toDecimal();
// '66.97'
total.toJSON();
// { amountMinor: '6697', currency: 'CNY', minorUnit: 2 }Use decimal strings for user input and public APIs:
amount('19.99', 'CNY');
amount.decimal('19.99', 'CNY');Use minor units for databases, payment providers, and ledgers:
amount.minor(1999, 'CNY');Use serialized payloads across services and audit logs:
const payload = amount('19.99', 'CNY').toJSON();
const value = amount.fromJSON(payload);amountMinor is serialized as a string so JSON does not force large values through JavaScript Number.
Use fromNumber only when integrating with existing code that already passes decimal numbers. The rounding option is required so the conversion is visible in code review:
amount.fromNumber(19.995, 'USD', { rounding: 'half-up' });Use bigint for values that may exceed JavaScript safe integers:
amount.bigint('900719925474099.93', 'USD').toMinor();
// 90071992547409993nimport { amount } from '@crapthings/money-safe';
const subtotal = amount('39.90', 'CNY').times(2).plus('29.00');
const discount = subtotal.times('0.10', { rounding: 'half-up' });
const shipping = subtotal.toMinor() >= 9900 ? amount('0.00', 'CNY') : amount('12.00', 'CNY');
const total = subtotal.minus(discount).plus(shipping);
const paymentPayload = total.toJSON();See the checked checkout example.
import { allocate, amount, money, multiply, parseMoneyBigInt } from '@crapthings/money-safe';
const price = money(1999, 'USD');
const discounted = multiply(price, '0.875', { rounding: 'half-even' });
const shares = allocate(money(100, 'CNY'), [1, 1, 1]);
// [{ amountMinor: 34, currency: 'CNY', minorUnit: 2 }, ...]
const largeBalance = parseMoneyBigInt('900719925474099.93', 'USD');
amount('1.00', 'CNY').split(3).map((part) => part.toDecimal());
// ['0.34', '0.33', '0.33']- Store and calculate in minor units.
- Keep currency and minor-unit metadata with the amount.
- Add or subtract only matching money values.
- Use decimal strings at text/API boundaries.
- Use minor units at database/payment boundaries.
- Format only at display boundaries.
- Choose a rounding mode when converting rates into minor units.
money-safe intentionally leaves business policy in your application:
- Foreign-exchange rates.
- Tax and invoice policy.
- Ledger account movement.
- Payment-provider orchestration.
- Product pricing rules.
See:
Recipes: