npm packages are not installable yet: [email protected] can be published after 2026-09-16 10:30 UTC. Details
Tollstile

Test payment failures

Simulate provider outages, timeouts, and rejections with the test rail before real money is involved.

Public Beta · early access
import { createTollstile, memoryLedger, testRail } from "tollstile";
import { fakeClock } from "tollstile/testing";

const rail = testRail();
const clock = fakeClock();
const toll = createTollstile({ rails: [rail], ledger: memoryLedger({ clock }), clock });

rail.simulate({ settle: "timeout-after-effect" }); // money moved, but the answer was lost
// …call a priced route: the charge ends `unknown`

rail.simulate({});
clock.advance(60_000);
await toll.reconcile({ olderThanMs: 1_000 });
// the charge is `settled`, and rail.effects.settlements is still 1
SimulationEffect
verify: "unavailable"503, handler not run
verify: "paid"The payment moves during verification, like a pushed on-chain transfer: the charge is settled before the handler
challenge: "unavailable"The test rail's offer is left out of the 402; with no other rail, 503 payment_unavailable
settle: "reject"Charge failed, SETTLEMENT_REJECTED event; the adapter withholds the output and sends a fresh 402 with error code settlement_rejected
settle: "timeout-before-effect"unknown; reconciliation retries or releases
settle: "timeout-after-effect"unknown; reconciliation records the settlement once
refund: "timeout-after-effect"unknown; reconciliation records the refund once
lookup: "unavailable"Reconciliation leaves the charge pending

rail.effects counts settlements, refunds, and releases so tests can assert nothing happened twice. tollstile/testing also exports httpContext() and mcpContext() to drive gates without a framework.

Retries

Send Idempotency-Key on the first paid request and keep it on retries of the same request (on MCP: _meta["tollstile/idempotency-key"]). A completed charge returns 409 already_paid; an in-flight charge returns 409 request_in_progress; an unknown outcome returns 503 payment_outcome_unknown. Do not start a new payment while the outcome is unknown. Released/refunded attempts may run again. See Idempotency for request matching and rail-specific key scopes.

On this page