GuidesOrder lifecycle

Order lifecycle

Goal: read a tenant’s orders through the fdk_ merchant API, understand the statuses an order moves through, and pick the right money-moving action for a given state.

Audience: a back-office integration authenticating with an fdk_ credential (Authorization: Bearer fdk_…). The tenant is carried by the credential, never in the path or body.

The status set

An order’s status is one of eleven values:

StatusMeaning
pending_paymentCreated, payment not yet captured.
placedCaptured and accepted — the normal post-checkout state.
processingBeing prepared for fulfillment.
on_holdPaused (fraud review, stock, manual hold).
shippedHanded to the carrier.
partially_shippedSome lines shipped, others pending.
deliveredDelivered to the shopper.
cancelledVoided before fulfillment; inventory released.
refundedFully refunded.
partially_refundedPartially refunded.
payment_failedCapture failed at checkout.

Money on every order is integer cents: subtotal_cents, discount_cents, shipping_cents, tax_cents, total_cents.

Reading orders

List the tenant’s orders

Scope read:own_orders — a read-only key can never move money.

GET /v1/merchant/orders HTTP/1.1
Host: api.ruo.pro
Authorization: Bearer fdk_live_…

Returns { orders: [...] }, each carrying its status, totals, coupon_code, and ship_to.

Get one order with items and refunds

GET /v1/merchant/orders/{id} HTTP/1.1
Authorization: Bearer fdk_live_…
{
  "order": { "id": "…", "status": "placed", "total_cents": 5911, "…": "…" },
  "items": [ { "sku": "NOVA-BPC-5MG", "quantity": 2, "line_total_cents": 4998 } ],
  "refunds": []
}
⚠️

refunds: [] is the verified “no refund records” answer. refunds: null means the refund read failed — unknown, never “none”. Do not render null as a zero-refund order.

Transitions are guarded

Every status change is a checked transition. Two codes tell you a transition was refused; branch on err.body.error.code:

  • invalid_transition — the requested move is illegal for the current status.
  • already_in_status — the order is already where you asked it to go. A retried post that walked the status the first time sees this rather than re-walking it.
  • order_fulfilled_use_returns — a 409 on cancel: the order already shipped/delivered and must go through the returns lifecycle instead.

Cancel vs refund vs return

Choose by the order’s state and what you want to happen:

You want to…Order stateUse
Void the whole order, release stock, return paymentNot yet shippedcancel
Return money but keep the order recordplaced and not fully refundedrefund
Take back a shipped item, restock, and settleShipped / deliveredreturn (RMA)

Cancel — pre-fulfillment only

POST /v1/merchant/orders/{id}/cancel HTTP/1.1
Authorization: Bearer fdk_live_…
Content-Type: application/json
 
{ "reason": "customer changed their mind" }

Scope manage:own_orders. Cancel releases reserved inventory and refunds any captured payment. The response reports reservations_released, refunded, and refunded_cents. A shipped/delivered order is refused with a 409 — go to returns.

Refund — full or as recorded

POST /v1/merchant/orders/{id}/refund HTTP/1.1
Authorization: Bearer fdk_live_…
Content-Type: application/json
 
{ "reason": "goodwill" }

The charge is derived server-side from the order — you never send an amount. The response reports the full split, including store_credit_returned_cents and gift_card_returned_cents. An already-fully-refunded order, or one with no card charge to refund (tender-only), is a 409.

Merchant refund and cancel are auto-idempotent per order — no Idempotency-Key header. A retry replays (replayed: true); it never double-refunds. This differs from the storefront-token cancel/refund/edit calls and the returns receive step, which do require the deterministic Idempotency-Key header.

Return — post-fulfillment

Shipped and delivered orders go through the returns (RMA) lifecycle (requested -> approved -> received -> refunded), where the receive step restocks and settles the refund and requires the Idempotency-Key header. See the recipe below.