Home
Preorders

Orders

Preorder order lifecycle, payments, fulfillment, tags, and bulk actions.

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:

StateMeaning
placedOrder is in the preorder flow, no money collected yet
awaiting_balanceDeposit paid, balance still owed
balance_collectedBalance paid, awaiting fulfillment
releasedFulfillment holds lifted, ready to ship
fulfilledShopify reports the order as fulfilled
cancelledcancelled_at is set
refundedfinancial_status == 'refunded'

State drives the state machine — invalid transitions return 409 Conflict with an unblock-hint message.

Reads

MethodPathDescription
GET/preorders/ordersList preorder orders in this shop, paginated.
GET/preorders/orders/:idRead a preorder order's full representation.

List filters

ParamNotes
stateFilter by derived state (post-fetch filter)
offer_idScope to a single offer
variant_idOrders containing a specific Shopify variant
customer_idFilter by Shopify customer ID
from, toCreated-at window (ISO 8601)
page, per_pagePagination

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
  • payments block: deposit, balance, total_outstanding, charged_at, refunds
  • fulfillment block: status, holds, released_at
  • tags block: current Shopify tags
  • attributed_offers: every offer this order is attributed to
  • line_items: per-line variant + offer details

Lifecycle

The four top-level lifecycle actions are intent-bearing wrappers around state changes.

MethodPathDescription
POST/preorders/orders/:id/releaseRelease fulfillment holds and apply offer-configured order tags.
POST/preorders/orders/:id/cancelCancel a preorder. Optionally refund deposit / balance and notify the customer.
POST/preorders/orders/:id/hold_fulfillmentsApply fulfillment holds on the order's fulfillment orders.
POST/preorders/orders/:id/split_fulfillment_ordersSplit fulfillment orders so preorder items ship separately from regular items.

Release

Body fieldTypeDefaultNotes
forcebooleanfalseRequired to release before balance is collected — otherwise returns 409
tag_witharray of stringsnoneExtra tags applied alongside the offer-configured ones

Cancel

Body fieldTypeDefaultNotes
refund_depositbooleanfalseIssue a refund for the deposit portion
refund_balancebooleanfalseIssue a refund for the balance portion (only valid if collected)
notify_customerbooleanfalseSend the cancellation email
reasonenumnoneOne of customer_request, merchant_decision, inventory_unavailable, other
Note

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

MethodPathDescription
GET(root)Read the deposit, remaining balance, and refund state of the order.
POSTsend_collection_urlSend the remaining-balance payment-collection URL to the customer. Idempotent — resends with a fresh URL.
POSTcharge_balanceCharge the remaining balance. See modes below.
POSTrefund_depositRefund the deposit portion of a preorder.
POSTrefund_balanceRefund the remaining-balance portion. Only valid after the balance was collected.
POSTrefundRefund the entire preorder (deposit + balance).

The refund actions accept optional notify_customer (boolean) and note (string) body fields.

charge_balance modes

Body fieldTypeDefaultNotes
modeenum: auto / manualautoauto 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.
Note

The body field is mode, not methodmethod collides with Ruby's Object#method in the interactor's context object.

Fulfillment capability

Path prefix: /preorders/orders/:id/fulfillment

MethodPathDescription
GET(root)Read the fulfillment hold state and fulfillment-order summary.
PATCH(root)Update fulfillment attributes. Only hold_reason_note is writable.
POSTapply_holdsApply fulfillment holds on the order's fulfillment orders. Idempotent.
POSTrelease_holdsRelease fulfillment holds without applying tags. Use top-level release if you want tags applied too.
POSTset_hold_reasonSet the hold-reason note (reason_note) used next time the order is held.
Note

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

MethodPathDescription
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.
POSTaddAdd one or more tags to the Shopify order.
POSTremoveRemove one or more tags from the Shopify order.
POSTsetReplace 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.

MethodPathNotes
POST/preorders/orders/bulk_releaseForwards force and tag_with.
POST/preorders/orders/bulk_cancelForwards refund_deposit, refund_balance, notify_customer, reason.
POST/preorders/orders/bulk_send_payment_collection_urlNo extra params.
POST/preorders/orders/bulk_set_tagsmode 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_id

Returns the standard async-job envelope. See Bulk & Async Jobs.