Order-to-cash is a multi-system transaction without a transaction coordinator: payment, allocation, picking, shipping, and invoicing each commit locally, and any step can fail after earlier steps committed. The saga pattern — a sequence of local transactions, each paired with a compensating action that semantically undoes it — is the standard answer, covered generically in MicroservicesArchitecture and SoftwareArchitecture. This page is the commerce-specific application: what the order saga actually looks like, choreography vs orchestration at each scale tier, and the open-source engines that run it.
step compensation
───────────────────────── ─────────────────────────
authorize payment void authorization
allocate inventory release allocation
release to WMS cancel pick task (if not picked)
pick + pack restock items
ship (label, manifest) — point of no return —
capture payment refund
invoice to ERP credit note
Three design facts fall out of writing the table. First, compensations are business decisions, not technical inverses — "cancel after pick" means restocking labor, and "cancel after ship" means a returns flow, so where you allow cancellation is a policy the state machine must encode. Second, there is a point of no return (carrier handoff): after it, failure handling switches from compensation to the returns process. Third, every step must be idempotent and every compensation must tolerate the step having half-happened (DataConsistencyAndSyncPatterns) — sagas run on at-least-once rails.
Choreography — no coordinator; each service reacts to events (payment hears OrderPlaced, inventory hears PaymentAuthorized…). Fewest moving parts, natural at garage/growth tier where three services chain linearly. Its failure mode is invisibility at scale: nobody owns the whole flow, so "where is order 4711 stuck, and why?" becomes archaeology across four services' logs, and adding a step means touching every neighbor.
Orchestration — an explicit coordinator owns the order state machine, commands each step, and handles failures per its policy. One place to read the flow, one place to see every stuck order, one place to change the process. Its cost: the orchestrator is real infrastructure with state that must survive crashes.
The honest tier mapping: garage — the monolith is the orchestrator (a status column plus careful code; enjoy it). Growth — choreography over the broker for the happy path, plus the reconciliation net for escapes; or an early lightweight orchestrator. Enterprise — explicit orchestration, because operability ("show me all orders stuck in allocation > 30min, by cause") stops being optional.
orders.status field, transactional transitions, and a sweeper cron that retries or alerts on stuck states. This is a real saga implementation; every pattern above applies to it; graduate when its visibility or retry logic starts being rebuilt badly.Happy-path orchestration is a week of work; the value is in the exception catalog, and pick/pack operations generate them daily: partial pick (3 of 4 items on the shelf — split-ship or hold-and-wait is a policy, encode it), payment capture fails after ship (dunning path, not a crash), customer cancels mid-flow (race against the point of no return — the state machine answers atomically), carrier rejects the manifest (re-rate and relabel, don't strand the box), backorder (the allocation step gains a waiting state with customer messaging). A useful heuristic for engine choice: if your exception catalog fits in the monolith's code legibly, you don't need an engine yet; when the catalog is what you're afraid to touch, you do.