Orders
The orders resource is the runtime counterpart to Offer. Where the offer resource configures what preorder behavior looks like, the orders resource operates on individual customer orders: releasing them, charging balances, refunding, tagging, cancelling.
The resource is derived from OrderAttribution — Stoq's per-order preorder record. URLs use the Shopify order ID.
Path prefix: /preorders/orders
Order state
Every order's state is derived, not stored — a StatusCompiler reads cancelled_at, financial_status, charged_at, fulfillment_status, and total_outstanding, and returns one of:
| State | Meaning |
|---|---|
placed | Order is in the preorder flow, no money collected yet |
awaiting_balance | Deposit paid, balance still owed |
balance_collected | Balance paid, awaiting fulfillment |
released | Fulfillment holds lifted, ready to ship |
fulfilled | Shopify reports the order as fulfilled |
cancelled | cancelled_at is set |
refunded | financial_status == 'refunded' |
State drives the state machine — invalid transitions return 409 Conflict with an unblock-hint message.
Reads
| Method | Path | Description |
|---|---|---|
GET | /preorders/orders | List preorder orders in this shop, paginated. |
GET | /preorders/orders/:id | Read a preorder order's full representation. |
List filters
| Param | Notes |
|---|---|
state | Filter by derived state (post-fetch filter) |
offer_id | Scope to a single offer |
variant_id | Orders containing a specific Shopify variant |
customer_id | Filter by Shopify customer ID |
from, to | Created-at window (ISO 8601) |
page, per_page | Pagination |
The response key is orders, plus a meta pagination block:
{
"orders": [ { "id": 123456789, "state": "awaiting_balance", "...": "..." } ],
"meta": { "total_count": 47, "page": 1, "per_page": 25, "total_pages": 2 }
}Read shape
The serializer returns a rich representation including:
- Top-level:
id,shopify_order_id,state,created_at,customer, totals paymentsblock:deposit,balance,total_outstanding,charged_at,refundsfulfillmentblock:status,holds,released_attagsblock: current Shopify tagsattributed_offers: every offer this order is attributed toline_items: per-line variant + offer details
Lifecycle
The four top-level lifecycle actions are intent-bearing wrappers around state changes.
| Method | Path | Description |
|---|---|---|
POST | /preorders/orders/:id/release | Release fulfillment holds and apply offer-configured order tags. |
POST | /preorders/orders/:id/cancel | Cancel a preorder. Optionally refund deposit / balance and notify the customer. |
POST | /preorders/orders/:id/hold_fulfillments | Apply fulfillment holds on the order's fulfillment orders. |
POST | /preorders/orders/:id/split_fulfillment_orders | Split fulfillment orders so preorder items ship separately from regular items. |
Release
| Body field | Type | Default | Notes |
|---|---|---|---|
force | boolean | false | Required to release before balance is collected — otherwise returns 409 |
tag_with | array of strings | none | Extra tags applied alongside the offer-configured ones |
Cancel
| Body field | Type | Default | Notes |
|---|---|---|---|
refund_deposit | boolean | false | Issue a refund for the deposit portion |
refund_balance | boolean | false | Issue a refund for the balance portion (only valid if collected) |
notify_customer | boolean | false | Send the cancellation email |
reason | enum | none | One of customer_request, merchant_decision, inventory_unavailable, other |
Cancel is Stoq-side — it sets cancelled_at on the OrderAttribution. It does not cancel the Shopify order. The merchant cancels the Shopify order separately if they want.
Payments capability
Path prefix: /preorders/orders/:id/payments
| Method | Path | Description |
|---|---|---|
GET | (root) | Read the deposit, remaining balance, and refund state of the order. |
POST | send_collection_url | Send the remaining-balance payment-collection URL to the customer. Idempotent — resends with a fresh URL. |
POST | charge_balance | Charge the remaining balance. See modes below. |
POST | refund_deposit | Refund the deposit portion of a preorder. |
POST | refund_balance | Refund the remaining-balance portion. Only valid after the balance was collected. |
POST | refund | Refund the entire preorder (deposit + balance). |
The refund actions accept optional notify_customer (boolean) and note (string) body fields.
charge_balance modes
| Body field | Type | Default | Notes |
|---|---|---|---|
mode | enum: auto / manual | auto | auto charges the customer's saved payment method — it may fail if there's no saved method or the charge is declined. manual just records balance collection without charging — for off-platform payments. |
The body field is mode, not method — method collides with Ruby's Object#method in the interactor's context object.
Fulfillment capability
Path prefix: /preorders/orders/:id/fulfillment
| Method | Path | Description |
|---|---|---|
GET | (root) | Read the fulfillment hold state and fulfillment-order summary. |
PATCH | (root) | Update fulfillment attributes. Only hold_reason_note is writable. |
POST | apply_holds | Apply fulfillment holds on the order's fulfillment orders. Idempotent. |
POST | release_holds | Release fulfillment holds without applying tags. Use top-level release if you want tags applied too. |
POST | set_hold_reason | Set the hold-reason note (reason_note) used next time the order is held. |
fulfillment/release_holds is the diagnostics-friendly counterpart to top-level release — same fulfillment effect, no tag side effects.
On the PATCH, all_held, any_held, and fulfillment_orders are read-only — hold state changes go through apply_holds / release_holds. Setting hold_reason_note (via PATCH or set_hold_reason) updates the note on the first attributed preorder offer; existing holds already in Shopify aren't re-written.
Tags capability
Path prefix: /preorders/orders/:id/tags
| Method | Path | Description |
|---|---|---|
GET | (root) | Read the order tags applied by Stoq. |
PATCH | (root) | Replace the order's tag set (PATCH form of set). applied_by_stoq and applied_by_merchant are read-only. |
POST | add | Add one or more tags to the Shopify order. |
POST | remove | Remove one or more tags from the Shopify order. |
POST | set | Replace every tag on the Shopify order with the supplied list. |
Body
{ "tags": ["preorder", "vip"] }add and remove return 422 on an empty list. set (and the PATCH) is a true replace: pass an empty list to clear all tags. It's implemented as fetch-current + remove stale + add new on the Shopify side.
Bulk actions
Bulk endpoints fan out one job-call per order. They submit a job and return 202 Accepted with a job_id / status_url / order_count. Cap is 1000 orders per submission.
| Method | Path | Notes |
|---|---|---|
POST | /preorders/orders/bulk_release | Forwards force and tag_with. |
POST | /preorders/orders/bulk_cancel | Forwards refund_deposit, refund_balance, notify_customer, reason. |
POST | /preorders/orders/bulk_send_payment_collection_url | No extra params. |
POST | /preorders/orders/bulk_set_tags | mode is one of add / remove / replace (default add). replace overwrites each order's full tag set. |
Per-order error handling
Per-order errors are swallowed inside the job — one bad order doesn't poison the batch. The job still completes successfully. Errors are logged and visible in the audit log / Stoq Admin.
Polling
GET /preorders/orders/jobs/:job_idReturns the standard async-job envelope. See Bulk & Async Jobs.
