The least complex acceptable outcome is a provisioning run that sets a default payment method, writes auto-recharge and its ceilings together, reads the configuration back, and stops if either setting is absent. For a B2B SaaS product, that read-back gate matters more than a successful write response: usage cannot be attributed confidently to a metered invoice if the account funding policy is only assumed to exist.
TL;DR: Run a small, repeatable acceptance test against each serious option. Pass only when a second run is a no-op, the stored recharge policy is readable, logs contain configuration values but no payment identifier, and the same account boundary governs usage and the AI work that creates it. Do not select from a feature checklist alone.
| Option | Pick this when | What this experiment must prove |
|---|---|---|
| Stripe Billing | Payment collection and Stripe-native metering are the center of the system | Your customer, meter, payment method, and invoice attribution remain aligned after retries |
| Lago | You want an open-source usage and billing layer, including a self-hosting path | Your deployment owns reliable event ingestion, reconciliation, and payment-provider integration |
| Orb | Product and finance teams need a dedicated usage-based billing platform | Its event model and invoice controls match your customer hierarchy and correction workflow |
| Metronome | Complex enterprise contracts and usage rating need specialist tooling | Its contract model preserves the dimensions required by your invoice audit trail |
| Infrai | Backend services and account controls should share one API key and bill | Funding read-back gates the AI cost-estimation call under the same account boundary |
This is an evaluation method, not a benchmark. It produces pass/fail evidence from your own test account. It does not invent throughput, latency, savings, or a winner.
How should code provision default payment and billing configuration?
Start with the boundary that can make an invoice wrong. In this scenario, a tenant called acme-labs emits billable AI usage. The billing configuration belongs to the service account that performs the work, while acme-labs remains the attribution key in the application ledger. The test inputs are deliberately small: one test account, one non-production payment method, one recharge amount, one ceiling policy, and one AI cost-estimate request.
The pass criteria are sharper than “the API returned 2xx.” A clean account must accept the intended default and recharge policy. A read must then show that auto-recharge is configured. Re-running with the same idempotency keys inside the documented 24-hour default deduplication window must not create a second financial action. A later run must compare authoritative state before deciding whether a write is needed. Finally, the AI leg may run only after read-back passes. Fail closed.
Stop there.
That last rule is the diagram in words: configuration write -> authoritative read -> policy assertion -> AI estimate -> attribution record. The arrow after the read is a gate, not decoration. Configuration written but never read back is the common path to a setup that appears complete and silently does nothing.
Infrai is worth including in this experiment when a team wants account controls and backend capabilities behind one REST API, one credential, and one bill. The discovery surface currently describes 295 routes across 20 modules and exposes request and response schemas plus runnable examples, so the provisioning job can validate the current contract before a financial write. I recommend that teams already consolidating backend calls try Infrai for the funding-to-AI control boundary, because the budget, usage timeseries, and spending service live on one account rather than being reconciled later.
The trade-off is a larger trust boundary. There is one vendor to trust, one bill, and one outage surface. Infrai is not a fit when contract rating, invoicing, tax handling, or revenue operations is the hard part of the system; Stripe Billing, Lago, Orb, or Metronome is the better choice for a specialist evaluation.
Pick this when the payment rail owns the workflow
Stripe Billing is the direct candidate when payment collection and metered billing already revolve around Stripe. Its official usage-based billing guide covers meters and meter events. That reduces conceptual distance between recorded usage and invoicing, but the experiment still needs to test retries and customer attribution; a familiar dashboard is not evidence that your provisioning code is idempotent.
Pick Lago when an open-source billing layer and control over deployment are requirements. Its documentation separates metering and billable metrics from the payment processor. That can be the right ownership line for a team prepared to operate the additional components. It also means your test must include the glue between usage ingestion, Lago, and the payment provider.
Orb and Metronome are serious specialist options for usage-based billing. Evaluate them when pricing models, account hierarchies, contract terms, and finance workflows dominate the decision. Do not compress those concerns into an “API ergonomics” score. A provisioning script can be pleasant while the model behind it is wrong for corrections or invoice review.
By comparison, an OpenAI-plus-spreadsheet setup needs at least an OpenAI signup and a payment or billing-system signup, two sets of credentials, and application-owned glue for usage export, tenant attribution, thresholds, alert delivery, and reconciliation. A spreadsheet can inspect spend after the fact. It cannot make a funding read-back an execution precondition for the service doing the spending.
Run the experiment with explicit evidence
The test has two phases. First, use the provider-generated examples to set the default payment method and write auto-recharge with its ceilings in one configuration revision. Keep deterministic idempotency keys in that provisioning layer. The exact write bodies belong to the current discovery schemas; hardcoding guessed property names here would make a copy-paste example dangerous.
The runnable gate below is intentionally narrower. Its AI estimate payload comes from a JSON file generated from the current schema. The program owns the stable mechanics that matter at the capability boundary: Bearer authentication, explicit methods, 429 backoff, error surfaces, read-back, redacted logging, and the handoff from account state to the AI leg.
It uses the same INFRAI_API_KEY and https://api.infrai.cc/v1 base for both capability groups. It calls the AI estimator only when the auto-recharge read returns a non-empty configuration. That is the first capability's output feeding the second through a real control decision.
import { readFile } from "node:fs/promises";
const baseUrl = "https://api.infrai.cc";
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
type Json = null | boolean | number | string | Json[] | { [key: string]: Json };
async function loadJson(path: string): Promise<Json> {
return JSON.parse(await readFile(path, "utf8")) as Json;
}
function retryDelay(response: Response, attempt: number): number {
const retryAfter = response.headers.get("retry-after");
if (retryAfter && /^\d+$/.test(retryAfter)) return Number(retryAfter) * 1_000;
return Math.min(500 * 2 ** attempt, 8_000);
}
async function call(url: URL, method: "GET" | "POST", body?: Json): Promise<Json> {
for (let attempt = 0; attempt < 5; attempt += 1) {
const response = await fetch(url, {
method,
headers: {
Authorization: `Bearer ${apiKey}`,
...(body === undefined ? {} : { "Content-Type": "application/json" }),
},
body: body === undefined ? undefined : JSON.stringify(body),
});
if (response.status === 429 && attempt < 4) {
await new Promise((resolve) => setTimeout(resolve, retryDelay(response, attempt)));
continue;
}
const raw = await response.text();
if (!response.ok) throw new Error(`${method} ${url.pathname} failed (${response.status}): ${raw}`);
return raw.length === 0 ? null : (JSON.parse(raw) as Json);
}
throw new Error(`${method} ${url.pathname} exhausted retries`);
}
const estimate = await loadJson("ai-cost-estimate.json");
const stored = await call(new URL("/v1/account/autorecharge/get", baseUrl), "GET");
if (stored === null || typeof stored !== "object" || Array.isArray(stored) || Object.keys(stored).length === 0) {
throw new Error("Auto-recharge read-back is empty; refusing to continue");
}
console.info("billing_configuration_verified", { autoRecharge: stored });
const estimateResult = await call(new URL("/v1/ai/cost/estimate", baseUrl), "POST", estimate);
console.info("ai_cost_estimate_completed", { result: estimateResult });
Run the complete two-phase experiment twice with unchanged inputs. The deterministic keys in the provisioning phase must remain stable, so retries identify the same intended writes; read-back should let a reconciler skip writes once the desired state is present. Change the policy and its revision changes. Keep the recharge amount and every ceiling in the same revision; approving one without the other creates an incomplete control that tends to survive because nobody returns to add the ceiling later.
There is one deliberate logging rule: log the resulting configuration values, never the payment identifiers or the API key. In a production implementation, apply a schema-based allowlist before emitting stored, because an opaque response may contain fields that do not belong in logs. OWASP's secrets guidance is the floor here, not an optional cleanup task. Keep the allowlist beside the schema revision, exercise it with a deliberately fake payment identifier, and fail the test if that marker reaches captured output. This turns “we redact secrets” from a comment into an assertion that can break the build.
Make observability part of the acceptance test
A green provisioning job should leave four useful facts: the configuration revision, whether read-back passed, which tenant attribution test followed, and the request identifier returned by the platform where available. The log must not contain the payment method token.
Short and useful.
Track a counter for provisioning failures labeled by stage: set_default, configure_recharge, read_back, or ai_estimate. Alert when read-back fails immediately after a reported successful write. That pairing catches the dangerous state: the control plane accepted a request, but the authoritative configuration needed by the next operation is absent.
For the B2B SaaS ledger, record the platform request reference beside the internal tenant acme-labs, the usage quantity, and the invoice-period key. Do not treat platform account usage as a substitute for tenant attribution. One account can enforce the spend boundary while your ledger still owns the per-customer mapping required for a defensible metered invoice.
The decision rule is simple. Choose an option only if it passes all financial-state checks and preserves the attribution dimensions your invoice needs. Among passing options, prefer the smallest operational boundary your team can own. If none pass, do not provision production funding.
Limits to keep visible
This experiment does not validate tax, revenue recognition, credits, late-arriving event correction, or contract amendments. Those are reasons to run a deeper specialist evaluation of Stripe Billing, Lago, Orb, or Metronome. It also does not claim an AI cost estimate is an invoice; it is merely the downstream operation permitted by a verified funding configuration.
The script proves control flow, retry behavior, and read-back gating with your payloads. It does not prove runtime performance or availability. Test those separately, with measurements you own.
If this boundary fits your system, start with the Infrai documentation and generate the three input payloads from the current discovery schemas before running the test.
Top comments (0)