Add prepaid credits
Let callers pay from a prepaid balance. Credits are reserved before the handler runs, then committed or released.
Public Beta · early access
Use credits() when callers top up a balance and each call draws from it.
import { credits, memoryBalance, payPerCall } from "tollstile";
import { tollstile } from "@tollstile/hono";
const balance = memoryBalance({ acct_1: "$5.00" }); // implement Balance on your database in production
app.post(
"/v1/generate",
tollstile(
toll.price("$0.02", { access: [credits({ balance }), payPerCall()] }),
{ principal: (c) => (c.get("user") ? { id: c.get("user").id } : null) },
),
generate,
);- Signed-in callers with enough credit pass; anyone else is asked to pay per call.
- The account defaults to the authenticated principal's id. Pass
account: (context) => …to choose another.
Reserve, commit, release
A charge is recorded in the ledger before the balance is touched:
- The price is reserved on the balance.
- The handler runs.
- On success the reservation is committed; on failure it is released.
If the process dies in between, reconciliation asks the balance what happened and commits or releases — credits are never lost or spent twice.
Implement Balance on your database
import type { Balance } from "tollstile";
export const balance: Balance = {
async reserve(account, amount, key) {
// In one transaction: if a reservation with `key` exists, return "reserved".
// Otherwise, if available >= amount, subtract and insert (key, account, amount, "reserved").
return "reserved"; // or "insufficient"
},
async commit(key) { /* reserved → committed */ },
async release(key) { /* reserved → released, and add the amount back */ },
async status(key) { return "committed"; /* reserved | committed | released | none */ },
};Every method must be idempotent by key.
Retries
Send Idempotency-Key on the first request and retries. Credit charges use the policy account as payer scope: a completed charge returns 409 already_paid without another balance reservation or handler call. Without a key, each admitted request spends credits again. Released attempts can run again. See Idempotency.