Idempotency and retries
Every retriable command needs a stable identity. SAPA treats an idempotency key as the identity of one material command, not as a general request token.
Retry rule
Section titled “Retry rule”- Generate the key before the first attempt.
- Persist it with the business operation that will retry.
- Send the same key and exactly the same material fields on every retry.
- Treat
replayed: trueas success, not a duplicate action. - Generate a new key only for a genuinely new command.
import { createCheckout } from "@byfungsi/sapa";
const command = { type: "single", provider: "midtrans", customer: { externalId: "customer-42", email: "buyer@example.com", name: "Buyer", }, successUrl: "https://merchant.example/checkout/success", cancelUrl: "https://merchant.example/checkout/cancel", clientIp: null, idempotencyKey: "order-2026-000042", item: { name: "Consulting session", amount: 500_000n, currency: "IDR", fulfillment: { type: "billing_only" }, },} as const;
const checkout = createCheckout(command);Running checkout again with the same composed runtime replays the retained
outcome. Changing the amount, customer, selected offer, quantity, or other
material fields while reusing the key returns an idempotency conflict.
Which operations use keys?
Section titled “Which operations use keys?”- checkout creation
- postpaid enrollment
- original usage and every correction
- seat assignment and unassignment
- credit reservation
- license issuance, revocation, and rotation
Some transitions use the retained resource identity instead: consuming or
releasing a credit reservation uses reservationId; finalizing a metered
period uses the subscription and exact period end.
Unknown checkout outcomes
Section titled “Unknown checkout outcomes”A provider timeout can mean the provider accepted the request but the response
was lost. Do not create again with a new key. Read the checkout, then call
recoverCheckout(checkoutId). Recovery reconciles an existing attempt and
never initiates provider creation.