Complete e-commerce order state machine — from Created through Paid, Fulfillment, Shipped, Delivered, plus all the failure modes (PaymentFailed, Backordered, ReturnedUndelivered, Cancelled, Refunded).
stateDiagram-v2
[*] --> Created
Created --> AwaitingPayment: place order
AwaitingPayment --> PaymentFailed: card declined
PaymentFailed --> AwaitingPayment: retry
PaymentFailed --> Cancelled: give up
AwaitingPayment --> Paid: payment authorised
Paid --> Cancelled: customer cancels
Paid --> InFulfillment: warehouse picks
InFulfillment --> Backordered: out of stock
Backordered --> InFulfillment: stock arrives
Backordered --> Cancelled: too long
InFulfillment --> Shipped: handed to carrier
Shipped --> InTransit
InTransit --> Delivered
InTransit --> ReturnedUndelivered: failed delivery
ReturnedUndelivered --> InFulfillment: re-ship
ReturnedUndelivered --> Refunded: give up
Delivered --> ReturnRequested: customer return
ReturnRequested --> Returned: warehouse confirms
Returned --> Refunded
Delivered --> [*]: 30-day window closes
Cancelled --> Refunded: refund issued
Refunded --> [*]
An e-commerce order lifecycle with explicit handling for the unhappy paths that account for a surprisingly large fraction of real orders. After Created, the order moves through AwaitingPayment, with PaymentFailed → retry / give-up loops. Once Paid, it enters InFulfillment, where Backordered is a first-class state with its own re-entry and timeout transitions. Shipped → InTransit → Delivered is the happy path; ReturnedUndelivered and the post-delivery ReturnRequested → Returned → Refunded loops cover the rest. Cancellations from any pre-shipped state route to Refunded.
Reach for an explicit state machine like this any time multiple teams (checkout, payments, warehouse, carrier integration, customer support) need to coordinate around the same record without stepping on each other. It is also valuable as documentation when onboarding new engineers — they can see what states exist, what transitions are legal, and what the failure modes are without grepping the codebase.
Implement this in code with a state-machine library (XState, statelyai, robot, or a hand-rolled enum + transition table). Persist the current state on the order row and the transition history in a separate event-log table — that gives you both fast reads and a full audit trail. For partial fulfillment (multiple shipments per order), promote shipment to its own entity with its own state machine. For subscriptions, replace the post-delivery branch with a recurring-cycle state machine and reuse the payment branch.