@crapthings/money-safe
v0.1.0
Published
A small TypeScript money library for amount boundaries, minor units, rounding, allocation, and serialized payloads.
Maintainers
Readme
money-safe
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.
Install
pnpm add @crapthings/money-safeQuick Start
import { 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 }Boundary Guide
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();
// 90071992547409993nCheckout Payload
import { 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.
Examples
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']Core Ideas
- 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.
Not For
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.
API
See:
Recipes:
