Refunds Workflow

In this how to we take you through how to perform cancellations and returns using the “advanced” refund workflow

What you’ll learn

In this how to we take you through how to perform cancellations and returns using the “advanced” refund workflow, specifically:

  • Key concepts and terms
  • The difference between the original and advanced flows
  • Processes and scenarios for common cancellation and return use cases from the perspective of a seller
  • The GraphQL API calls you’ll need to make

📼 Videos covering this topic can also be found here

Key Terms

Before launching into the processes that can be followed, it’s worth clarifying the language and terms used when we talk about refunds and returns.

TermDescription
Cancellation (Process)Cancellation occurs before purchased items are dispatched. So for example, a Seller may wish to cancel the order if they have run out of stock.
Return (Process)This is the term we use when an order has been dispatched, and the customer wishes to return it for a refund. An example of this is that the item did not meet the customers expectations and they do not want a replacement. By using the advanced workflow, sellers and operators can choose where they want the item to be returned or not.
Exchange (Process)The Exchange process occurs when the order has been dispatched and the Customer wishes to swap (exchange) it for another item. For example the dispatched item was faulty, and the customer would like a replacement. The exchange process is not directly supported by the API.
Refund Request (Entity)A Refund Request is created when either the Cancellation or Return process is initiated. It is the primary vehicle used to manage the lifetime of this request. A successful Refund Request will ultimately result in an Invoice Amendment.
Invoice Amendment (Entity)An Invoice Amendment will be made to a customer invoice if there needs to be any kind of adjustment following a successful Refund Request, (either the Cancellation or Return process). Note that an Invoice Amendment can be created directly on an Invoice without needing to create a Refund Request, (“short circuiting the process”).

Original Vs. Advanced Flows

Both the original and advanced workflows follow the same high-level process, as outlined below:

Simple Refunds Flow

  • Create: The creation of the Return or Cancellation Request
  • Return: Process the request (e.g. mark items as having been returned)
  • Refund: Finalize the refund

Some other points to note:

  • You can see which roles (Seller or Operator) can perform each step using the GraphQL API
    • In this article we are focused primarily on the operations a seller can perform
  • We have omitted the Deny & Revert steps in the above example for brevity

Expanding this example for the original and advanced workflows, you will observe some differences:

Original Workflow

Original Refunds Flow

Some points to note on the original workflow:

  • When using the GraphQL API the same flow is used for both Cancellations and Returns
    • I.e. The Cancellation step (where items have not yet been dispatched) still requires that you mark items as “Returned”
  • Only the Operator can deny the refund request
  • In the case of a Return, there is no way to determine that the product does not need to be returned
    • E.g. If the return was for a perishable food item, it is unlikely you’d want that back

Advanced Workflow

Advanced Refunds Flow

Some points to note on the advanced workflow:

  • The “Return” step is now split into 2 “optional” steps: “Return” & “Accept”
  • For Cancellation flows, you can now omit the need to mark items as returned (Return and Accept steps), and move straight to approving (Refund)
  • For Returns you can choose whether you:
    • Require the item to be returned (Return)
    • Do not require the item to be returned (Accept)
  • Sellers can now “Deny” refund requests at a line item level (depending on where they are in request cycle)

We’ll go through a more detailed worked example later in this article, but to summarize the main benefits of the advanced workflow over the original:

  1. Refunds can be processed individually at the Line Item Level (as opposed processing an entire request that may contain many line items)
  2. The Seller and Operator can decide whether products need to be returned (or not)
  3. The Cancellation flow is is streamlined

Common Use Cases

To further illustrate how the Advanced workflow can be used, we’ll run through a number of common scenarios.

Using the advanced workflow, we are going to cover the following uses cases:

Scenario IdTypeRequire Items to be returnedSeller Approval requiredMark Items as Returned
1CancellationN/aNoN/a
2CancellationN/aYesN/a
3ReturnNoNoN/a
4ReturnNoYesN/a
5ReturnYesYesYes
6ReturnYesNoYes

All use cases will start with the same base condition(s):

  • An Order has successfully been created with 1 line item
  • For the Return scenarios, the line item will have been dispatched

Before running through the use-cases, let’s take a quick look at the mutations we are going to be using.

GraphQL Mutations

With the new workflow, we have added some new mutations, and updated some existing ones, we have summarized these below:

mutationDescriptionOperatorSeller
refundRequestCreateCreate the refund or cancellation request. This mutation has been updated from the original workflow to accept a line item status. This status drives the remaining workflow steps, and is a key concept in the advanced workflow. We will be covering this in more detail later.YesYes
refundRequestLineItemAcceptRequired when the Seller needs to provide “approval” of the return or cancellation request. This can be used alone, (when items are not required to be returned), or in combination with refundRequestLineItemReturn (when items are required to be returned).YesYes
refundRequestLineItemReturnUsed when line items are required to be returnedYesYes
refundRequestLineItemDenyUsed to deny the request at a line item levelYesYes - depends on status
refundRequestRefundUsed to approve the request - available only to the operator (sellers cannot approve refund requests)YesNo
refundRequestApproveThis endpoint is currently in beta. Used to approve the request at a line item level - available only to the operator (sellers cannot approve refund requests)YesNo
returnShipmentCreateUsed to create return shipments, so you can pair this with refundRequestLineItemReturn when items are required to be returned. The use of this mutation is optional and is not on the critical path of the “returns flow”YesYes

Creation Statuses

As mentioned above, the refundRequestCreate mutation has been updated to accept a status for each line item that is added to the request, an example of this mutation is shown below:

mutation refundRequestRequiringApproval{
  refundRequestCreate(
    input: {
      invoiceId: "SW52b2ljZS0xMDAxOA=="
      lineItems: [
        {
          lineItemId: "TGluZUl0ZW0tMTk="
          quantity: 1
          reason: "Milk has gone sour"
          status: PENDING_APPROVAL
        }
      ]
      notes: [{ note: "Please confirm if you want item returned" }]
    }
  ) {
    errors {
      field
      messages
    }
    refundRequest {
      id
      status
    }
  }
}

Here you can see this request adds 1 line item to the request with a status of PENDING_APPROVAL, this means that the Seller will need to determine how they want to process the request, this could be either:

  • Just accept the request (refundRequestLineItemAccept)
  • Require that the items are returned (refundRequestLineItemReturn + refundRequestLineItemAccept)

The takeaway point is that this status determines next available steps in the overall workflow, you would select a different status depending on how you wanted the request to be processed. The complete list of statuses you can supply here are:

StatusDescription
REFUND_ACCEPTEDIt has already been determined that no return items are required, the operator can just approve the request
PENDING_APPROVALSeller determines how they want to process the request
AWAITING_RETURNIt has been determined that return items are required, Seller needs to accept the items

Use Case Examples

Pulling this information together, you can observe the mutation calls you’d need to make to cover all our scenarios, noting that we have abbreviated the full mutation name for a simpler label as follows:


Mutation Key


Note: Be sure to pay attention to status used with each call to Create (refundRequestCreate).


Call Matrix


State Transitions

Before we move on to a worked example, the last concept that you should be aware of are the various states that each of the primary objects participating in the refund request can take. Those objects are:


ObjectDescription
OrderThe parent object for the entire Marketplacer order. An order contains 1 or more Invoices, one for each Seller.
Note: Sellers do not have direct access to this object given that it can contain line items for other sellers.
InvoiceThe object relating to the Sellers part of the Order, and a primary participant in the refund flow
LineItemRepresents products that were purchased with the creation of the Order and Invoice.
RefundRequestThis object is created with a successful call to refundRequestCreate. It is the primary vehicle by which the entire refund request is tracked. Note that 1 RefundRequest can contain many RefundRequestLineItems
RefundRequestLineItemThis object is created along with the RefundRequest, and represents the individual refund components which can be Invoice Line Items, (see above), or Custom Line items, representing “non-product” refund components, e.g. Postage costs etc. In this article we are dealing exclusively with RefundRequestLineItem objects that were created using an Invoice Line Item.

A simple example of the relationship between these objects is shown below:


Object Relations


All of the objects above have state, and in our worked example we’ll provide state transitions for all of them, however for the moment we’ll focus in on the states that the following objects can have:

  • RefundRequest
  • RefundRequestLineItem

RefundRequest States

The RefundRequest object can have the following states:


StateDescription
AwaitingThere is still some action required to be taken on the refund request
ProcessedAll refund request line items have been actioned by the Seller and it is now for the Operator to action
RefundedThe refund request has been finalized (note if some line items are refunded and some are denied, the status will still show as refunded)
DeniedThe refund request has been denied (note, only when all refund request line items are denied

RefundRequestLineItem States

The RefundRequestLineItem object can have the following states:


StateDescription
Pending ApprovalThe line item is pending an action by the Seller
Awaiting ReturnThe line item has been requested to be returned to the Seller
Refund AcceptedThe line item has been authorized by the seller and they have completed the actions required to progress this refund request to the operator
RefundedThe line item has been refunded
DeniedThe line item has been denied

RefundRequest & RefundRequestLineItem State Correlation

The matrix below shows the correlation between RefundRequest and RefundRequestLineItem states:


Refund State Matrix

Worked Example

The following worked example follows Scenario 5 which utilizes all of the new and updated mutations. You should be able to take this example and adapt it easily to the remaining 5 uses-cases.

As a reminder, Scenario 5 is as follows:

Scenario 5

Before we start

As this is a return scenario the following pre-conditions need to be met before we can begin with the refund flow:

  • An order will need a created order with 1 line item.
    • As a seller you cannot create orders, so you will need to reach out to your marketplace seller onboarding team for them to assist in the creation of some test orders.
  • The line item on the order / invoice has been dispatched
    • You can use the shipmentCreate GraphQL mutation to achieve this or just dispatch the line item from the Seller Portal

At this point our primary objects should have the following state:

ObjectStateNotes
OrderCOMPLETE
InvoicePAID, SENTYou should use Invoice statusFlags for the Invoice state
Line ItemALLOCATED
Refund RequestN/aA Refund Request has not been created yet therefore there is no state
Refund Request Line ItemN/aA Refund Request has not been created yet therefore there is no state

We’ll additionally track the webhook events for the following webhook types as we move through out example:

  • Refund Request
  • Refund Request Line Item
Webhook EventEventsNotes
Refund RequestN/aA Refund Request has not been created yet therefore there are no associated webhook events
Refund Request Line ItemN/aA Refund Request has not been created yet therefore there are no associated webhook events

Step 1: Call refundRequestCreate

Scenario 5 - Step 1

The first step is to call refundRequestCreate, for this you will require:

  1. The Invoice Id
  2. The LineItem Id of the Line Item you want to return
  3. The Line Item status we want to use in this case this will be: PENDING_APPROVAL

This call can be made by:

  • The Operator
  • The Seller

An example mutation call is shown below:

mutation {
  refundRequestCreate(
    input: {
      invoiceId: "SW52b2ljZS0xMDAxOA=="
      lineItems: [
        {
          lineItemId: "TGluZUl0ZW0tMTk="
          quantity: 1
          reason: "Cracked Screen"
          status: PENDING_APPROVAL
        }
      ]
      notes: [{ note: "High value item, requires return" }]
    }
  ) {
    errors {
      field
      messages
    }
    refundRequest {
      id
      status
      lineItems {
        id
        status
      }
    }
  }
}

A successful call will result in the following:

  • RefundRequest Id
  • RefundRequestLineItem Id (we’ll need this for the next step)

Our primary objects should have the following state:

ObjectStateNotes
OrderCOMPLETE
InvoicePAID, SENT, AWAITING_RETURNWe have an additional status flag of AWAITING_RETURN
Line ItemALLOCATED
Refund RequestAWAITINGTransitioned from “N/a”
Refund Request Line ItemPENDING_APPROVALTransitioned from “N/a”

The following webhook events will fire:

Webhook EventEventsNotes
Refund RequestCreate
Refund Request Line ItemCreate

Step 2 Call refundRequestLineItemReturn

Scenario 5 - Step 2

The Seller in this case takes the decision that they want the line item returned, so they will make a call to refundRequestLineItemReturn, to do so they will need the RefundRequestLineItem Id of the Line Item you want to return

This call can be made by:

  • The Operator
  • The Seller

An example mutation call is shown below:

mutation {
  refundRequestLineItemReturn(
    input: {
      refundRequestLineItemId: "UmVmdW5kUmVxdWVzdExpbmVJdGVtLTE2"
      notes: [{ note: "Confirm the return of this item is required" }]
    }
  ) {
    refundRequestLineItem {
      status
    }
    errors {
      field
      messages
    }
  }
}

Following a successful call, our primary objects should have the following state:

ObjectStateNotes
OrderCOMPLETE
InvoicePAID, SENT, AWAITING_RETURN
Line ItemALLOCATED
Refund RequestAWAITING
Refund Request Line ItemAWAITING_RETURNThis transitioned from PENDING_APPROVAL

The following webhook events will fire:

Webhook EventEventsNotes
Refund RequestN/a
Refund Request Line ItemUpdate

Step 2a [Optional] Call returnShipmentCreate

Scenario 5 - Step 2a

The seller can optionally create a return shipment that can including tracking details, attachments etc. To use returnShipmentCreate you will need to pass the refundRequestId. This call can be made by:

  • The Operator
  • The Seller

An example mutation call is shown below:

mutation {
  returnShipmentCreate(
    input: {
      refundRequestId: "UmVmdW5kUmVxdWVzdC0yMA=="
      carrierId: "U2hpcG1lbnRDYXJyaWVyLTQ="
      trackingNumber: "ABC-1236"
      shippedItems: [
        { 
          lineItemId: "UmVmdW5kUmVxdWVzdExpbmVJdGVtLTIw", 
          quantity: 1 
        }
      ]
    }
  ) {
   shipment {
      id
    }
    errors {
      field
       messages
    }
  }
}

Following a successful call, the statuses of our primary objects do not change:

ObjectStateNotes
OrderCOMPLETE
InvoicePAID, SENT, AWAITING_RETURN
Line ItemALLOCATED
Refund RequestAWAITING
Refund Request Line ItemAWAITING_RETURN

No events for the webhooks we are monitoring will fire either:

Webhook EventEventsNotes
Refund RequestN/a
Refund Request Line ItemN/a

Step 3 Call refundRequestLineItemAccept

Scenario 5 - Step 3

The Seller will now wait until the item has been returned, once it has been and it fulfils the return criteria of the Seller, they would then call refundRequestLineItemAccept, to do so they will need:

  1. The RefundRequestLineItem Id of the Line Item that has been returned

This call can be made by:

  • The Operator
  • The Seller

An example mutation call is shown below:

mutation {
  refundRequestLineItemAccept(
    input: {
      refundRequestLineItemId: "UmVmdW5kUmVxdWVzdExpbmVJdGVtLTE3"
      notes: [{ note: "Item has been returned in a satisfactory condition" }]
    }
  ) {
    refundRequestLineItem {
      status
    }
    errors {
      field
      messages
    }
  }
}

Following a successful call, our primary objects should have the following state:

ObjectStateNotes
OrderCOMPLETE
InvoicePAID, SENTWe no longer have the AWAITING_RETURN status flag
Line ItemALLOCATED
Refund RequestPROCESSEDThis transitioned from AWAITING
Refund Request Line ItemREFUND_ACCEPTEDThis transitioned from AWAITING_RETURN

The following webhook events will fire:

Webhook EventEventsNotes
Refund Requeststatus:processed
Update
Refund Request Line ItemUpdate

Step 4 Call refundRequestRefund

Scenario 5 - Step 4

With all of the Sellers steps completed, the Operator now has to finalize the refund by calling refundRequestRefund, to do so they need to supply:

  1. The RefundRequestId Id of the refund request

This call can be made by:

  • The Operator

An example mutation call is shown below:

mutation {
  refundRequestRefund(
    input: {
      refundRequestId: "UmVmdW5kUmVxdWVzdC0xNw=="
      notes: [{ note: "Refund Approved" }]
    }
  ) {
    errors {
      field
      messages
    }
    refundRequest {
      id
      status
    }
  }
}

Following a successful call, our primary objects should have the following state:

ObjectStateNotes
OrderCOMPLETE
InvoicePAID, SENT, REFUNDEDWe have an additional status flag of REFUNDED
Line ItemREFUNDEDTransitioned from ALLOCATED
Refund RequestREFUNDEDThis transitioned from PROCESSED
Refund Request Line ItemREFUNDEDThis transitioned from REFUND_ACCEPTED

The following webhook events will fire:

Webhook EventEventsNotes
Refund Requeststatus:refunded
Update
Refund Request Line ItemUpdate

Step 4a Call refundRequestApprove

Scenario 5 - Step 4a

It is assumed that you have followed all the example steps up to and including Step 3 above, with that being the case, the Operator can finalize the refund by calling refundRequestApprove in one of the following 2 ways.


Option 1 - Simplex

This is essentially an identical approach to that used with refundRequestRefund as shown below:

mutation {
  refundRequestApprove(input: 
    {
      refundRequestId: "UmVmdW5kUmVxdWVzdC0xNw=="
    }
  ) {
      refundRequest{
        id
        status
    }
    errors {
      field 
      messages
    }
  }
}

Calling this mutation will result in the same outcome you would have with calling refundRequestRefund.


Option 2 - Granular

The entire value-proposition of refundRequestApprove is to allow the caller to specify granular values for:

  • lineItemAmount - The amount to be refunded to the shopper
  • commissionAmount - The commission component of the lineItemAmount
  • remittanceAmount - The remittance component of the lineItemAmount

You can also optionally specify the tax component of each if required. An example of this approach to calling refundRequestApprove is shown below:


mutation {
  refundRequestApprove(input: 
    {
      refundRequestId: "UmVmdW5kUmVxdWVzdC0xMDk="
      lineItemPriceBreakdowns: [
        {
          id: "UmVmdW5kUmVxdWVzdExpbmVJdGVtLTE0OQ=="
          lineItemAmount: 1000
          commissionAmount: 200
          remittanceAmount: 800
        }
      ]
    })
  {
    refundRequest{
      id
      status
    }
    errors{
      field 
      messages
    }
  }
}