The Order Lifecycle, Built
PENDING → PAID → FULFILLED, with CANCELLED and REFUNDED added later. Which transitions are legal, who triggers each, and in what order to build them so that the store works before the machine is complete — the state module applied end to end on the capstone's order.
The situation, the reflex, and why it stalls
Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.
You know how to discover a state machine. In what order do you build the order's lifecycle so that every step leaves a working store, and which transitions can wait?
I have the order machine on paper. I could build all of it — every state, every transition, cancellation, refunds — before showing anyone anything, or I could build the three states checkout needs and add the rest as they are asked for. I do not know which, and I suspect "build all of it" is the reflex talking.
Implement the whole machine at once. It is on paper, it is finite, and half-built state machines feel dangerous — surely the safe thing is to have every transition in place before the first order exists.
The whole machine includes refunds, which need the provider's refund API, which is an integration nobody has done. The first order cannot be placed until the last transition is written, and the store is late for a feature no customer has used.
- The whole machine includes refunds, which need the provider's refund API, which is an integration nobody has done. The first order cannot be placed until the last transition is written, and the store is late for a feature no customer has used.
- Every transition is built against a guess about who triggers it. "Warehouse ships" is implemented as an admin button because there is no warehouse system yet, and when one arrives the transition's actor is wrong.
- The states that were guessed — is there a PACKED between PAID and SHIPPED? — are built and then argued about, when building the minimum would have let the requirement decide.
- Nothing is learned from a real order about what the machine is missing, because there are no real orders until everything is done.
The move
Precisely enough to apply it to a problem you have never seen — not a slogan.
- Build the machine along the happy path first: the states an order must pass through for the store to have done its job. For the capstone store that is PENDING (created, unpaid), PAID and FULFILLED. Build the transition function with only those rows, and reject everything else. A machine with three states and two transitions is a complete machine; it is not a half-built one, because it refuses what it does not know.
- Add exits as the requirements arrive, and add each with its actor. CANCELLED from PENDING is cheap — nothing to undo — and usually arrives first. CANCELLED-after-payment is a refund, needs the provider, and is a different piece of work; it becomes REFUNDED and it waits until the refund integration is justified by a customer asking.
- For each new transition, write who triggers it before writing the code, and build it for that actor. If the actor is a system that does not exist yet — the warehouse — build the transition behind an interface and trigger it from the admin page for now, so the actor can change without the machine changing.
- At each step the store works end to end, and each real order teaches something about the next transition. The machine grows by requirements, in the same way the architecture does (Architecture From Requirements).
The machine at step one, and at step four
Two views of the same order machine. The first is what exists after step one: three states, two transitions, and everything else rejected. The second is after the warehouse and refunds arrived. Every node in the second was added by a requirement, and every transition names its actor.
| From | Event | Actor | To | Added because |
|---|---|---|---|---|
| PENDING | payment confirmed | provider | PAID | the store must sell |
| PAID | fulfilled | admin (later: warehouse adapter) | FULFILLED → SHIPPED | the store must deliver |
| PENDING | cancel | customer, or system on timeout | CANCELLED | abandoned checkouts held stock |
| PAID | refund succeeded | system, after admin or customer request | REFUNDED | a paid order had to be undone |
| SHIPPED | delivered | carrier | DELIVERED | a real carrier reports it |
The order of building it
The sequence is one defensible order and it says why each step is where it is. The alternative — the complete machine first — is the contested view above, and the device names when it wins.
- 1PENDING → PAID → FULFILLED, with a transition function that rejects everything else
because The store sells with the smallest honest machine; the refusal is the part that cannot be retrofitted cheaply.
- 2Record every transition with actor and time
because The history is what every later step, and every support question, reads.
- 3PENDING → CANCELLED
because Cheapest exit, arrives with the first abandoned checkout, and it is the first transition that touches stock.
- 4Interface for the fulfilment actor
because The warehouse does not exist; an admin button behind an interface lets the actor change without the machine changing.
- 5PAID → REFUNDED via the provider
because An integration with all five external-system questions; built when a paid order actually needs undoing.
- 6Split FULFILLED into SHIPPED / DELIVERED
because Only when a carrier reports delivery does the distinction have an actor.
The slice that proves step one
The smallest thing that shows the machine exists and refuses: an order placed, paid in test mode, and a refund attempt rejected by name. It proves enforcement and connectivity; it does not prove anything about the transitions not yet built, and it should not pretend to.
- BrowserChecks out a cart; sees the order as pending, then paid.
- CheckoutCreates the order in PENDING; on the provider's confirmation calls transition(order, PaymentConfirmed, provider).
- Order moduleHolds the table with two rows; rejects transition(order, Refund, admin) with a named reason; records each transition.
- DatabaseOrder row with status; transition history rows.
How to do it
Most important first.
- Take the discovered machine (Finding the State Machine) and mark the happy path: the states a successful order must pass through. Build those, and only those, with a transition function that rejects the rest.
- Add the cheapest exit next — cancel before payment — and confirm the function rejects cancel after payment with a clear message.
- For each waiting state, decide the actor of the transition out of it and put an interface between the machine and that actor (Interfaces Emerge From Boundaries).
- Add refund only when there is a requirement with a customer behind it; it brings a provider integration with all five external-system questions.
- After each addition, re-ask "from here, what can happen?" against a real order's history — the transitions you were missing are in the support inbox.
Worked on a concrete problem
The move has to produce something. This is what it produced.
- Step one: PENDING → PAID (provider confirms) → FULFILLED (admin marks fulfilled). Two rows in the table. Checkout creates a PENDING order, payment moves it to PAID, an admin button moves it to FULFILLED. The store sells things. Refund, cancel and shipping states do not exist, and an attempt to refund is rejected with "no transition from PAID on Refund by admin" — a correct, honest answer.
- Step two, when a customer abandons at payment: PENDING → CANCELLED (customer, or system after a timeout). Now stock reserved for a pending order must be released, which is the first time the machine touches inventory, and the release is written into the transition's record so stock history can explain it. Step three, when a paid order needs undoing: PAID → REFUNDED, triggered by the refund succeeding, not by the cancel request. This step is the provider integration and gets its own slice.
- Step four, when a real warehouse appears: FULFILLED splits into SHIPPED and DELIVERED, with the carrier as an actor from outside, and the admin button becomes an adapter over the warehouse's notification. The machine changed because the requirement did; nothing built in step one had to be guessed.
How you know it worked
What now exists that did not before, and what question you can now ask.
- After the first step there is a working store with a three-state order, and every other transition is rejected by name rather than silently allowed.
- Each transition added since names its actor and arrived with a requirement, and you can say which customer or event caused it.
- A refund attempt on an unpaid order has always been impossible, at every step, because the table never had the row.
- The order's history shows the transitions taken, with actors, and support can read it.
The questions you can now ask
The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.
- ?Which states must a successful order pass through, and can the store work with only those?
- ?Which transition is being asked for by an actual requirement, and who triggers it?
- ?Which actor of a transition does not exist yet, and what interface stands in for it?
- ?What did the last real order's history show that the machine was missing?
What can go wrong
- The happy path is built without the rejection. A three-state machine that lets any handler set any of the three values is the reflex with fewer values; the refusal is what makes it a machine.
- Exits are added when imagined rather than when required, and the machine grows to the full paper version anyway, one guess at a time.
- The actor of a transition is not put behind an interface, so when the warehouse arrives the transition is rewritten instead of re-wired.
- The machine is grown and the state history is not, so when the first refund dispute arrives nobody can see which transitions the order took.
- Building transitions on demand means the first customer who needs a refund waits for it to be built; building it early means building an integration before its first use. The choice is which delay you would rather explain.
- A machine that rejects the unknown is honest and occasionally rejects something legitimate that nobody foresaw; a permissive column never rejects and never protects.
- Splitting FULFILLED into SHIPPED and DELIVERED later is a migration of existing orders, which is a real cost that building the split early would have avoided — if the split had been guessed right.
- "Build the minimum" means "skip the transition function." It means the minimum number of states and transitions, enforced. The enforcement is the cheap part and the part that cannot be added later without a migration of every bad value.
- "Cancel and refund are the same transition with a flag." They have different actors, different preconditions and different side effects. The machine that merges them has one word for three things, which is where this module started.
- "Once the warehouse arrives, the old FULFILLED orders are wrong." They are complete under the machine that existed when they were placed. A migration decides what the old state maps to; the history records that the mapping happened.
Where this applies
Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.
- STAGE-SPECIFICGreenfield: the machine starts with the happy path and grows. In an existing store the machine is recovered from the data, and "grow by requirements" becomes "add the refusal and migrate the values that should never have existed".
- CONTESTEDThe strongest opposing view: lifecycles are cheap to design and expensive to migrate, so build the complete machine you can foresee — including CANCELLED and REFUNDED — before the first order, because adding a state later means migrating live orders and re-deriving every report. Practitioners who hold this view point out that the order lifecycle of a store is well known, not a novel unknown, and that "grow by requirements" is right for uncertain domains and wrong for solved ones. They are right when the domain is genuinely known and the team has built it before.
- ILLUSTRATIVEThe four steps and the three-state starting machine are the capstone's teaching sequence; a real store's order of additions follows its customers, not this list.
Where the depth lives
This domain asks the question and hands the answer off by name.