Why a status column isn’t enough
The tempting first model is a singlestatus field on the order — pending, paid, shipped, done. It works in a demo and falls apart in production, because the interesting part of an order is not the states. It is the waits between them. Every wait is a place where a human has to do something, and might not. A string in a column has no idea that time is passing, or that an order has been sitting in paid for nine days while the seller ignores it.The happy path already forks
Even the happy path is not a straight line, because demoda lets a buyer either have an item mailed or collect it in person, and those are genuinely different flows. Once payment clears, a mailed order waits for the seller to ship and then sits in shipping; an in-person order goes straight to waiting for pickup. Both converge on fulfilled, at which point the seller gets paid.A timeout on every edge
This is the discipline that holds the whole thing together, and it is worth stating plainly: nothing waits forever. Every state above that waits on a person has its own timeout, and they do not fire blind — they nudge first. A seller who hasn’t shipped gets reminded, twice; a buyer who hasn’t picked up or confirmed receipt gets reminded too. Only if the reminders are ignored does the timeout actually fire.And when it does, most states resolve forward. A shipped order the buyer never confirms auto-completes so the seller still gets paid; the same is true for a pickup that is never confirmed. The one state that resolves backward is payment itself: if the buyer never pays inside the window, the order is cancelled and the reserved stock goes back on sale. None of these are edge cases we bolted on after the first bad week — on a marketplace the unhappy paths are the product.When it goes wrong: judging
Two things break the happy path outright. A seller can reject an order they can’t fulfil — if it was paid by card, the buyer is refunded automatically; if it was a bank transfer, which can’t be clawed back with an API call, it goes to a human. And a buyer can report a problem, which moves the order into judging — a hold where the dispute is actually decided.From judging there are exactly two ways out: favour the seller and the order fulfils and funds release, or favour the buyer and it refunds. Judging itself has a time limit, so even a contested order can’t sit forever — if no one resolves it in time, it defaults to fulfilled. timeout / hold dispute / refund success
The app is never the source of truth
One rule sits underneath all of this: the client never decides anything that involves money. The app can request — start a checkout, mark a package sent — but the states that matter are moved by the backend, reacting to a payment provider’s webhook or to one of those timers. When a buyer pays, we do not trust the app to tell us so; we wait for MercadoPago to confirm it, and only then does the order become paid.Every transition is a testable handler
Because each transition is its own handler rather than a branch buried in a giant function, each one can be tested in isolation — including the ones that are annoying to reproduce for real. You can test “seller accepts but never ships” without standing up a marketplace, waiting three days, and bribing someone not to ship a jacket. That is the quiet payoff of an explicit machine: the scary parts become ordinary unit tests.What it bought us
Orders resolve themselves. Support is not chasing packages that have been “shipping” for two weeks. Money is never sitting in an ambiguous place with no owner. And when someone does ask what happened to an order, the answer is a path through a diagram, not an archaeology dig through logs. The pattern is not specific to fashion, or even to marketplaces — it applies anywhere two parties transact over time and either one can go quiet.Building something where the unhappy paths matter as much as the happy one? That is the part we like.
This describes an architectural pattern from our work on demoda, abstracted to the design level. Read the full project write-up in the demoda case study.