AbsoluteJS

Billing

@absolutejs/billingv0.17.2betaPlatform & Infra

Pure-function usage billing for Bun apps — declare a priced plan and compute host-portable invoices in integer micros.

Turns metered usage into invoices without float drift or vendor lock-in. createPlan declares a priced product — flat base fee, per-dimension unit prices, graduated tiers, free allowances — and computeInvoice is a pure function from a usage snapshot to line items and a total, all in integer micros. It pairs with @absolutejs/metering for usage collection and leaves Stripe, QuickBooks, or another invoicing integration to the host, so previewing an upcoming invoice, re-pricing a past period, or dry-running a plan change is a plain function call.

#Installation

BASH
bun add @absolutejs/billing

#Capabilities

Overview

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:

Show 5 more

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 a Usage snapshot into an Invoice with 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.

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 }] }

Show 10 more

Custom — { price: (chargedQuantity) => micros } (escape hatch for surge / caps / non-monotonic pricing)

Optional knobs:

freeTier — units subtracted before pricing

unit — divisor so bytesEgress priced as MB ↔ unit:

10241024

label — invoice line-item display name

Plan-level knobs:

basePriceMicros — flat fee per period

minimumChargeMicros — floor; an adjustment line item fills

any gap

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

Show 3 more

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/.

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

Show 1 more

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

Show 1 more

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.

Show 2 more

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.

Integer-micro money math

All prices are integer micros (1/1,000,000 of a currency unit — the same denomination Stripe stores internally), so float drift is structurally impossible and rounding policy is explicit.

Flexible pricing shapes

Each dimension is flat per-unit, graduated tiers, or a custom price function — the escape hatch for surge pricing, caps, and other non-monotonic schemes.

Free tiers and minimums

Per-dimension free allowances subtract before pricing, a plan-level base fee charges per period, and minimumChargeMicros floors the total with an adjustment line item.

Pure invoice computation

computeInvoice has no IO and no side effects, so you can preview invoices before the period closes, re-price history under a proposed plan, and dry-run changes before publishing.

Metering integration

Consumes Usage snapshots from @absolutejs/metering directly, and unit divisors let you price bytesEgress per MB or cpuMs per second without pre-converting.

Outcomes

What you can build

Model SaaS pricing

Declare provider-neutral plans with base prices, usage dimensions, free allowances, graduated tiers, minimum charges, labels, and explicit rounding.

Compute invoices deterministically

Preview, dry-run, and re-price usage snapshots without calling Stripe, QuickBooks, or another invoicing provider.

Hardening checklist

Production guidance

Preserve billing evidenceKeep all prices in integer micros, version the plan used for each period, retain the input usage snapshot, and push the resulting invoice through a separately idempotent provider adapter.

Follow in order

Troubleshooting path

1
An invoice total differs
Inspect each invoice line’s charged quantity, free allowance, unit divisor, tier, rounding policy, minimum-charge adjustment, and integer-micro amount before comparing external invoice output.

#@absolutejs/billing quick start

Partial snippet

# @absolutejs/billing

TS
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"

#Quick Start

Partial snippet

Declare a plan with a base fee, per-dimension prices, and free allowances, then compute an invoice from a metering snapshot.

TS
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,
    },
  },
});

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"

#Tiered Pricing

Partial snippet

Graduated tiers price each band separately — the first million requests at one rate, everything beyond at another.

TS
const plan = createPlan({
  name: 'scale',
  currency: 'usd',
  pricedDimensions: {
    requests: {
      tiers: [
        { upTo: 1_000_000, perUnitMicros: 200 },
        { upTo: Infinity, perUnitMicros: 100 },
      ],
    },
  },
});
Host-owned invoicing
This package is the cost model only. Your host application pushes the computed invoice to Stripe, QuickBooks, an internal billing engine, or a PDF generator, keeping the pricing math pure and testable.
Beta
The package is pre-1.0. The plan and invoice shapes are settling, but expect minor API adjustments before a stable release.

Current package surface

What ships today

@absolutejs/billingv0.17.2 · betaPlatform & InfranpmSource
15entry points

Import surface · click to copy