Skip to content
chiltepin

Generated from: “Show how an order moves from cart to delivered and what can go wrong at each stage.

Order lifecycle

Written by an agent from the skill, validated by chiltepin check, rendered by the renderer — shown as generated, 13 September 2026.

DOCUMENTDRAFT

Order lifecycle

How an order moves from cart to delivered, and what fails at each stage.

An order is one row in the orders table with a status column. Every stage below changes that column exactly once, and every failure has a named status so support can see where the order stopped. The happy path takes six steps; each step has one owner and one way to fail.

SECTION 01 · Note

Assumptions

Note
Single warehouse, card payment through a hosted payment gateway, one parcel carrier. Payment is captured at checkout, not at shipment. A cancelled or failed order is always refunded in full; partial refunds are out of scope.

Order states

Each order holds one status at a time. The active states on the top row are the happy path. The wait states hold the order while an outside party (customer, warehouse, carrier) acts. The terminal states never change again, and REFUNDED is the only exit for money already taken.

SECTION 02 · State machine
STATE
State machine: 14 states, 18 transitionsCARTPAYMENT_PENDINGPAIDRESERVEDPACKEDSHIPPEDPAYMENT_FAILEDBACKORDEREDRETURNED_TO_SENDERCANCELLED123456789101112131415161718
Legendstartstatewaitingendtransition
FromEventGuardTo
1s0add itemCART
2CARTcheckoutPAYMENT_PENDING
3CART7 days idleABANDONED
4PAYMENT_PENDINGpayment capturedPAID
5PAYMENT_PENDINGcard declinedPAYMENT_FAILED
6PAYMENT_FAILEDretry cardPAYMENT_PENDING
7PAYMENT_FAILED3 declinesABANDONED
8PAIDstock reservedRESERVED
9PAIDstock shortBACKORDERED
10BACKORDEREDrestockRESERVED
11BACKORDEREDcustomer cancelsCANCELLED
12RESERVEDpick completePACKED
13RESERVEDcustomer cancelsCANCELLED
14PACKEDcarrier scanSHIPPED
15SHIPPEDproof of deliveryDELIVERED
16SHIPPED3 failed attemptsRETURNED_TO_SENDER
17RETURNED_TO_SENDERreceived at warehouseCANCELLED
18CANCELLEDrefund settledREFUNDED

Checkout

Checkout is the only stage where two systems must agree before the order moves. Stock is reserved before the card is charged, so a decline never leaves a paid order with no stock. The reservation is released on decline, which is why a customer who retries can see "out of stock" on the second attempt.

SECTION 03 · Sequence
SEQUENCE
Sequence diagram: 11 messages between 4 actorsStorefrontOrders APIInventoryEXTPayment GatewayALT[all lines in stock][a line is short]ALT[approved][declined]1POST /orders/{id}/checkout2reserve(sku, qty)3reservation_id4capture(amount, card_token)5charge_id6200 PAID7decline_code8release(reservation_id)9402 PAYMENT_FAILED10short(sku, available)11409 BACKORDERED
Legendcallresponseerrorthe answer the caller getsEXTexternal actorfragment (alt / opt / loop)active

Fulfillment and delivery failures

After payment the order is out of the customer's hands and every failure needs an automatic decision. Retry when the next attempt is free. Ask the customer when it costs them time. Refund when nothing else can move the order.

SECTION 04 · Flowchart
FLOW
Flowchart: 13 stepsOrder PAIDAll lines instock?Restock ETA under14 days?Hold as BACKORDERED,email ETAPick and packCarrier scanwithin 24h?Rebook pickup, alertwarehouseDelivery attemptsucceeded?Fewer than 3attempts?Return to senderDELIVEREDCancel and refundREFUNDED12345678910111213
1yes2no3yes4no5restocked6customer cancels7yes8no9yes10no11yes, next day12no13received at warehouse
Legendstartstepdecision (diamond)nextoptionalerror pathhappy path

Failure catalog by stage

Each failure maps to one detection signal and one owner. Anything not in this table is a bug in the state machine, not a support case.

SECTION 05 · Comparison
StageWhat goes wrongHow we detect itRecoveryOwner
CARTCustomer leaves without checkoutNo activity for 7 daysMark ABANDONED, send one reminder at 24hMarketing
CARTPrice changes before checkoutLine price differs from catalog at checkoutReprice cart, show a diff, ask to confirmStorefront
PAYMENT_PENDINGCard declinedGateway returns decline_codeRelease stock, allow 3 retries, then ABANDONEDOrders API
PAYMENT_PENDINGGateway timeoutNo response in 30sQuery charge by idempotency key; never capture twiceOrders API
PAYMENT_PENDINGFraud holdGateway risk score above thresholdHold 4h for manual review, then capture or cancelRisk
PAIDStock short after paymentInventory returns short(sku)BACKORDERED if ETA under 14 days, else cancel and refundInventory
RESERVEDPick error or damaged itemPicker rejects line in WMSRe-pick from another bin; if none, treat as stock shortWarehouse
PACKEDCarrier never scans parcelNo scan within 24h of label printRebook pickup, alert warehouse leadWarehouse
SHIPPEDDelivery attempt failsCarrier event 'attempted'Retry next business day, up to 3 attemptsCarrier
SHIPPEDParcel lost in transitNo scan for 5 daysOpen carrier claim, reship if stock, else refundSupport
SHIPPEDReturn to senderCarrier event 'returned'Cancel on receipt, refund in fullWarehouse
DELIVEREDCustomer reports not receivedSupport ticket within 14 daysCheck proof of delivery; reship or refund at support's callSupport

Retry counts and day limits are the current production values; change them in config, not here.

View the Markdown
```meta
title: Order lifecycle
subtitle: How an order moves from cart to delivered, and what fails at each stage.
tag: DRAFT
```

An order is one row in the `orders` table with a `status` column. Every stage
below changes that column exactly once, and every failure has a named status
so support can see where the order stopped. The happy path takes six steps;
each step has one owner and one way to fail.

```callout
tone: note
title: Assumptions
body: "Single warehouse, card payment through a hosted payment gateway, one parcel carrier. Payment is captured at checkout, not at shipment. A cancelled or failed order is always refunded in full; partial refunds are out of scope."
```

## Order states

Each order holds one status at a time. The active states on the top row are
the happy path. The wait states hold the order while an outside party
(customer, warehouse, carrier) acts. The terminal states never change again,
and `REFUNDED` is the only exit for money already taken.

```state
id: order-states
dir: LR
states:
  - { id: s0, col: 1, row: 1, kind: start }
  - { id: cart, col: 2, row: 1, kind: wait, name: CART }
  - { id: pending, col: 3, row: 1, kind: wait, name: PAYMENT_PENDING }
  - { id: paid, col: 4, row: 1, kind: active, name: PAID }
  - { id: reserved, col: 5, row: 1, kind: active, name: RESERVED }
  - { id: packed, col: 6, row: 1, kind: active, name: PACKED }
  - { id: shipped, col: 7, row: 1, kind: wait, name: SHIPPED }
  - { id: delivered, col: 8, row: 1, kind: terminal, name: DELIVERED }
  - { id: abandoned, col: 2, row: 2, kind: terminal, name: ABANDONED }
  - { id: payfailed, col: 3, row: 2, kind: wait, name: PAYMENT_FAILED }
  - { id: backordered, col: 5, row: 2, kind: wait, name: BACKORDERED }
  - { id: returned, col: 7, row: 2, kind: active, name: RETURNED_TO_SENDER }
  - { id: cancelled, col: 4, row: 3, kind: active, name: CANCELLED }
  - { id: refunded, col: 6, row: 3, kind: terminal, name: REFUNDED }
transitions:
  - s0 -> cart: add item
  - cart -> pending: checkout
  - cart -> abandoned: 7 days idle
  - pending -> paid: payment captured
  - pending -> payfailed: card declined
  - payfailed -> pending: retry card
  - payfailed -> abandoned: 3 declines
  - paid -> reserved: stock reserved
  - paid -> backordered: stock short
  - backordered -> reserved: restock
  - backordered -> cancelled: customer cancels
  - reserved -> packed: pick complete
  - reserved -> cancelled: customer cancels
  - packed -> shipped: carrier scan
  - shipped -> delivered: proof of delivery
  - shipped -> returned: 3 failed attempts
  - returned -> cancelled: received at warehouse
  - cancelled -> refunded: refund settled
```

## Checkout

Checkout is the only stage where two systems must agree before the order
moves. Stock is reserved before the card is charged, so a decline never
leaves a paid order with no stock. The reservation is released on decline,
which is why a customer who retries can see "out of stock" on the second
attempt.

```sequence
id: checkout-sequence
actors:
  - { id: Storefront, name: Storefront }
  - { id: Orders, name: Orders API }
  - { id: Inventory, name: Inventory }
  - { id: Gateway, name: Payment Gateway, external: true }
messages:
  - Storefront -> +Orders: POST /orders/{id}/checkout
  - Orders -> +Inventory: reserve(sku, qty)
  - alt: all lines in stock
  - Inventory --> -Orders: reservation_id
  - Orders -> +Gateway: capture(amount, card_token)
  - alt: approved
  - Gateway --> -Orders: charge_id
  - Orders --> -Storefront: 200 PAID
  - else: declined
  - Gateway -x-> -Orders: decline_code
  - Orders -> Inventory: release(reservation_id)
  - Orders --> -Storefront: 402 PAYMENT_FAILED
  - end
  - else: a line is short
  - Inventory -x-> -Orders: short(sku, available)
  - Orders --> -Storefront: 409 BACKORDERED
  - end
```

## Fulfillment and delivery failures

After payment the order is out of the customer's hands and every failure
needs an automatic decision. Retry when the next attempt is free. Ask the
customer when it costs them time. Refund when nothing else can move the
order.

```flow
id: fulfillment-failures
dir: TB
nodes:
  - { id: start, col: 2, row: 1, kind: start, label: Order PAID }
  - { id: stock, col: 2, row: 2, kind: decision, label: All lines in stock? }
  - { id: eta, col: 3, row: 3, kind: decision, label: Restock ETA under 14 days? }
  - { id: hold, col: 4, row: 3, kind: process, label: "Hold as BACKORDERED, email ETA" }
  - { id: pick, col: 2, row: 4, kind: process, label: Pick and pack }
  - { id: scan, col: 2, row: 5, kind: decision, label: Carrier scan within 24h? }
  - { id: rebook, col: 3, row: 5, kind: process, label: "Rebook pickup, alert warehouse" }
  - { id: attempt, col: 2, row: 6, kind: decision, label: Delivery attempt succeeded? }
  - { id: retry, col: 3, row: 6, kind: decision, label: Fewer than 3 attempts? }
  - { id: rts, col: 4, row: 6, kind: process, label: Return to sender }
  - { id: done, col: 2, row: 7, kind: end, label: DELIVERED }
  - { id: cancel, col: 4, row: 7, kind: process, label: Cancel and refund }
  - { id: refunded, col: 4, row: 8, kind: end, label: REFUNDED }
edges:
  - start -> stock
  - stock -> pick: "yes"
  - stock -x-> eta: "no"
  - eta -> hold: "yes"
  - eta -x-> cancel: "no"
  - hold --> pick: restocked
  - hold --> cancel: customer cancels
  - pick -> scan
  - scan -> attempt: "yes"
  - scan -x-> rebook: "no"
  - rebook --> scan
  - attempt -> done: "yes"
  - attempt -x-> retry: "no"
  - retry --> attempt: "yes, next day"
  - retry -x-> rts: "no"
  - rts -> cancel: received at warehouse
  - cancel -> refunded
```

## Failure catalog by stage

Each failure maps to one detection signal and one owner. Anything not in
this table is a bug in the state machine, not a support case.

```table
id: failure-catalog
columns: [Stage, What goes wrong, How we detect it, Recovery, Owner]
rows:
  - [CART, Customer leaves without checkout, "No activity for 7 days", "Mark ABANDONED, send one reminder at 24h", Marketing]
  - [CART, Price changes before checkout, "Line price differs from catalog at checkout", "Reprice cart, show a diff, ask to confirm", Storefront]
  - [PAYMENT_PENDING, Card declined, "Gateway returns decline_code", "Release stock, allow 3 retries, then ABANDONED", Orders API]
  - [PAYMENT_PENDING, Gateway timeout, "No response in 30s", "Query charge by idempotency key; never capture twice", Orders API]
  - [PAYMENT_PENDING, Fraud hold, "Gateway risk score above threshold", "Hold 4h for manual review, then capture or cancel", Risk]
  - [PAID, Stock short after payment, "Inventory returns short(sku)", "BACKORDERED if ETA under 14 days, else cancel and refund", Inventory]
  - [RESERVED, Pick error or damaged item, "Picker rejects line in WMS", "Re-pick from another bin; if none, treat as stock short", Warehouse]
  - [PACKED, Carrier never scans parcel, "No scan within 24h of label print", "Rebook pickup, alert warehouse lead", Warehouse]
  - [SHIPPED, Delivery attempt fails, "Carrier event 'attempted'", "Retry next business day, up to 3 attempts", Carrier]
  - [SHIPPED, Parcel lost in transit, "No scan for 5 days", "Open carrier claim, reship if stock, else refund", Support]
  - [SHIPPED, Return to sender, "Carrier event 'returned'", "Cancel on receipt, refund in full", Warehouse]
  - [DELIVERED, Customer reports not received, "Support ticket within 14 days", "Check proof of delivery; reship or refund at support's call", Support]
note: "Retry counts and day limits are the current production values; change them in config, not here."
```