2.4 KiB
money
Purpose
Boundary Zod schema for MoneyLike — { amount: bigint, currency: string }. Payment APIs work in minor units (cents, pence, satoshi, lamports) and must never round-trip through IEEE-754 floats because rounding losses on 0.10 + 0.20 are exactly the kind of defect that ends up in a compliance audit. JSON cannot natively encode bigint, so the schema accepts either a real bigint literal, a finite JSON-number, or a numeric string and coerces via BigInt().
The currency code accepts 3-8 uppercase alphanumeric characters covering ISO 4217 (USD, EUR, JPY), crypto (BTC, ETH), stablecoins (USDC), and Solana micro-units (LAMPORTS).
Features
z.preprocess-backed amount coercion handles three input shapes (bigint/number/ numericstring) and refuses everything else (objects, floats expressed as strings,NaN).- Currency regex
^[A-Z0-9]+$rejects lowercase, spaces, and mixed alphanumeric junk before any downstream lookup. nonnegative()on the bigint prevents negative-amount intents which providers universally reject.- Inferred
Moneytype drops in everywhereMoneyLikedoes because the structure is identical.
Usage: Basic
import { MoneySchema } from "@suaveplan/api-plugin-payment/schemas/money";
const parsed = MoneySchema.parse({ amount: 999n, currency: "USD" });
// { amount: 999n, currency: "USD" }
Usage: Advanced
const result = MoneySchema.safeParse(await c.req.json());
if (!result.success) {
return c.json({ error: "invalid_money", issues: result.error.issues }, 400);
}
const { amount, currency } = result.data;
const intent = await provider.createIntent({ amount: { amount, currency } });
API Reference
MoneySchema—z.object({ amount: bigint (coerced), currency: string (3..8, [A-Z0-9]+) }).Money—z.infer<typeof MoneySchema>—{ amount: bigint; currency: string }.
Implementation Notes
z.preprocess is used rather than z.coerce.bigint() because the latter happily produces BigInt("12.5") → RangeError and the error message is opaque to API consumers. The custom preprocessor inspects the input shape, only forwards values that BigInt can safely convert, and lets the downstream z.bigint() issue produce a clean failure message. Floating-point inputs are intentionally rejected — a fractional amount in minor units is a programming error, not a roundable value.