Skip to content

Exact money

SAPA represents runtime monetary amounts as bigint minor units with an explicit currency. It never uses floating-point arithmetic for money. Version 0.1.0 defines exact semantics for IDR and USD; other three-letter codes are rejected until their minor-unit policy is registered centrally.

import { Billing } from "@byfungsi/sapa";
const billing =
yield *
Billing.oneTime({
price: { currency: "IDR", amount: 149_000n },
});

149_000n IDR means 149,000 rupiah. Amounts in current checkout, price, and payment definitions are positive. Signed adjustments use distinct constrained types.

The same representation supports currencies with fractional minor units:

const usdPrice = { currency: "USD", amount: 14_900n } as const;

14_900n USD means USD 149.00 because Polar and other USD payment rails express amounts in cents. SAPA stores the integer and currency together; it never stores 149.00 as a floating-point number and never converts currencies implicitly.

JSON has no bigint value. Use the facade-owned codecs instead of applying a global BigInt.prototype.toJSON patch.

import { ProductCatalog, ProductCatalogJson } from "@byfungsi/sapa";
import { Schema } from "effect";
declare const catalog: ProductCatalog;
const wireCatalog = Schema.encodeSync(ProductCatalogJson)(catalog);
const json = JSON.stringify(wireCatalog);
const decoded = Schema.decodeUnknownSync(ProductCatalogJson)(JSON.parse(json));

Encoded amounts are canonical decimal strings such as "100000". The codecs reject JSON numbers, signs, decimal points, exponent notation, leading zeroes, and values longer than 78 digits.

A metered rate is rational:

const rate = {
currency: "IDR",
amount: 25n,
perUnits: 10n,
rounding: "trunc",
} as const;

SAPA aggregates the period quantity and applies the rate once. It does not round each usage event independently.