The invoice, statement, and subscription model
Orbit surfaces three kinds of billing artifact, each with a precise meaning:- Invoice — a paid-style receivable document issued by the downstream billing provider (Orb for metered usage, Stripe for card checkouts). Orbit reads and renders it; it never issues it.
- Statement — a calendar-month summary of your prepaid wallet, derived purely from the append-only wallet ledger. This is the document Orbit owns end to end.
- Credit note / refund — reversals (wallet credit entry or provider refund) that adjust a prior document; in Orbit they ride the same ledger channels rather than being separate artifact types.
Documents — invoice vs statement vs adjustment
Invoices. The invoices surface (/billing/invoices, and
GET /api/v1/billing/invoices*) projects the billing provider’s read of
your receivable documents. An invoice carries a lifecycle status —
draft → issued → paid | void — plus synced for provider-side
finality. Orbit itself creates no subscription-style invoices; it lists
whatever the provider holds against your tenant id
(external_customer_id). When the provider read fails (or was never
provisioned — a new tenant sees a legitimately empty state), the surface
degrades to the durable recorded-invoice trail rather than a misleading
empty list.
Statements. A statement is a per-month (YYYY-MM) rollup computed from
the wallet ledger. Every top-up is a positive ledger entry; every usage
charge is a negative one; credits and clawbacks roll into an adjustment
line. The statement is therefore derived, not issued — deleting the
ledger rows is impossible, so a regenerated statement always agrees with
the ledger.
Adjustments instead of credit notes. Orbit does not mint separate
credit-note documents. Anything that reverses value — a card refund
against unused balance, a crypto payout, a residual credit — lands as a
signed ledger entry of type topup (positive) or usage (negative), and
the statement groups it under adjustment. Downstream providers (Orb /
Stripe) can issue their own credit notes on their side of the integration;
the Orbit statement reconciles them as ordinary signed lines.
Generation — provider invoices, plus a cost breakdown rollup
Two generation paths converge on the same surface.- Provider invoice detail. A live fetch validates the provider’s
envelope and required money fields (status, currency, totals) at the
boundary before projecting onto the summary/detail shapes — a partial
provider body fails closed rather than surfacing
undefinedfinancial figures. The download route redirects to the provider-hosted PDF or hosted invoice page, so the accounting system follows a302to the provider’s rendered document. - Usage cost breakdown. Because provider line items carry no
channel/destination attribution, each invoice detail is enriched with a
cost_breakdown— the same channel × country aggregation the live usage-by-channel surface runs, scoped to the invoice’s billing period (line-item span first, then the subscription period). Sources: your tenant’s message ledger plus voice call log. It is a proportional showback of usage for the period and intentionally excludes plan fees, minimums, discounts, and tax — so its rows need not sum to the invoice total. The enrichment is fail-open: a missing period or a database fault returns the invoice without a breakdown, never a 500.
Recurring subscriptions — two shapes, one gate
The tenant’s plan subscription. The subscription-management service (dashboard self-service, plan checkout, pay-by-link mint, portal) treats the Stripe subscription as authoritative. Lifecycle actions arecancel (schedules cancel_at_period_end), reactivate, and
change_plan; each mutation carries a deterministic idempotency key so a
retried operation cannot re-apply the change. Cancel sets a projected
*-cancel:* key; a price-swap uses *-change:*. The “Scheduled for
cancellation” bit is read from the subscription itself, not from a mutable
org flag — a webhook-only flip still renders correctly.
Commerce “subscribe & save”. An agent can renew a recurring schedule
(weekly / biweekly / monthly / quarterly) on behalf of a buyer — the
commerce layer used by agentic checkouts. The schedule is a snapshot the
caller round-trips: active → paused (freeze) → active (re-anchor,
never fire a backlog) → canceled (terminal). Every renewal executes
against the SAME payment mandate the buyer consented to at
subscribe-time — a scoped, spend-capped, SHA-256–committed authorization.
A mandate is active, and leaves active only to one of the terminal
states exhausted, revoked, or expired — after which no further cycle
is ever authorized. A renewal that the mandate declines (per-transaction
cap, total cap, non-allowed merchant/category, expired/revoked) leaves
the schedule untouched so the caller can retry after fixing the mandate.
Mandate checking is the reason no outbound loop fires on a schedule the
buyer revoked: the scheduler can mint the hosted pay link, but the charge
authorization re-verifies the consent digest first. (The rendered
re-order message rides RCS or WhatsApp outbound per the commerce layer —
never voice/SMS per the platform’s outbound invariant.)
Statement balance arithmetic — reconciling the period
A statement row carriesopening_balance, total_topups, total_usage,
and closing_balance. By construction the signed line items
(topup, usage, adjustment) sum to the delta:
reference-prefixed charge lines, or the top-up session
id). The usage_by_channel rollup covers only negative debits —
statement topup lines never include usage, so a normal paid provider
invoice does not raise the wallet’s issued-invoice subset.
When reconciling against the provider-side receivable (an invoice issued
against the wallet, or a pay-as-you-go statement), use the
revenue recognition schedule rather than
comparing raw totals — the two views converge once both period closes are
run to the same as-of date. Balance-owed on a customer account is not a
live computed figure; it is the closing-balance delta over the invoice
snapshot at period close.
Tax semantics — scoped, never estimated
Orbit never estimates tax, and this is deliberate.- Hosted checkouts (top-up / pay-by-link). Automatic tax and Adaptive
Pricing are OFF by default. The buyer pays exactly the subtotal; the
wallet credits exactly the subtotal. A “wrong total” is never silently
inflated — if a tenant enables the optional provider tax flag, the
buyer pays
subtotal + tax, the wallet is still credited the subtotal, and the tax line is stamped onto the ledger entry so wallet↔ledger reconciliation stays exact. - Provider invoices. The per-invoice tax total comes straight from
the provider’s document (
tax_amount); Orbit never synthesizes it. A nil tax field means the provider assessed none — not that Orbit estimated zero. - Reseller/metered surfaces. Where a tax line is modeled (usage-
metering rollups a platform resells to its customers), it is a scoped
optional parameter with an optional exemption certificate — only an
approvedcertificate matching the jurisdiction suppresses it. Even there, the amount is computed from explicit inputs, never estimated from address or geography.
Related
- Billing ops: invoices, statements, and pay-by-link — the operator flow across the four surfaces; this page supplies the model.
- Wallets, credits, and charges — how a wallet debit is attributed to a channel.
- Revenue recognition — the per-period schedule for matching an invoice to the ledger.
- Usage Metering API — the scoped tax input on reseller rollups.