← Blog

Updates,

Refund Reasons, Now for Returns and Individual Lines

Łukasz Ostrowski

Structured reasons now extend to returns and replacements, and you can attach a reason to each individual line of a refund or return.

Last year we introduced structured refund reasons: instead of free-text notes, you could attach a reference to a Model (formerly Page) representing a predefined reason, making refunds easy to aggregate, translate, and report on.

That first release covered manual refunds and granted refunds at the order level. With Saleor 3.23 we're extending the same idea in two directions that merchants kept asking for: returns and replacements, and per-line granularity.

Per-line reasons for granted refunds

Previously a granted refund carried a single reasonReference for the whole order. But a refund often spans several items returned for different reasons — one damaged, one the wrong size. Now each line can carry its own reason.

orderGrantRefundCreate and orderGrantRefundUpdate accept a reasonReference on every line, stored on the individual OrderGrantedRefundLine:

GRAPHQL
1mutation OrderGrantRefundCreate($id: ID!, $input: OrderGrantRefundCreateInput!) {
2 orderGrantRefundCreate(id: $id, input: $input) {
3 grantedRefund {
4 id
5 reasonReference {
6 id
7 title
8 }
9 lines {
10 id
11 quantity
12 reason
13 reasonReference {
14 id
15 title
16 }
17 }
18 }
19 }
20}
JSON
1{
2 "id": "T3JkZXI6...",
3 "input": {
4 "amount": 25,
5 "reason": "Returned by customer",
6 "reasonReference": "UGFnZTox",
7 "lines": [
8 { "id": "T3JkZXJMaW5lOjE=", "quantity": 1, "reasonReference": "UGFnZToy" },
9 { "id": "T3JkZXJMaW5lOjI=", "quantity": 1, "reasonReference": "UGFnZToz" }
10 ]
11 }
12}

A few rules worth knowing:

  • The order-level reasonReference is required for staff when a refund reason type is configured, and always optional for apps.
  • Per-line reasonReference is always optional for everyone. There's no inheritance from the order level — lines without one are stored as null.
  • Any reference you provide must point to a Page of the configured PageType, or you'll get a ValidationError.

Reasons for returns and replacements

Refunds aren't the only place customers tell you why. Returns and replacements are now first-class citizens too, through the orderFulfillmentReturnProducts mutation.

Reasons work at two levels here: a global reason / reasonReference describing the whole return (stored on the resulting Fulfillment), and a per fulfillment line reason / reasonReference (stored on each FulfillmentLine).

GRAPHQL
1mutation OrderFulfillmentReturnProducts($orderId: ID!, $input: OrderReturnProductsInput!) {
2 orderFulfillmentReturnProducts(order: $orderId, input: $input) {
3 returnFulfillment {
4 id
5 reason
6 reasonReference { id title }
7 lines {
8 id
9 reason
10 reasonReference { id title }
11 }
12 }
13 replaceFulfillment {
14 id
15 lines { id reason reasonReference { id title } }
16 }
17 }
18}
JSON
1{
2 "orderId": "T3JkZXI6...",
3 "input": {
4 "refund": false,
5 "reason": "Customer return request #1234",
6 "reasonReference": "UGFnZTox",
7 "fulfillmentLines": [
8 { "fulfillmentLineId": "RnVsZmlsbG1lbnRMaW5lOjI1MQ==", "quantity": 1, "replace": true, "reasonReference": "UGFnZToz" },
9 { "fulfillmentLineId": "RnVsZmlsbG1lbnRMaW5lOjI1Mg==", "quantity": 1, "replace": false, "reasonReference": "UGFnZTo0" }
10 ]
11 }
12}

Importantly, the return reason (why the items are coming back) is independent of the refund reason (why money was returned). The refund flag has no effect on reason validation — the same rules apply whether or not you're issuing a refund alongside the return.

A separate setting for returns

Because returns and refunds are conceptually different events, they're governed by separate settings. Returns use a dedicated returnReasonReferenceType, independent of the refundReasonReferenceType you already configured for refunds. Configuring one does not configure the other.

You set it the same way — create a Model Type for your return reasons, then point the setting at it:

GRAPHQL
1mutation SetReturnTypeRequired($pageTypeId: ID!) {
2 returnSettingsUpdate(input: { returnReasonReferenceType: $pageTypeId }) {
3 returnSettings {
4 reasonReferenceType { id name }
5 }
6 errors { field message code }
7 }
8}

Once configured, staff users must supply a global reasonReference when returning products; apps may omit it. To opt back out, run returnReasonReferenceClear.

Supported mutations at a glance

Mutation Order-level Per-line Since
transactionRequestAction refundReasonReference 3.22
orderGrantRefundCreate reasonReference lines[].reasonReference 3.22 / 3.23
orderGrantRefundUpdate reasonReference addLines[].reasonReference 3.22 / 3.23
orderFulfillmentReturnProducts reasonReference fulfillmentLines[].reasonReference 3.23

The refund mutations are gated by refundReasonReferenceType; the return mutation by returnReasonReferenceType.

Full details, validation rules, and complete examples are in the refunds documentation.

    Get more useful guides, tech insights, and free learning materials by subscribing to our list.
    All human-written!

    By registering you agree to our Privacy Policy.
    The form is protected by reCAPTCHA - Privacy Policy and Terms of Service.