How to use the advanced refunds flow
16 minute read
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
- The GraphQL API calls you’ll need to make
Important
If you are an existing Marketplacer customer, Advanced Refunds may not be turned on, you can enable this setting through the Marketplacer Operator Portal.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.
Term | Description |
---|---|
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. Exchanging is not currently supported. |
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:
- 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
- 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
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
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:
- Refunds can be processed individually at the Line Item Level (as opposed processing an entire request that may contain many line items)
- The Seller and Operator can decide whether products need to be returned (or not)
- 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.
Note
This section does not cover every conceivable Cancellation or Return scenario, however it provides a good grounding on all the key concepts.Using the advanced workflow, we are going to cover the following uses cases:
Scenario Id | Type | Require Items to be returned | Seller Approval required | Mark Items as Returned |
---|---|---|---|---|
1 | Cancellation | N/a | No | N/a |
2 | Cancellation | N/a | Yes | N/a |
3 | Return | No | No | N/a |
4 | Return | No | Yes | N/a |
5 | Return | Yes | Yes | Yes |
6 | Return | Yes | No | Yes |
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:
Note
The detailed inline GraphQL documentation, as well as links to our Insomnia and Postman collections (that contain example mutations), can be found here.mutation | Description | Operator | Seller |
---|---|---|---|
refundRequestCreate | Create 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. | Yes | Yes |
refundRequestLineItemAccept | Required 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). | Yes | Yes |
refundRequestLineItemReturn | Used when line items are required to be returned | Yes | Yes |
refundRequestLineItemDeny | Used to deny the request at a line item level | Yes | Yes - depends on status |
refundRequestRefund | Used to approve the request, this has not changed from the original workflow. | Yes | No |
refundRequestApprove | This endpoint is currently in beta. You can speak to your Technical Account Manger to discuss making this available to use. It is a direct replacement to refundRequestRefund , allowing the Operator a greater degree of control when approving refunds and cancellations. We provide an example of how this mutation can be used in our worked example. You can also read more about it in the release notes. | Yes | No |
returnShipmentCreate | Used 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” | Yes | Yes |
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 {
refundRequestCreate(
input: {
invoiceId: "SW52b2ljZS0xMDAxOA=="
lineItems: [
{
lineItemId: "TGluZUl0ZW0tMTk="
quantity: 1
reason: "Milk has gone sour"
status: PENDING_APPROVAL
}
]
notes: [{ note: "Do not require item return" }]
}
) {
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:
Status | Description |
---|---|
REFUND_ACCEPTED | It has already been determined that no return items are required, you can just approve the request |
PENDING_APPROVAL | Seller can determine how they want to process the request |
AWAITING_RETURN | It 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:
Note: Be sure to pay attention to status
used with each call to Create (refundRequestCreate
).
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:
Object | Description | |
---|---|---|
Order | The parent object for the entire Marketplacer order. An order contains 1 or more Invoices, one for each Seller | |
Invoice | The object relating to the Sellers part of the Order , and a primary participant in the refund flow | |
LineItem | Represents products that were purchased with the creation of the Order and Invoice. | |
RefundRequest | This 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 | |
RefundRequestLineItem | This 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:
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:
State | Description |
---|---|
Awaiting | There is still some action required to be taken on the refund request |
Processed | All refund request line items have been actioned by the Seller and it is now for the Operator to action |
Refunded | The refund request has been finalized (note if some line items are refunded and some are denied, the status will still show as refunded) |
Denied | The 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:
State | Description |
---|---|
Pending Approval | The line item is pending an action by the Seller |
Awaiting Return | The line item has been requested to be returned to the Seller |
Refund Accepted | The line item has been authorized by the seller and they have completed the actions required to progress this refund request to the operator |
Refunded | The line item has been refunded |
Denied | The line item has been denied |
RefundRequest & RefundRequestLineItem State Correlation
The matrix below shows the correlation between RefundRequest
and RefundRequestLineItem
states:
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.
Note
The Postman and Insomnia collections found here contain examples of all the mutations we are going to use.As a reminder, Scenario 5 is as follows:
Note
In this example we are going to follow the “happy path” and not perform any denials. It’s also worth remembering that while we will create a return shipment usingreturnShipmentCreate
this step is not technically part of the critical path of the return flow.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:
- You will need a created order with 1 line item.
- For more information on creating orders in GraphQL refer to this article
- 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
- You can use the
At this point our primary objects should have the following state:
Object | State | Notes |
---|---|---|
Order | COMPLETE | |
Invoice | PAID, SENT | You should use Invoice statusFlags for the Invoice state |
Line Item | ALLOCATED | |
Refund Request | N/a | A Refund Request has not been created yet therefore there is no state |
Refund Request Line Item | N/a | A 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
Webhooks
For more information on what Webhooks are and how they can be employed in your solution, please refer to our docs here.Webhook Event | Events | Notes |
---|---|---|
Refund Request | N/a | A Refund Request has not been created yet therefore there are no associated webhook events |
Refund Request Line Item | N/a | A Refund Request has not been created yet therefore there are no associated webhook events |
Step 1: Call refundRequestCreate
The first step is to call refundRequestCreate
, for this you will require:
- The
Invoice
Id - The
LineItem
Id of the Line Item you want to return - 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
IdRefundRequestLineItem
Id (we’ll need this for the next step)
Our primary objects should have the following state:
Object | State | Notes |
---|---|---|
Order | COMPLETE | |
Invoice | PAID, SENT, AWAITING_RETURN | We have an additional status flag of AWAITING_RETURN |
Line Item | ALLOCATED | |
Refund Request | AWAITING | Transitioned from “N/a” |
Refund Request Line Item | PENDING_APPROVAL | Transitioned from “N/a” |
The following webhook events will fire:
Webhook Event | Events | Notes |
---|---|---|
Refund Request | Create | |
Refund Request Line Item | Create |
Step 2 Call refundRequestLineItemReturn
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
Note
Please take care to distinguish between theLineItem
Id and the RefundRequestLineItem
Id. In this example we are working exclusively with RefundRequestLineItem
Ids.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:
Object | State | Notes |
---|---|---|
Order | COMPLETE | |
Invoice | PAID, SENT, AWAITING_RETURN | |
Line Item | ALLOCATED | |
Refund Request | AWAITING | |
Refund Request Line Item | AWAITING_RETURN | This transitioned from PENDING_APPROVAL |
The following webhook events will fire:
Webhook Event | Events | Notes |
---|---|---|
Refund Request | N/a | |
Refund Request Line Item | Update |
Step 2a [Optional] Call returnShipmentCreate
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
Return Shipment Deep Dive
We cover this particular step in more detail in this article, which covers the following additional topics:
- Querying shipment carriers
- Adding url-based attachments
- Adding base 64 encoded attachments
- Updating the status of a return shipment
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:
Object | State | Notes |
---|---|---|
Order | COMPLETE | |
Invoice | PAID, SENT, AWAITING_RETURN | |
Line Item | ALLOCATED | |
Refund Request | AWAITING | |
Refund Request Line Item | AWAITING_RETURN |
No events for the webhooks we are monitoring will fire either:
Webhook Event | Events | Notes |
---|---|---|
Refund Request | N/a | |
Refund Request Line Item | N/a |
Step 3 Call refundRequestLineItemAccept
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:
- 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:
Object | State | Notes |
---|---|---|
Order | COMPLETE | |
Invoice | PAID, SENT | We no longer have the AWAITING_RETURN status flag |
Line Item | ALLOCATED | |
Refund Request | PROCESSED | This transitioned from AWAITING |
Refund Request Line Item | REFUND_ACCEPTED | This transitioned from AWAITING_RETURN |
The following webhook events will fire:
Webhook Event | Events | Notes |
---|---|---|
Refund Request | status:processed | |
Update | ||
Refund Request Line Item | Update |
Step 4 Call refundRequestRefund
Note
This is the final step of our worked example, however we provide an alternate “final step” below (step 4a) using therefundRequestApprove
mutation, which as mentioned previously is a more granular replacement for refundRequestRefund
.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:
- 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:
Object | State | Notes |
---|---|---|
Order | COMPLETE | |
Invoice | PAID, SENT, REFUNDED | We have an additional status flag of REFUNDED |
Line Item | REFUNDED | Transitioned from ALLOCATED |
Refund Request | REFUNDED | This transitioned from PROCESSED |
Refund Request Line Item | REFUNDED | This transitioned from REFUND_ACCEPTED |
The following webhook events will fire:
Webhook Event | Events | Notes |
---|---|---|
Refund Request | status:refunded | |
Update | ||
Refund Request Line Item | Update |
Step 4a Call refundRequestApprove
Note
This is an alternate approach torefundRequestRefund
(Step 4 above), and as mentioned previously refundRequestApprove
is available on request only via your Marketplacer Technical Account ManagerIt 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 shoppercommissionAmount
- The commission component of thelineItemAmount
remittanceAmount
- The remittance component of thelineItemAmount
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
}
}
}
Note
Opting in torefundRequestApprove
does change the outputs of the remittance PDFs and add extra fields to GraphQL Invoice Amendments. Otherwise it is backwards compatible with refundRequestRefund
.