Return process mutation on exchange items causes partially paid order and incorrect suggested financial outcome

Background

I use two mutations for creating returns:

Both on API version 2026-01.

Some of my merchant stores are on the legacy workflow, and some are on the new workflow, which affects how their returns interact with returnProcess mutation - introduced in 2025-07 (migration guide).


Business Requirement

When a return contains exchange items, I need the fulfillment order for those exchange items to be created immediately after the return is opened — before the refund is processed. The refund happens later in a separate step via an automated flow.

This means I cannot call returnProcess on exchange line items and return line items simultaneously. They must happen in separate calls.


Behavior per Workflow

Legacy Workflow :white_check_mark:

After calling returnCreate, exchange items are already processed automatically. A fulfillment order is created on hold with:

"fulfillment_hold": {
  "reason": "awaiting_return_items"
}

Later, when I call returnProcess for the return line items, the SuggestedReturnFinancialOutcome is correct and the refund amount is accurate.

New Workflow :cross_mark:

After returnCreate, exchange items are not automatically processed. To create the fulfillment order, I call returnProcess with only the exchange line items:

Step 1 — returnCreate:

{
  "verb": "return_created",
  "arguments": [{
    "return_line_items": [
      { "id": 56359321864, "quantity": 1 },
      { "id": 56359354632, "quantity": 1 },
      { "id": 56359387400, "quantity": 1 }
    ],
    "exchange_line_items": [
      { "id": 978714888, "quantity": 1 }
    ]
  }]
}

Step 2 — returnProcess (exchange items only):

{
  "verb": "return_processed",
  "arguments": [{
    "return_line_items": [],
    "exchange_line_items": [
      { "id": 978714888, "quantity": 1 }
    ]
  }]
}

This creates the fulfillment order, but with an unexpected hold reason:

"fulfillment_hold": {
  "reason": "awaiting_payment",
  "reason_notes": null
}

This also causes the order to become PARTIALLY_PAID, and as a result, the SuggestedReturnFinancialOutcome is always reduced by the total value of the exchange items.

Step 3 — returnProcess (return line items, called later):

{
  "verb": "return_processed",
  "arguments": [{
    "return_line_items": [
      { "id": 56359321864, "quantity": 1 },
      { "id": 56359354632, "quantity": 1 },
      { "id": 56359387400, "quantity": 1 }
    ],
    "exchange_line_items": []
  }]
}

At this point the suggested refund is incorrect — it is short by the total value of the exchange items. Weirdly, this was not happening before and this three-step flow was working perfectly for my use case.


Questions

  1. Why does calling returnProcess on only exchange line items in the new workflow cause an awaiting_payment hold and a PARTIALLY_PAID order status, while the legacy workflow handles the same scenario without these side effects?

  2. Is there a supported way to create the exchange fulfillment order immediately after returnCreate — without processing the return line items at the same time — that does not result in an awaiting_payment hold or affect the SuggestedReturnFinancialOutcome?

  3. Is this a known behavioral difference between the legacy and new workflows, and if so, what is the recommended pattern for handling deferred refunds with exchanges in the new workflow?

2 Likes

Hey @Berk_Ali_Cam,

The difference between the legacy and new workflows comes down to when exchange items get processed. In the legacy workflow, returnCreate handled exchange items automatically. In the new workflow, nothing happens to exchange items until returnProcess is called. When you call returnProcess with only exchange line items, the system records the exchange obligation without the return credit to offset it. That’s what produces the awaiting_payment hold, PARTIALLY_PAID status, and the incorrect suggestedFinancialOutcome.

The new workflow you should send both returnLineItems and exchangeLineItems to be passed together in a single returnProcess call. The examples in the migration guide follow this pattern.

For your use case (exchange FO created immediately, refund deferred), financialTransfer is optional on ReturnProcessInput. I tested passing both returnLineItems and exchangeLineItems in a single call with financialTransfer omitted:

  • Exchange fulfillment order created immediately
  • No refund issued (totalRefundedSet stayed at $0)
  • suggestedFinancialOutcome returned correct amounts
  • Order financial status stayed clean (not PARTIALLY_PAID)

The exchange FO will still have an awaiting_payment hold when the exchange is net-payable (exchange item costs more than the return). That’s expected in the new workflow. In the legacy workflow you saw awaiting_return_items instead because the exchange was processed at a different stage.

The change on your end would be passing both sets of line items to returnProcess at the same time, and having your automated refund flow handle the financial settlement as a separate step rather than through a second returnProcess call.

Hi Kyle,

Thanks for the clear explanation — that makes sense. I’ll update the call to pass both return and exchange line items together and test it out on our end.

Appreciate the help!

1 Like

In the end I decided to go with the intended flow, so I create the FO during refund. I also had a question about this legacy and new workflows. Is there a way to switch my shop to new workflow? How is this being determined?

To switch, we do have a checklist on the migration guide on what will need to change: Migrate to return processing

Thanks for the suggestion — I’ve already done the migration and my new workflow stores are working as expected.

My question is more about how Shopify determines that a shop is still on the legacy flow in the first place. Is it an internal feature flag rolled out gradually? Is it tied to the shop’s creation date, an installed app, or some other criteria? And is there any GraphQL field exposed that lets me query that state per shop?

Asking because I have some merchant stores that are still on the legacy behavior (after returnCreate mutation exchange items are already processed) despite me using API version 2026-01, and I’d like to understand what drives that difference.

Hey @Berk_Ali_Cam ,

The new workflow (where returnProcess is required for exchange items) is the expected behavior across most shops at this point. Merchants aren’t opting into this or toggling anything on their end (except for some plus merchants using test drive features), unlike customer accounts where there’s a clear legacy vs non-legacy split.

For the shops you’ve identified that are still on the old behavior, if they are plus shops they may not have yet opted in to the new exchanges and for any others, those are likely edge cases where the migration hasn’t fully completed yet. There isn’t a GraphQL field to query which workflow a given shop is on however, over time you’ll see fewer and fewer shops behaving this way.