Context
To retrieve inspection statuses from your WMS/ERP and import return tracking details from yayloh into your WMS/ERP, this integration between yayloh, Shopify, and your ERP/WMS is essential. It allows for prompt and informed return or exchange processing.
High-level Requirements
[yayloh → ERP or WMS] New Return Notification
yayloh notifies ERP or WMS of a new return coming our way.
[ERP or WMS → yayloh] Quality Control Result
ERP or WMS notifies yayloh that the inspection is complete and forwards the results.
Other P2 Requirements
Return Shipping Labels
Use yayloh's integrations to obtain a shipping label from either your independent contracts or your 3PL's accounts.
Solution
There are three possible approaches:
A custom integration with ERP/ ERP using their APIs.
Extending yayloh's APIs to allow yayloh to integrate with ERP or WMS.
[Preferred] Using Shopify as a communication channel.
Integrating Through Shopify
The main advantages of this approach are that
We have a common identifier for everything that we already understand (the Shopify identifiers)
It’s not partner-dependent for both yayloh and ERP or WMS,
New Return Notification
Step 1:
When a new return is expected, yayloh creates a new return using the returnCreate
mutation. This will automatically create a reverse fulfillment order (see below).
A reverse fulfillment order is a group of one or more items in a return that will be processed by a merchant or a third-party fulfillment service. Creating a return creates a reverse fulfillment order. There can be more than one reverse fulfillment order for a return at a given location. If you create a return on an archived order, then the order is automatically unarchived.
Additionally, yayloh generates the return shipping label creates a ReverseDelivery
using the reverseDeliveryCreateWithShipping
mutation, adding the tracking number, carrier, and tracking label URL.
Return creation required fields:
Return creation required fields:
returnLineItems
mapped with the original Shopify fulfillment line items.quantity
the returned quantity.returnReason
the reason declared by the customer,OTHER
if it cannot be mapped.returnReasonNote
the note if any declared by the customer.
Step 3:
ERP or WMS listens to REVERSE_DELIVERIES_ATTACH_DELIVERABLE
webhook events (payload reference) and creates the return in their system.
ERP or WMS Return Creation Field Mapping:
ERP or WMS Return Creation Field Mapping:
warehouse
the shop integration’s auto-fulfillment warehouse.order
retrieved by retrieving the Shopify order ID and doing internal matching.⚠️ If no order is found, we must create a dummy one for the return.
line_items
the reverse fulfillment order line items.product
mapped using the Shopify <> ERP or WMS product link.quantity
the line item quantity.return_reasons
mapped from the return line items' return reasons field.tracking_number
andlabel_url
mapped from the reverse delivery deliverable fields.carrier_service
either left empty or filled withcustom_carrier
.source
isSHOPIFY_INTEGRATION
orEXTERNAL_PORTAL
to define.
💡 Limitations: The only limit of this solution is customer-managed return shipping information. In that case, we cannot identify the parcel at the warehouse, and yayloh does not have a return tracking details to communicate.
Step 4:
This generates an expected return to your ERP/WMS.
💡 Return shipments are identified at the warehouse using the carrier tracking number or the Shopify order name on the yayloh return labels.
Quality Control Result
When an ERP or WMS warehouse identifies and inspects a return, ERP or WMS calls the
reverseFulfillmentOrderDispose
mutation. That addsDispositions to the ReverseFulfillmentOrderLineItems
. The disposition type isMISSING
for missing items,NOT_RESTOCKED
for damaged items, andRESTOCKED
for OK items.
yayloh subscribes to the
reverse_fulfillment_orders/dispose
webhook events and uses them as a trigger for the rest of the flow, including merchant refunds.
Reference Code
JS code used to validate the use case:
JS code used to validate the use case:
const ORDER_ID = "gid://shopify/Order/5449499476223";
const FULFILLMENT_LINE_ITEM_ID = "gid://shopify/FulfillmentLineItem/11870355390719";
const ORDER_TRANSACTION_ID = "gid://shopify/OrderTransaction/6416853991679";
const REFUND_AMOUNT = "885.95";
console.log("returnCreate start");
const returnCreateResponse = await admin.graphql(
`#graphql
mutation returnCreate($returnInput: ReturnInput!) {
returnCreate(returnInput: $returnInput) {
return {
id
returnLineItems(first: 10) {
nodes {
id
}
}
reverseFulfillmentOrders(first: 10) {
nodes {
id
lineItems(first: 10) {
nodes {
id
totalQuantity
}
}
}
}
}
userErrors {
field
message
}
}
}`,
{
variables: {
returnInput: {
notifyCustomer: false,
orderId: ORDER_ID,
requestedAt: "2023-09-07T15:50:00Z",
returnLineItems: [
{
fulfillmentLineItemId: FULFILLMENT_LINE_ITEM_ID,
quantity: 1,
returnReason: "SIZE_TOO_LARGE",
returnReasonNote: "Custom note",
},
],
},
},
}
);
const returnCreateResponseJson = await returnCreateResponse.json();
console.log("returnCreateResponse done", JSON.stringify(returnCreateResponseJson));
console.log("reverseDeliveryCreateWithShipping start");
const reverseDeliveryCreateWithShipping = await admin.graphql(
`#graphql
mutation reverseDeliveryCreateWithShipping($reverseDeliveryLineItems: [ReverseDeliveryLineItemInput!]!, $reverseFulfillmentOrderId: ID!, $notifyCustomer: Boolean!, $trackingInput: ReverseDeliveryTrackingInput!, $labelInput: ReverseDeliveryLabelInput!) {
reverseDeliveryCreateWithShipping(reverseDeliveryLineItems: $reverseDeliveryLineItems, reverseFulfillmentOrderId: $reverseFulfillmentOrderId, notifyCustomer: $notifyCustomer, trackingInput: $trackingInput, labelInput: $labelInput) {
reverseDelivery {
id
}
userErrors {
field
message
}
}
}`,
{
variables: {
labelInput: {
fileUrl: "<https://label.example.com/label.pdf>",
},
notifyCustomer: false,
reverseDeliveryLineItems: [
{
quantity: 1,
reverseFulfillmentOrderLineItemId: returnCreateResponseJson.data.returnCreate.return.reverseFulfillmentOrders.nodes[0].lineItems.nodes[0].id,
},
],
reverseFulfillmentOrderId: returnCreateResponseJson.data.returnCreate.return.reverseFulfillmentOrders.nodes[0].id,
trackingInput: {
number: "tracking_number_1",
url: "<https://tracking.example.com/1>",
},
},
}
);
const reverseDeliveryCreateWithShippingJson = await reverseDeliveryCreateWithShipping.json();
console.log("reverseDeliveryCreateWithShipping done", JSON.stringify(reverseDeliveryCreateWithShippingJson));
// ...continued code
Resources
Manage reverse fulfillment orders: https://shopify.dev/docs/apps/fulfillment/returns-apps/reverse-fulfillment-orders.
Manage reverse deliveries: https://shopify.dev/docs/apps/fulfillment/returns-apps/reverse-fulfillment-orders