How orders work
An order's life in Era: its statuses, which actions move it, how payment and stock follow it, and the settings that steer it.
An order in Era (sales_order, the orders resource) is born either from a basket at checkout or from a draft someone builds by hand. It then waits to be sent, goes out in one or more despatches, and may come back as a return — or is cancelled or expires on the way. Payment and stock are not fields you set: they follow the order through the actions below.
complete_draft_order
draft ─────────────────────────────────────┐
│ (automatically_confirm_orders = off) │ (on)
▼ ▼
unconfirmed ──── confirm_order ──────────► unfulfilled ◄──── complete_checkout / site_place_order
│ │
└── expire_order ──► expired │ fulfil_order (part) fulfil_order (rest)
├──────────► partially fulfilled ──────────► fulfilled
cancel_order (from any open status) ──► canceled │
return_products / receive_return
▼
partially_returned · returnedThe statuses
| Status | Means | Lines can change |
|---|---|---|
draft | Being built by hand. Not an order yet: no number on the customer's side, no stock held. Can be deleted. | yes |
unconfirmed | Placed from a draft, waiting for someone to confirm it. Stock is already held. | yes |
unfulfilled | Confirmed and waiting to be sent. Every order from a checkout starts here. | no |
partially fulfilled | Some lines have gone out. (Written with a space — kept from the original shop data on purpose.) | no |
fulfilled | Everything has gone out. | no |
partially_returned / returned | Some or all of what went out has come back. | no |
canceled | Cancelled. Stock it held is released; finance is asked to refund what was paid. | no |
expired | An unconfirmed order nobody confirmed in time. | no |
An order also has an origin: checkout (from a basket) or draft (built by hand); marketplace imports add marketplace.
The actions that move it
Each action is in the shop reference; its section lists every precondition code.
| Action | From | To | What else happens |
|---|---|---|---|
retail.create_draft_order | — | draft | Takes the next order number. Refused on a closed channel. |
retail.set_order_lines | draft, unconfirmed | same | Replaces all lines, re-prices, re-applies discounts and tax; freezes draft prices for draft_order_price_freeze_hours. |
retail.complete_draft_order | draft | unfulfilled or unconfirmed | Allocates stock for every line — all or none (insufficient_stock). Checks shipping address and coupon. |
retail.complete_checkout | a basket | unfulfilled | Turns the basket's 15-minute stock reservations into allocations and deletes the basket. Refuses stale prices (prices_stale) and, unless allowed, unpaid baskets (order_not_paid). |
retail.site_place_order | a site basket | unfulfilled | The website's checkout: places the order first and takes payment right after, so a failed payment leaves an unpaid order rather than a lost one. |
retail.confirm_order | unconfirmed | unfulfilled | Needs retail:approve. |
retail.fulfil_order | unfulfilled, partially fulfilled | partially fulfilled or fulfilled | Creates a despatch from named warehouses; lowers shelf stock and writes a stock movement. Never sends more than was ordered (over_fulfilment); a preorder line waits (preorder_not_fulfillable). |
retail.cancel_fulfilment | a despatch | recalculated | Puts the stock back on the shelf. |
retail.cancel_order | any open status | canceled | Needs retail:manage. Releases held stock, gives back gift cards and the coupon use. Live despatches must be cancelled first (order_has_fulfilments). |
retail.expire_order | unconfirmed | expired | Releases held stock and the coupon. Run every 15 minutes for orders older than expire_orders_after_minutes. |
retail.request_return | anything that went out | unchanged | Opens a return (requested → received → inspected → closed, or rejected). When it is received, the order becomes partially_returned or returned; on inspection resellable goods go back into stock. |
retail.return_products | anything that went out | partially_returned or returned | Records the return directly, without the return workflow and without restocking. |
retail.delete_draft_order | draft | removed | Placed orders are cancelled, never deleted (not_a_draft). |
Lines, delivery method and customer can be changed by retail.update_order only while the order is still draft or unconfirmed (the customer also while nothing has gone out).
Payment: charge_status
Payments are not stored on the order. When an order is placed, finance opens an agreement for it; payments recorded against that agreement (by a payment app, a bank transfer, a person in Finance → Collections) are what the order has collected. After every payment, allocation, refund or provider event, Era recomputes the order's charge_status:
charge_status | When |
|---|---|
none | Nothing collected. |
partial | Less than the amount due. |
full | Exactly the amount due. |
overcharged | More than the amount due. |
The amount due is the total plus gift cards, minus refunds already granted. All amounts are whole minor units (kuruş, cents) — total_amount: 125000 is 1,250.00.
Things to know:
- Sending does not wait for payment.
fulfil_orderdoes not checkcharge_status; if you only ship paid orders, filter for them:GET /api/v1/orders?status=unfulfilled&charge_status=full. - Cancelling refunds. When an order is cancelled, finance is asked to refund what was collected for it.
- Partial refunds are granted with
retail.grant_refund(needsretail:manage; never more than the order was sold for). Finance then refunds it from the payment that can cover it — or, if no single payment can, leaves it for a person to send. authorize_statusexists on the model but is not used yet: Era does not hold card authorisations separately from charges.
Stock
| Moment | What happens to stock |
|---|---|
| Item in a basket | A reservation for 15 minutes, so two shoppers cannot buy the last one. |
| Order placed (checkout or draft) | Reservations become allocations: the goods are held for this order (quantity_allocated rises). All lines or none. |
| Despatch | Shelf count and allocation go down; a stock movement with reason fulfilment is written. |
| Despatch cancelled | Goods go back on the shelf (fulfilment_canceled). |
| Order cancelled or expired | Allocations are released. Shelf counts do not change — the goods never left. |
| Return inspected | Resellable goods go back on the shelf (returned). |
The settings that steer it
In Settings → Shop (/settings/shop):
| Setting | Default | Effect |
|---|---|---|
Confirm new orders automatically (automatically_confirm_orders) | on | A completed draft goes straight to unfulfilled; off, it waits as unconfirmed for confirm_order. Checkout orders always start unfulfilled. |
Allow unpaid orders (allow_unpaid_orders) | off | Off, complete_checkout refuses a basket with nothing paid (order_not_paid). The website's own checkout places the order first and takes payment next, whatever this says. |
Approve shipments automatically (fulfilment_auto_approve) | on | Off, a despatch is created waiting_for_approval (stock already moved) until someone approves it. |
Expire unconfirmed orders after (minutes) (expire_orders_after_minutes) | 0 (never) | Unconfirmed orders older than this expire and release their stock. |
Allow overselling (allow_oversell) | off | On, a line with too little stock is allocated as a backorder instead of refusing insufficient_stock. |
Allocation strategy (allocation_strategy) | warehouse order | Which warehouse is held first: the channel's warehouse order, or the one with the most free stock (prioritize-high-stock). |
Draft price freeze (hours) (draft_order_price_freeze_hours) | 24 | How long a draft's prices are fixed after its lines change. |
Low stock at (low_stock_threshold) | 5 | When free stock falls to this, retail.stock_low is published. |
Reacting to orders
Every step publishes an event — retail.order_placed, retail.order_confirmed, retail.fulfilment_created, retail.order_canceled, retail.order_expired, retail.order_returned, retail.order_charge_status_changed, retail.stock_low and more (the full list). Point a webhook or a workflow at them.
era orders list --status unfulfilled --charge-status full --json
era orders fulfil_order ORDER_ID --data '{"lines":[{"orderLineId":"LINE_ID","warehouseId":"WAREHOUSE_ID","quantity":1}],"notifyCustomer":true}'