The order lifecycle
Every status an order can hold, what moves it, what locks it, and when stock is reserved or restored.
Every order holds exactly one status. This page lists them all, what moves an order between them, and — the part that matters most — when stock moves.
The statuses
| Status | Meaning | Stock |
|---|---|---|
DRAFT | Built from a message, not yet confirmed by the customer. | Not reserved |
PAYMENT_PENDING | Confirmed. Stock reserved, prices frozen, deadline running. | Reserved |
PAYMENT_REVIEW | A receipt arrived and was rejected. Locked until a new one arrives. | Reserved |
PAID | You approved the payment. Invoice issued. | Reserved (sold) |
PACKING | Being picked and packed. | Sold |
OUT_FOR_DELIVERY | On the van. | Sold |
DELIVERED | Complete. | Sold |
CANCELLED | Cancelled by the customer or by you. | Restored |
EXPIRED | The payment deadline passed unpaid. | Restored |
CONFIRMED exists on old records. It is not used by the live flow — a confirm
moves an order straight from DRAFT to PAYMENT_PENDING.
The normal path
DRAFT → PAYMENT_PENDING → PAID → PACKING → OUT_FOR_DELIVERY → DELIVERED
What moves an order
| Transition | Triggered by |
|---|---|
DRAFT → PAYMENT_PENDING | The customer tapping Confirm |
PAYMENT_PENDING → PAID | You, and only you |
PAYMENT_PENDING → PAYMENT_REVIEW | You rejecting a receipt |
PAYMENT_REVIEW → PAYMENT_PENDING | The customer sending a fresh receipt |
PAYMENT_PENDING → EXPIRED | The deadline passing, unpaid |
Any pre-fulfilment status → CANCELLED | The customer, or you |
PAID → PACKING → OUT_FOR_DELIVERY → DELIVERED | You, from WhatsApp or the dashboard |
Which statuses are open to change
A draft is the only status you can add to. While an order is a draft, any further order-shaped message from that customer joins it, and adding a product already on the order merges into that line.
Once it moves to awaiting payment it is locked. A new order-shaped message gets told payment is outstanding, rather than either appending to a reserved order or opening a second one silently. A request to change a quantity or remove a line raises an alert for you instead of acting.
This boundary was deliberately tightened. It used to sit much later, and the result was orders changing shape after the customer had already been told what to pay.
Expiry
An unpaid order expires at its deadline — normally 24 hours after confirm.
Expiry restores the stock and marks the order EXPIRED, which is deliberately
distinct from CANCELLED: one is a deadline, the other is a decision, and you
will want to tell them apart when you look back at a month.
Expiry is safe to run twice. It re-checks the status and the deadline before touching anything, so a payment landing at the same moment can never cause a double restore.
Extending a deadline
EXTEND FB-1204 pushes the deadline out, from WhatsApp or from the order
screen. Use it when a customer says the transfer is coming — it is cheaper than
letting the order expire and rebuilding it.
Extending is only available on statuses where a deadline is meaningful. The button is disabled elsewhere for the same reason the command refuses: they read the same list.
Reading an order's history
Every order screen shows its full payment history, including rejected receipts. That history is the audit trail. A rejected receipt disappearing would remove exactly the record you would want if a customer disputed something.

