Skip to content

Metered billing

Metered billing is a server-to-server workflow. Do not expose usage recording or invoice finalization directly to untrusted clients.

flowchart LR
  accTitle: Metered billing lifecycle
  accDescr: A metered offer progresses through enrollment, usage, corrections, period finalization, invoice checkout, and authenticated payment.
  Register["Register metered offer"] --> Enroll["Enroll postpaid subscription"]
  Enroll --> Usage["Record positive usage"]
  Usage --> Correction["Append signed corrections"]
  Correction --> Finalize["Finalize exact period"]
  Finalize --> Checkout["Create invoice checkout"]
  Checkout --> Payment["Authenticate payment notification"]
import { enrollPostpaidSubscription, recordMeteredUsage } from "@byfungsi/sapa";
import { Effect } from "effect";
const program = Effect.gen(function* () {
const enrollment = yield* enrollPostpaidSubscription({
idempotencyKey: "api-enrollment-customer-42",
externalCustomerId: "customer-42",
offerKey: "api.metered",
offerRevision: 1,
});
return yield* recordMeteredUsage({
idempotencyKey: "request-batch-2026-09-16T12:00Z",
subscriptionId: enrollment.subscriptionId,
meter: { key: "api.request", revision: 1 },
quantity: 120n,
occurredAt: Date.UTC(2026, 8, 16, 12),
});
});

Original usage must be positive and occur in a valid retained period. Its meter, rate, period, and subscription head are authenticated historical facts.

import { correctMeteredUsage } from "@byfungsi/sapa";
const correction = correctMeteredUsage({
idempotencyKey: "request-batch-2026-09-16T12:00Z-correction-1",
originalUsageIdempotencyKey: "request-batch-2026-09-16T12:00Z",
quantityDelta: -20n,
correctedAt: Date.UTC(2026, 8, 17, 9),
});

A correction always references the original usage key, never another correction. It appends history and does not rewrite the original record.

finalizeMeteredInvoice({ subscriptionId, periodEndsAt }) closes exactly the current period. Finalization aggregates first, applies the retained rational rate once, and records billing watermarks. Exact retries return the retained invoice.

The result can be payment_due, not_due, or credit_due. Only payment_due can create an invoice checkout. Read the authoritative lifecycle with getMeteredInvoiceCollection(invoiceId).

Usage accepted after close and post-close corrections become signed adjustments on the next invoice at the original rate. Finalized invoices never mutate.