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

DimensionStates
status`open → cancelled
payment_statusunpaid → partially_paid → paid → partially_refunded → refunded
fulfillment_status`unfulfilled → partially_fulfilled → fulfilled
inventory_statusuncommitted → 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

TablePurpose
ordersHeader: name, totals, addresses (snapshot), status fields, customer, market
order_line_itemsPer-variant snapshot — title, sku, options, taxable, prices, line_discount, fulfillable_quantity, properties (custom attributes)
order_number_sequencesPer-(account × market) numbering counter
order_commentsInternal staff comments + customer-visible notes
paymentsOne row per payment attempt (multi-payment per order supported)
payment_transactionsGateway-level events log
fulfillmentsOne row per shipment (split shipments supported)
fulfillment_itemsPer-line allocation — which units of which line are in which shipment
fulfillment_eventsCarrier tracking event ingest (delivered, attempted, etc.)
returnsRMA lifecycle
return_itemsPer-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.