Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Core Concepts

Understanding these three concepts will help you work effectively with the billing platform.


1. NanoMoney — Why Not Floats?

All monetary amounts in this system are stored as i128 integers with scale=12 (pico-units).

This means 1 USD = 1_000_000_000_000 (one trillion raw units). The type is called NanoMoney internally, and field names always end with _nanos (e.g., total_nanos, amount_nanos, threshold_nanos).

Why not floats? IEEE 754 double-precision floats have about 15-16 significant decimal digits — which sounds like a lot until you’re summing millions of small charges. 0.1 + 0.2 != 0.3 in floating point. For a billing system handling millions of dollars, these errors compound into real money. Using i128 integers eliminates this class of bug entirely.

Why scale=12 (not 2)? Standard “cents” arithmetic (i64 with scale=2) breaks down for usage-based pricing. An AI API might charge $0.000001 per token — that’s 0.000001 * 10^12 = 1_000_000 pico-units. With scale=2, this would round to zero. With scale=12, we have sub-nanodollar precision.

In API responses, _nanos fields are serialized as strings to avoid JavaScript’s 53-bit integer limit. To display to users, divide by 10^12:

const dollars = BigInt(total_nanos) / BigInt(1_000_000_000_000);
const cents = (BigInt(total_nanos) % BigInt(1_000_000_000_000)) / BigInt(10_000_000_000);
console.log(`$${dollars}.${String(cents).padStart(2, '0')}`);

Rounding happens exactly once — at invoice output, when converting to Money (2-decimal display). Never round intermediate calculations.


2. Subscription Lifecycle

A subscription moves through these states:

                    ┌─────────────┐
                    │   Trialing  │ ← trial_days > 0
                    └──────┬──────┘
                           │ trial ends / payment succeeds
                    ┌──────▼──────┐
              ┌────►│   Active    │◄────┐
              │     └──────┬──────┘     │
              │            │ payment fails
              │     ┌──────▼──────┐     │
              │     │  PastDue    │     │ resume()
              │     └──────┬──────┘     │
              │            │ dunning exhausted
              │     ┌──────▼──────┐     │
              │     │   Paused    ├─────┘
              │     └──────┬──────┘
              │            │ period ends
              │     ┌──────▼──────┐
              └─────┤  Cancelled  │
                    └─────────────┘
                           │ period ends
                    ┌──────▼──────┐
                    │   Expired   │
                    └─────────────┘

Key fields:

  • period_start / period_end — the current billing period
  • charged_through_date — how far the subscription has been billed (advances with each successful charge)
  • status — the current lifecycle state

State transitions are enforced — the service will reject invalid transitions (e.g., you cannot resume a Cancelled subscription).


3. The Metering → Rating → Invoicing Pipeline

The billing loop flows in three stages:

Usage Events → [Metering] → UsageQuantity (Decimal)
                                    │
PriceSheet ───────────────→ [Rating] → RatedLineItem (i128 nanos)
                                    │
                            [Invoicing] → Invoice (Money, ISO precision)

Metering receives raw usage events (CloudEvents-compatible JSON), deduplicates by event id, and aggregates quantities by meter definition. Output is always a Decimal quantity — never money. Metering doesn’t know prices.

Rating takes UsageQuantity + a PriceSheet (the plan’s pricing configuration) and produces RatedLineItem records with amount_nanos: i128. The rate_all() function is a pure function — no I/O, fully deterministic. Supports flat, per-unit, graduated, volume, package, and committed pricing models.

Invoicing collects RatedLineItem records, adds proration credits if applicable, appends tax line items if a TaxAdapter is configured, and produces a finalized Invoice. Rounding from pico-units to display currency happens exactly once at this stage.

Draft invoices can be modified (add line items, apply credits). Once finalized (POST /admin/v1/invoices/:id/finalize), the invoice is immutable and receives a sequential number (INV-XXXXXX).