Skip to main content
All CollectionsIntegrations
Integrate your ERP or WMS with yayloh
Integrate your ERP or WMS with yayloh

A Step-by-Step Guide to Connecting yayloh with Your ERP or WMS Using the Shopify Return API for Streamlined Return Management

Vineeta Pendse avatar
Written by Vineeta Pendse
Updated over a month ago

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

  1. [yayloh → ERP or WMS] New Return Notification

    yayloh notifies ERP or WMS of a new return coming our way.

  2. [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

  1. We have a common identifier for everything that we already understand (the Shopify identifiers)

  2. 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:

  • 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:

  • 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 and label_url mapped from the reverse delivery deliverable fields.

  • carrier_service either left empty or filled with custom_carrier.

  • source is SHOPIFY_INTEGRATION or EXTERNAL_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

  1. When an ERP or WMS warehouse identifies and inspects a return, ERP or WMS calls the reverseFulfillmentOrderDispose mutation. That adds Dispositions to the ReverseFulfillmentOrderLineItems. The disposition type is MISSING for missing items, NOT_RESTOCKED for damaged items, and RESTOCKED for OK items.

  2. 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:

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

Did this answer your question?