Skip to content

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.

  1. Generate the key before the first attempt.
  2. Persist it with the business operation that will retry.
  3. Send the same key and exactly the same material fields on every retry.
  4. Treat replayed: true as success, not a duplicate action.
  5. 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.

  • 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.

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.