Skip to content

Order management patterns

Order management spans two flows:

  • how to synchronize the orders and the returns into Mirakl Connect,
  • how to carry out the actions Mirakl Connect requests.

This page collects the patterns that mature connectors converge on across both flows, and the anti-patterns that recur.

Restate the whole object, and let the newest version win

What to do. Treat every upsertOrders call and every upsertReturns call as a complete restatement of the object, never as a patch. Build the order or the return from the channel's current truth, with all its lines, its full adjustment history, and its current status, and send that entire state each time. Stamp your outgoing object with the channel's own last-modified time, in channel_updated_at. Resolve any collision between two versions of the same object, and keep the one that carries the later timestamp.

Why it works. The contract offers no PATCH. A field or a line you leave out is not unchanged, it is gone, and an adjustments list that carries only the newest entry erases the ones before it. To restate the whole object removes that risk by construction, because there is nothing to omit. Connect versions the updates on channel_updated_at and silently drops any update older than the value it holds, so a redelivered or late message cannot overwrite a current order with a stale one. When you align your own conflict resolution to the same newest-wins rule, the two layers agree.

In practice. A connector holds the order's current state, keyed by channel_order_id. It folds each observed change from the channel into that state, and emits the complete object. The append-only adjustments list grows, but the connector always sends it in full. Deduplicate the events you consume by their envelope id, a ULID, so that a repeat delivery does no work twice. Read Sync orders into Connect.

Run the action loop as a durable, idempotent state machine

What to do. Give each OrderActionEvent a stored lifecycle of its own:

  1. On receipt, persist the action_id and what the action asks for.
  2. Perform the action on the channel, in a way that is safe to repeat.
  3. Report the terminal outcome, SUCCESS or FAILED, through updateActionStatus, exactly once, when the action is genuinely complete.
  4. Treat an ACTION_ALREADY_FINAL response as a confirmation that the outcome was already recorded, not as an error to retry.

Why it works. Actions are asynchronous: Mirakl Connect gives you an action_id, then waits for an outcome that can take a call to the channel to produce. An action whose outcome you never report stays open, and Mirakl Connect still expects an answer. A duplicate delivery that runs the channel operation twice ships twice or refunds twice. A stored lifecycle makes a missing outcome a condition you can see and retry, and it makes repeated deliveries apply once. To report only once the action is complete keeps you from acknowledging work the channel has not finished.

In practice. When the event arrives, write a pending record against the action_id. Execute against the channel, guarded so that a repeat is a no-op. On completion, call updateActionStatus and mark the record final. If that call returns ACTION_ALREADY_FINAL, the record is simply already closed. After an action that changes the state, follow it with an upsertOrders or an upsertReturns that reports the resulting status. Read Handle order action events.

Translate every channel failure into a precise ActionError code

What to do. When you cannot carry an action out, map the channel's refusal onto the bounded ActionError code that names it, such as ORDER_IN_WRONG_STATE, CARRIER_NOT_SUPPORTED, MISSING_CARRIER, or WAREHOUSE_ID_REQUIRED. Put the specifics in the message, which a human reads. Reserve OTHER for the failures that genuinely fit none of the named codes.

Why it works. The code is the machine-readable contract that Mirakl Connect and the seller act on. The message is explicitly subject to change, and nothing must parse it. A precise code turns a failed action into something Mirakl Connect can act on and the seller can fix. To collapse every failure into OTHER loses that information.

In practice. Keep a mapping from the channel's error vocabulary to the ActionError enum. Report the closest code, with a message that quotes the channel's own reason. Read Handle order action events.

Reconcile returns against the per-return response

What to do. Read the 200 from upsertReturns as a report, not as a receipt. Walk return_success and return_errors, record which returns Connect applied, and drive only the failed ones again. First, make sure that you synchronized the order and the order lines that each return references.

Why it works. upsertReturns is partially successful: a 200 means that the request was well-formed, not that Connect applied every return in it. A return whose order or order lines Connect has not seen yet fails inside a response that is otherwise successful. To treat the 200 as total success silently drops those failures, and the returns then never appear in Connect.

In practice. Synchronize the order with upsertOrders before its returns. Submit the returns in batches. On the response, retry the return_errors entries, and fix the referenced ids where the field points at them. Leave the return_success entries alone. Read Returns.

Anti-patterns

Upserting deltas instead of the whole object

What it looks like: the connector sends only what changed, such as the one line that shipped or the single new adjustment, and expects Connect to merge it into the order it already holds.

Why it fails: there is no merge. upsertOrders and upsertReturns replace the object wholesale, so Connect drops every line and every adjustment that is absent from the payload, instead of preserving them. A partial adjustment list rewrites the refund and cancellation history of the line down to the one entry you sent. A missing line disappears from the order. The connector believes that it made a small update, and it has instead erased everything it did not restate.

Do this instead: build and send the complete object every time, with all the lines, the full append-only adjustments history, and the current status. There is then nothing to merge and nothing to omit. Read Restate the whole object.

Reporting an action once and forgetting it, or losing the action_id

What it looks like: the connector performs the channel operation, and then it either never calls updateActionStatus, or it fires the call optimistically without tracking the action_id. It then cannot report FAILED when the channel later refuses, and it cannot tell whether it already answered.

Why it fails: an action with no reported outcome stays open, because Mirakl Connect holds it and waits. An outcome reported against a lost or wrong action_id never reaches the action it was meant for. And without a record of what it already answered, a repeat delivery drives the channel operation a second time, which is a second shipment or a second refund.

Do this instead: persist the action_id the moment the event arrives, tie the channel operation and its outcome to that record, and report the terminal status exactly once. An ACTION_ALREADY_FINAL response confirms that the outcome was already recorded, so treat it as a success, not as a failure to retry. Read Run the action loop as a durable, idempotent state machine.

Acknowledging an action without re-syncing the state it produced

What it looks like: the connector reports SUCCESS on an action, such as an accept, a shipment, or a cancellation, and stops there. It never pushes the order's resulting status back to Connect.

Why it fails: the outcome of the action and the state of the order are two separate reports. updateActionStatus tells Mirakl Connect that the action finished, and it does not carry the order's new status. An order that was just shipped still reads as AWAITING_SHIPMENT in Connect until an upsertOrders says otherwise. The copy in Connect then drifts from the channel, and every view of the order downstream is wrong.

Do this instead: treat an action that changes the state as two steps. Report the outcome with updateActionStatus, then report the new state with upsertOrders or upsertReturns. The order has not finished moving until you have sent both. Read Run the action loop as a durable, idempotent state machine.

Reading a returns 200 as total success

What it looks like: the connector submits a batch of returns, sees a 200, and moves on. It treats the status code as a confirmation that Connect applied every return.

Why it fails: upsertReturns is partially successful. The 200 reports only that the request was syntactically valid, and individual returns inside it can still fail, most often because nobody synchronized their order or their order lines. Those failures live in return_errors, not in the HTTP status. A connector that never reads them never learns that the returns are absent from Connect.

Do this instead: inspect return_success and return_errors on every response, synchronize each return's order first, and drive only the failed returns again. Read Reconcile returns against the per-return response.