Orders & fulfillments
Orders are produced by completing a checkout. Once produced, they walk a four-dimensional state machine through fulfillment and payment.
4D state machine
| Dimension | States |
|---|---|
status | `open → cancelled |
payment_status | unpaid → partially_paid → paid → partially_refunded → refunded |
fulfillment_status | `unfulfilled → partially_fulfilled → fulfilled |
inventory_status | uncommitted → committed → fulfilled |
Each dimension transitions independently — a paid order can be unfulfilled (waiting on the warehouse) and an unpaid bank-transfer order can already be fulfilled (after dispatch on trust).
Schema
| Table | Purpose |
|---|---|
orders | Header: name, totals, addresses (snapshot), status fields, customer, market |
order_line_items | Per-variant snapshot — title, sku, options, taxable, prices, line_discount, fulfillable_quantity, properties (custom attributes) |
order_number_sequences | Per-(account × market) numbering counter |
order_comments | Internal staff comments + customer-visible notes |
payments | One row per payment attempt (multi-payment per order supported) |
payment_transactions | Gateway-level events log |
fulfillments | One row per shipment (split shipments supported) |
fulfillment_items | Per-line allocation — which units of which line are in which shipment |
fulfillment_events | Carrier tracking event ingest (delivered, attempted, etc.) |
returns | RMA lifecycle |
return_items | Per-line return inspection results |
Numbering
commerce.order.number_format (default {prefix}-{seq:06d}) +
commerce.order.number_prefix (default ORD). Each order on a
given market gets the next sequence atomically.
ORD-000001
ORD-000002
EXPORT-000001 (if a market has its own override)
Override per market via the settings hierarchy:
curl -X PUT "$API/settings/commerce.order.number_format.json" \
-H "Authorization: Bearer $NEVIOS_KEY" -H "Content-Type: application/json" \
-d '{"scope_type":"market","scope_id":308...,"value":"EXPORT-{seq:06d}"}'
Fulfillments
Each shipment is one fulfillments row + fulfillment_items
rows describing which units of which order line went into it.
Two status fields:
status— operational (pending → success | cancelled)delivery_status— carrier reality (pending → in_transit | out_for_delivery | delivered | failure | returned)
Tracking events come in via fulfillment_events (carrier webhook
ingest — Phase 2 work).
Returns / RMA
requested → approved → in_transit → received → inspected → completed
↘ rejected
Per-item inspection result + refund allocation. Returns can be
created only for fulfilled orders (409 otherwise).
Endpoints (admin)
POST /admin/2026-01/{handle}/orders.json
GET /admin/2026-01/{handle}/orders.json
GET /admin/2026-01/{handle}/orders/{id_or_name}.json
POST /admin/2026-01/{handle}/orders/{id}/cancel.json
POST /admin/2026-01/{handle}/orders/{id}/comments.json
POST /admin/2026-01/{handle}/orders/{id}/payments.json
POST /admin/2026-01/{handle}/payments/{id}/refund.json
POST /admin/2026-01/{handle}/orders/{id}/fulfillments.json
PUT /admin/2026-01/{handle}/fulfillments/{id}/status.json
POST /admin/2026-01/{handle}/fulfillments/{id}/events.json
POST /admin/2026-01/{handle}/orders/{id}/returns.json
PUT /admin/2026-01/{handle}/returns/{id}/inspect.json
Plus equivalents under /storefront/2026-01/ for customer-self-service
(view-only — no order mutations from storefront).
Order conversion (the implicit path)
Most orders aren't created via POST /orders.json — they're created
by completing a checkout. commerce_api.services.orders.create_order_from_checkout
is a thin wrapper that translates a frozen checkout into the
create_order shape, preserving:
- All line item snapshots
- Both addresses (snapshot, not FK)
- Shipping selection (method name + rate)
- Discount codes used
- Totals (recomputed for safety)
- Payment intent ID (linked back to the gateway)
- Reverse-charge flag
Hand-create via POST /orders.json is reserved for staff-entered
phone orders, B2B custom quotes, and migration.
Next: Settings.