@absolutejs/billing
Provider-neutral pricing and invoice computation used by the hosted AbsoluteJS.ai platform.
@absolutejs/billing is the pure-function layer between
@absolutejs/metering (which collects usage events) and an
invoicing backend (Stripe, QuickBooks, an internal billing engine).
It does two things:
createPlan(...)— declares a priced product: optional flat base fee, per-dimension unit prices, optional graduated tiers, optional per-dimension free allowances.computeInvoice({ plan, period, tenant, usage })— pure function that turns aUsagesnapshot into anInvoicewith line items and a total.
All money math is done in integer micros (1 micro = 1/1,000,000
of a currency unit — the same denomination Stripe stores prices in
internally). Float drift is structurally impossible: a $0.0002
per-request price is 200 and rounding policy is explicit.
import { createPlan, computeInvoice, formatMicros } from "@absolutejs/billing";
const plan = createPlan({
name: "pro",
currency: "usd",
basePriceMicros: 20_000_000, // $20/mo
pricedDimensions: {
requests: { perUnitMicros: 200, freeTier: 1_000_000 },
cpuMs: { perUnitMicros: 50, unit: 1000, freeTier: 60_000 * 60 * 10 },
bytesEgress: {
perUnitMicros: 100,
unit: 1024 * 1024,
freeTier: 100 * 1024 * 1024,
},
hibernationGbSeconds: { perUnitMicros: 5 },
},
});
const invoice = computeInvoice({
plan,
tenant: "acme",
period: { start, end },
usage, // a Usage from @absolutejs/metering
});
console.log(formatMicros(invoice.totalMicros, invoice.currency));
// "27.50 USD"
Pricing shapes
A PricedDimension is one of three:
- Flat per-unit —
{ perUnitMicros: 200, unit: 1 } - Tiered (graduated) —
{ tiers: [{ upTo: 1_000_000, perUnitMicros: 200 }, { upTo: Infinity, perUnitMicros: 100 }] } - Custom —
{ price: (chargedQuantity) => micros }(escape hatch for surge / caps / non-monotonic pricing)
Optional knobs:
freeTier— units subtracted before pricingunit— divisor sobytesEgresspriced as MB ↔unit: 1024*1024label— invoice line-item display name
Plan-level knobs:
basePriceMicros— flat fee per periodminimumChargeMicros— floor; an adjustment line item fills any gaprounding—'truncate'(default; sub-cent → $0.00) or'round-half-up'currency— display label; not convertedmetadata— arbitrary keys that flow through to the invoice
Why pure?
The control plane needs to:
- Preview an upcoming invoice before the period closes
- Re-price a past period under a new plan ("what would this customer have paid on the proposed enterprise tier?")
- Dry-run plan changes before publishing them
A pure cost-model function makes all three trivial — no Stripe SDK,
no side effects, no IO. The Stripe push (or QuickBooks export, or
mailed-PDF generator) lives outside this package, in
@absolutejs/billing-adapters/*.
License
BSL-1.1 with named carveout against hosted SaaS billing platforms
(Metronome, Orb, Lago, Stripe Billing, m3ter, Chargebee). See
LICENSE. Change date: 2030-05-31 → Apache 2.0.
Prepaid service credits
Durable balances, reservations, capped work, and migration rules are available through the prepaid, prepaid-postgres, and credit-work subpaths.
Secure credit checkout
Purchase-only handoffs provide expiring, single-use browser capabilities without a full login session.
@absolutejs/billing/reports provides customer-facing status, receipt pagination, and usage contracts. parseUsageRange uses a half-open UTC date interval of at most 90 days (default: the last 30 days including today). Receipt cursors are positions, never authorization: bind every query, reversal join, and cursor to the caller's account. Readers must project through the public helpers, which omit provider costs, payment tokens and vault references. Usage day and feature totals must reconcile exactly; purchased dollars and consumed service credits are different measures. Automatic refill is currently unsupported.
Deferred progress
checkpointDeferred(accountId, workId, binding, expectedRevision, value) saves
progress without settling or reserving more credits. The initial revision is 0;
success returns the incremented revision and saved string. It verifies the bound
effect and authorization, rejects stale revisions and terminal work, and limits
the value to one million characters. Existing work uses the same JSON state
column; no schema migration is required.
A checkpoint is not an execution lease. Claim execution separately, persist an in-flight marker before any provider call, drain metering before saving its result, and retain the reservation if the outcome is uncertain.
Uncapped embedding plans and application budgets
Pass monthlyTokenLimit: null in EmbeddingUsageSnapshot when the configured
provider plan has no monthly embedding-token cap. monthlyBudgetTokens can
separately describe a host-owned spending guardrail; it is never reported as
provider quota. Both values are host configuration, not a live provider balance.
exhausted: true reports currently refused embeddings without assuming a monthly
cap caused the refusal. Member allowances remain the host application's policy.
Provider admission
@absolutejs/billing/provider-budget exports createProviderBudget(client, table?) using the existing CreditSqlClient transaction interface. The host owns a journaled PostgreSQL table with id text primary key, scope text, period text, reserved_micros bigint, actual_micros bigint nullable, and status text (plus a scope/period index). Reserve a defensible maximum before an external request; do not treat an estimate as a hard maximum. A repeated reservation ID never authorizes another request. Monthly request and daily cost scopes can be reserved independently; release any earlier scope if later admission fails.
Settle known fulfilled or rejected calls exactly once. An interrupted/unknown provider outcome retains its full reservation until explicitly reconciled. This ledger bounds admitted research operations, independently of member-credit pricing; it does not discover or account for provider calls made outside it. The host must account for all retries/repairs in the reservation or disable them.
Usage attribution
LedgerEntry.attribution accepts a portable UsageAttribution record (call trace, run, subject, session, funded work, release, server and tool). It passes unchanged to the commit and rollup stores and does not affect pricing. The host must retain it with the event and bind subjects after authorization. Observability identity must not change a provider event's charging/idempotency identity. Unknown external-host spend is not zero.
Paid execution policy
@absolutejs/billing/paid-work-policy exports createPaidWorkPolicy,
PaidAuthorization, and PaidWorkApprovalError for Node/Bun servers. Create one
policy per application with currentWork and withWork adapters to its funded
execution context. The context carries userSub, optional actorId and
approvalId, and a mutable closed flag. Only trusted admission code may create
or attach this context.
Wrap authenticated handlers in withPaidRequester(userSub, request.method, handler, impersonatorId?). Reads (including HEAD and OPTIONS) cannot authorize
spending, even through a nested write scope. Start paid work with POST; its
response may stream. A GET endpoint should only read previously generated data.
The transport contract and the spending contract must agree: Eden's route types
alone cannot verify handler side effects.
Require a PaidAuthorization parameter at each paid transport boundary. Obtain
it using authorizePaidWork() in the admitted execution scope, and call
requirePaidAuthorization(authorization) immediately before starting a provider
attempt. Tokens are opaque, checked at runtime, specific to the policy instance
and execution scope, and rejected after their funded/deferred scope closes.
bindPaidExecution preserves a scope for response iterators that outlive their
handler. Do not serialize tokens, accept them from clients, or cache them across
requests. A payer ID alone cannot mint a capability: authorizePaidWork requires an
authenticated or funded scope. The lower-level requirePaidWorkOwner retains a
trusted-server payer fallback for attribution; that fallback does not authorize
a provider capability.
An impersonated request requires a funded work context whose actor matches the
impersonator and which has an approval ID. withoutAutomaticSpend blocks
unapproved background calls. withDeferredPaidRequester is exclusively for a
worker that has already atomically claimed a durable approval; it closes that
permission when its callback finishes.
This policy checks execution identity and intent. The host still authenticates sessions, validates approval ownership/expiry, reserves and bounds provider costs before calling them, meters actual usage, and settles or retains uncertain reservations. An authorization token is not a credit balance or a durable execution lease. TypeScript prevents accidental missing capabilities; runtime checks remain necessary for untrusted input and changing state.