Shopify Integration - Returns & Exchanges (via Return Portal)

This document describes how SPY integrates with Shopify Returns and Exchanges when a Return Portal is used for the customer-facing return flow.

Scope & Non-scope

  • In scope

    • Customer flows (normal and edge cases)
    • System/data flow between Return Portal, Shopify, and SPY
    • Which Shopify objects/fields SPY reads and writes
    • How SPY handles return shipping fee, shipping refund, and exchange release behavior
    • Portal-initiated fulfillment hold release for exchange orders (and restrictions)
    • Changes to returns and exchanges (current cancel-and-recreate approach + planned inline changes)
  • Not in scope

    • Importing the structured returnReason enum from Shopify (SPY uses returnReasonNote instead — see below)
    • Gift-card refund-to-new-gift-card (future work)
    • Vendor-specific portal behavior/UI beyond what impacts the Shopify/SPY integration

Key statements (current behavior)

  • Return reasons

    • SPY does not use Shopify's structured returnReason enum (e.g. COLOR, DEFECTIVE, SIZE_TOO_SMALL, etc.).
    • Instead, SPY reads the returnReasonNote free-text field from each Shopify Return Line Item and maps it to a Claim Cause (SPY's return reason category) using the Claim Cause External Name as the matching key.
    • If no Claim Cause matches the returnReasonNote value, SPY falls back to a default Claim Cause configured per Shopify Shop.
    • The Return Portal is responsible for populating returnReasonNote with a value that matches a configured Claim Cause External Name in SPY.
  • Return closure

    • SPY closes the Return in Shopify after SPY processes the return.
    • Note: If the Return Portal needs to reflect this, it must react to the Shopify-side change (implementation is portal-specific).
  • Return shipping

    • SPY always includes the return shipping fee (Return Shipping Fees) in the refund calculations.
    • Shipping refund (original outbound shipping) is only refunded if:
      • The Return Portal indicates "refund shipping" for that specific return on the Shopify Order (via metafield), and
      • SPY receives all items expected for that Return (i.e., not partial).
  • Tracking & label

    • SPY reads return label + tracking information from Shopify's Reverse Delivery data and makes this information available for return processing.
  • Exchanges

    • Exchange items are expected to be released by the Return Portal immediately when the return is created (so they exist as real order line items on the exchange order).
    • SPY synchronizes the exchange order data, but if Shopify has fulfillment holds, the exchange order is not released for fulfillment until the hold is released.
    • SPY only releases Shopify fulfillment holds for the exchange when there is no outstanding amount on the Shopify order after the return is processed.
    • If there is an outstanding amount after processing, SPY sends an invoice through Shopify. The holds are released once the invoice is paid and the outstanding amount reaches zero.
  • Portal-initiated fulfillment hold release (exchange orders)

    • The Return Portal may release fulfillment holds on exchange orders directly in Shopify — typically when it detects that the customer has delivered the return package to a parcel shop.
    • When the Return Portal releases the fulfillment holds in Shopify, SPY automatically detects the change and releases the corresponding items internally — no additional action is needed from the Return Portal.
    • Restriction: This approach does not work if the Shopify order was paid partially or fully with a gift card. In those cases, SPY must handle the hold release itself (via the standard flow described above).
  • Changes to returns and exchanges

    • SPY supports changes to returns and exchanges — but currently only via a cancel-and-recreate workflow.
    • To change a return (e.g., adjust returned items, swap exchange products, change quantities), the Return Portal must:
      1. Cancel the existing Return in Shopify.
      2. Create a new Return with the updated information.
    • SPY will pick up the cancellation and process the new Return as normal once it appears.
    • Important: Changes are only supported before the return has been processed by SPY and before the fulfillment holds on the exchange order have been released. Once either of these has occurred, the return/exchange can no longer be changed.
    • Planned: A future update will add support for inline changes, where the Return Portal can update an existing Return in place (e.g., add/remove line items, adjust quantities, modify exchange items) and SPY will detect and process those changes — without requiring cancellation and recreation. The same precondition applies: changes will only be possible before processing and before fulfillment hold release.
  • Partial returns with exchange (insufficient authorization)

    • If a return is partial, SPY closes the Return in Shopify with the partial information.
    • If there is an outstanding amount on the Shopify order after SPY processes the return, SPY does not release the exchange for fulfillment.
    • Instead, SPY sends an invoice through Shopify for the outstanding amount. When the invoice is paid and the outstanding amount reaches zero, SPY releases the hold and the exchange can proceed.
    • Note: Some Return Portals may support authorizing extra amount up front; regardless, SPY will invoice if there is any outstanding amount at processing time.

Flowcharts - Customer perspective

1) Return only - fully returned (normal scenario)

2) Return only - partial return

3) Return with exchange - fully returned

4) Return with exchange - partial return (may require invoice)

5) Change to return or exchange (current: cancel-and-recreate)

6) Change to return or exchange (planned: inline update)

7) "Normal scenario overview" (expected full return, happy path)

8) Portal-initiated fulfillment hold release (exchange order)

Note: This flow does not apply when the order was paid partially or fully with a gift card. In that case, the standard SPY-managed hold release flow must be used instead.

Flowchart - System/data exchange (who does what)

Shopify objects & fields used by SPY

Objects SPY reads (via GraphQL)

  • Order
    • Used to locate Returns, exchange line items, and to read the Return Portal metafield controlling shipping refund.
  • Return
    • id, name, status, totalQuantity, createdAt
    • returnShippingFees { amountSet { presentmentMoney { amount, currencyCode } } }
    • refunds { id } (linking to existing refunds)
  • Return Line Item
    • id
    • quantity, processableQuantity, processedQuantity, unprocessedQuantity
    • refundableQuantity, refundedQuantity
    • customerNote
    • returnReason (read but not used by SPY — see notes)
    • returnReasonNotethis is the field SPY uses for Claim Cause mapping via External Name
    • fulfillmentLineItem { lineItem { id, name, isGiftCard, variant { id }, product { id }, customAttributes { key, value } } }
  • Exchange Line Items (on Return)
    • Exchange line items are read to associate the exchange order with the corresponding return.
    • If exchange items are not "released" (i.e., no real order line item ID yet), SPY skips importing the exchange until they are released.
  • Reverse Fulfillment Order
    • Provides disposition/location data and supports return shipping label/tracking.
  • Reverse Delivery (ReverseDeliveryShippingDeliverable)
    • deliverable.label.publicFileUrl (return label PDF)
    • deliverable.tracking.{carrierName, number, url} (T&T)

Objects SPY writes (mutations/actions)

  • Return processing
    • SPY submits return processing with per-line-item dispositions (e.g., restocked/not restocked/missing) and may include a shipping refund amount (when applicable).
  • Return closure
    • SPY always performs a Return Close in Shopify after processing.
  • Fulfillment holds
    • SPY releases fulfillment holds on the exchange fulfillment orders only when there is no outstanding amount on the Shopify order (after return processing, and after any invoice is paid).
    • Alternatively, the Return Portal may release fulfillment holds directly in Shopify (e.g., when a return package is delivered to a parcel shop). SPY detects externally released holds and updates its internal state accordingly. This does not work for orders paid partially or fully with a gift card.

Order metafield used for shipping refund decision

SPY expects the Return Portal to set a single metafield on the Shopify Order that controls the shipping refund decision.

  • Key: the metafield key must be shipping_refund.
  • Namespace: the namespace is defined by the Return Portal (it is configured per Return Portal app in SPY), so SPY looks up the metafield under that portal-specific namespace using the fixed shipping_refund key.

Action required: The Return Portal must contact SPY at integrations@spysystem.dk and provide the namespace name it will use for this metafield. SPY configures this namespace per Return Portal app, and if SPY's configured namespace does not match the one the portal writes to, SPY will not find the metafield and will silently fall back to the default ("no shipping refund").

The metafield value contains a JSON array of objects shaped like:

  • returnId: Shopify Return global id string
  • hasShippingRefund: boolean

Example (illustrative):

[
  {"returnId":"gid://shopify/Return/1234567890","hasShippingRefund":true}
]

SPY behavior:

  • If the metafield is missing or cannot be parsed, SPY uses the default value (typically "no shipping refund").
  • If the metafield contains multiple entries, SPY selects the entry matching the specific Return being processed.

Notes / Common pitfalls

  • SPY ignores returnReason — only returnReasonNote matters

    • SPY does not read or use the structured returnReason enum field at all.
    • SPY mapping uses returnReasonNote → Claim Cause External Name.
    • If returnReasonNote is empty or does not match any configured Claim Cause External Name, SPY falls back to the Shopify Shop's default Claim Cause.
  • Exchange visibility for fulfillment

    • While Shopify fulfillment holds exist, the exchange order is not released for fulfillment. This is expected.
  • Changes to returns require cancel-and-recreate (for now)

    • SPY does not currently detect inline edits to an existing Return (e.g., adding/removing line items or changing quantities on a Return that already exists).
    • To make changes, the Return Portal must cancel the existing Return and create a new one. SPY will then pick up the new Return as normal.
    • Changes are not possible if the return has already been processed by SPY or if the fulfillment holds on the exchange order have already been released.
    • Planned: A future update will add inline change support, allowing the Return Portal to modify an existing Return directly. SPY will detect those modifications and update its data accordingly — removing the need for the cancel-and-recreate workflow. The same precondition applies.
  • Portal-initiated fulfillment hold release

    • The Return Portal can release fulfillment holds on exchange orders directly in Shopify. This is the expected approach when the Return Portal detects that the customer has delivered the return package to a parcel shop.
    • SPY automatically detects the hold release in Shopify and releases the exchange items internally — no additional integration steps are needed.
    • Important restriction: This approach does not work if the order was paid partially or fully with a gift card. For gift card orders, the Return Portal must not release the fulfillment holds itself — SPY will handle the hold release through its standard flow (after return processing and any outstanding amount is settled).
  • Gift cards

    • Refund-to-new-gift-card is not implemented yet (planned investigation).
    • Portal-initiated fulfillment hold release is not supported for orders paid partially or fully with a gift card (see above).