Marketplaces / Laredoute MIRAKL Technical Scope / Laredoute Order management / Laredoute Refund Order

Laredoute Refund Order

Summary of Changes: (The purpose of this table is to keep traceability and Product team to highlight the things that were changed into the scope, based on comments or discussions)

Date Version Name Applied changes
07/03/2023 v1.0 Hristiyan First Publish
06/06/2023 v1.1 Bogomil Additional validation for cancellations
10/07/2023 v1.2 Bogomil Refund flows logic
20/07/2023 v1.3 Bogomil Additional validation for cancellations
24/11/2023 v1.4 Hristiyan Updated the logic for list reasons. LaRedoute only supports French refund reasons so we only need to store those.
06/12/2023 v1.5 Hristiyan Clarification on the refund logic (no longer valid)
06/12/2023 v1.6 Bogomil Cancellation Internal Validation (no longer valid)
16/01/2024 v1.7 Hristiyan New logic for refunds overall, due to LaRedoute changes.

The purpose of this page is to describe in details the refund flow on MIRAKL.

The refund/cancellation request on MIRAKL is per item line id and we can include each separate item as additional “refund“ or “cancellation“ node. To process the refund/cancellation , we have to specify a reason which need to be added in the Refund UI for LaRedoute Marketplace. In order to obtain the reasons, we should use GET RE01 - List reasons call and then, to make a refund/cancellation , we should use PUT OR28 - Request a refund or OR30 - Request cancellations on order lines

GET RE01 - List reasons

API Call:/api/reasons

API Docs: https://laredoutestg-stg.mirakl.net/help/api-doc/seller/mmp.html#RE01

Please note we need to store only the reasons which are with type = “REFUND“ and type = “CANCELATION“. <v1.4> We also only need to store refunds which are with label written in French as NO other refund reasons will be accepted. Screenshot below :

</v1.4>

Example response:

{
    "reasons": [
        {
            "code": "14",
            "is_shop_right": false,
            "label": "No response from the shop",
            "type": "REFUND"
        },
        {
            "code": "15",
            "is_shop_right": false,
            "label": "Out of stock",
            "type": "REFUND"
        },
        {
            "code": "16",
            "is_shop_right": false,
            "label": "Cancelled by the client prior to shipping",
            "type": "REFUND"
        },
        {
            "code": "17",
            "is_shop_right": false,
            "label": "Item returned",
            "type": "REFUND"
        },
        {
            "code": "18",
            "is_shop_right": false,
            "label": "Item not received",
            "type": "REFUND"
        },
        {
            "code": "19",
            "is_shop_right": false,
            "label": "Agreement found with the vendor",
            "type": "REFUND"
        },
        {
            "code": "REFUND_OP01",
            "is_shop_right": false,
            "label": "TEST",
            "type": "REFUND"
        },
        {
            "code": "REFUND_test",
            "is_shop_right": false,
            "label": "toto",
            "type": "REFUND"
        },
        {
            "code": "REFUND_132",
            "is_shop_right": false,
            "label": "CommercialEntity",
            "type": "REFUND"
        },
        {
            "code": "REFUND_NOTRECEIVED",
            "is_shop_right": true,
            "label": "Colis non reçu, égaré",
            "type": "REFUND"
        },
        {
            "code": "REFUND_REFUSED",
            "is_shop_right": true,
            "label": "Colis non livré ou refusé",
            "type": "REFUND"
        },
        {
            "code": "REFUND_DISPUTE",
            "is_shop_right": true,
            "label": "Contestation de livraison",
            "type": "REFUND"
        },
        {
            "code": "REFUND_MISSING",
            "is_shop_right": true,
            "label": "Article manquant dans le colis ou colis vide",
            "type": "REFUND"
        },
        {
            "code": "REFUND_DAMAGED",
            "is_shop_right": true,
            "label": "Colis abîmé, en avarie",
            "type": "REFUND"
        },
        {
            "code": "REFUND_FEEDBACK",
            "is_shop_right": true,
            "label": "Retour client",
            "type": "REFUND"
        },
        {
            "code": "REFUND_DISAGREE",
            "is_shop_right": true,
            "label": "Désaccord sur montant remboursé",
            "type": "REFUND"
        },
        {
            "code": "REFUND_DEFECTIVERECEIPT",
            "is_shop_right": true,
            "label": "Défectueux à réception",
            "type": "REFUND"
        },
        {
            "code": "REFUND_WRONGITEM",
            "is_shop_right": true,
            "label": "Article reçu non conforme à la commande",
            "type": "REFUND"
        },
        {
            "code": "REFUND_DEFECTIVEUSE",
            "is_shop_right": true,
            "label": "Défectueux à l'usage",
            "type": "REFUND"
        },
        {
            "code": "REFUND_ACTIONMANAGER",
            "is_shop_right": false,
            "label": "Action Account Manager MKP",
            "type": "REFUND"
        },
        {
            "code": "34",
            "is_shop_right": true,
            "label": "Cancelled by the client prior to shipping",
            "type": "CANCELATION"
        },
        {
            "code": "CANCELATION_OUTOFSTOCK",
            "is_shop_right": true,
            "label": "Rupture de stock",
            "type": "CANCELATION"
        },
        {
            "code": "SYSTEM_LATE_SHIPMENT_CANCELATION",
            "is_shop_right": false,
            "label": "Canceled due to late shipment",
            "type": "CANCELATION"
        },
        {
            "code": "SYSTEM_CANCELATION_DEBIT_FAILED",
            "is_shop_right": false,
            "label": "Debit failed",
            "type": "CANCELATION"
        },
        {
            "code": "SYSTEM_CANCELATION_DEBIT_TIMEOUT",
            "is_shop_right": false,
            "label": "Debit timed out",
            "type": "CANCELATION"
        }
    ],
    "total_count": 90
}

Bear in mind we need to display the label into the UI but to send the code. Because in our UI we cannot distinguish the refund from the cancellation we need to add in the label what type is the reason is for the refund or for cancellation. E.G. [REFUND] - {label} or [CANCELATION] - {label}

PUT OR28 - Request a refund

API Call: /api/orders/refund

API Docs: https://laredoutestg-stg.mirakl.net/help/api-doc/seller/mmp.html#OR28

*Note:** Each payment row (type=refund) will need to be send as a separate request. If even one of the refunds failed it will fail the whole feed. Thus we have agreed to push them per payment row. Which means we will have one request with all of the refund row nodes.

Example call: (one item in order)

{
  "order_tax_mode": "TAX_EXCLUDED",
  "refunds": [
    {
      "amount": 1.5,
      "currency_iso_code": "USD",
      "excluded_from_shipment": false,
      "order_line_id": "Order_00010-A-1",
      "quantity": 0,
      "reason_code": "15",
      "shipping_amount": 2,
      "shipping_taxes": [
        {
          "amount": "1",
          "code": "tax1"
        },
        {
          "amount": "1",
          "code": "tax2"
        }
      ],
      "taxes": [
        {
          "amount": "1",
          "code": "tax1"
        },
        {
          "amount": "1",
          "code": "tax2"
        }
      ]
    }
  ]
}

Example call: (two items in order)

{
  "order_tax_mode": "TAX_EXCLUDED",
  "refunds": [
    {
      "amount": 1.5,
      "currency_iso_code": "USD",
      "excluded_from_shipment": false,
      "order_line_id": "Order_00010-A-1",
      "quantity": 0,
      "reason_code": "15",
      "shipping_amount": 2,
      "shipping_taxes": [
        {
          "amount": "1",
          "code": "tax1"
        },
        {
          "amount": "1",
          "code": "tax2"
        }
      ],
      "taxes": [
        {
          "amount": "1",
          "code": "tax1"
        },
        {
          "amount": "1",
          "code": "tax2"
        }
      ]
    }
    {
      "amount": 1.5,
      "currency_iso_code": "USD",
      "excluded_from_shipment": false,
      "order_line_id": "Order_00010-A-2",
      "quantity": 0,
      "reason_code": "15",
      "shipping_amount": 2,
      "shipping_taxes": [
        {
          "amount": "1",
          "code": "tax1"
        },
        {
          "amount": "1",
          "code": "tax2"
        }
      ],
      "taxes": [
        {
          "amount": "1",
          "code": "tax1"
        },
        {
          "amount": "1",
          "code": "tax2"
        }
      ]
    }
  ]
}

MIRAKL allows us to refund:

  • Partially items;
  • Shipping
  • Orders

So the only validation we will need is to make sure we are not refunding more than we can for particular order item line!

Mapping:

Integration Field Integration Notes Integration required Hemi Mapping Hemi Notes
order_tax_mode Please note: If the taxes are not specified, the prices with mode TAX_EXCLUDED and with mode TAX_INCLUDED will return the same amounts.

Possible values: • TAX_EXCLUDED: the price fields (price, unit price, shipping price and order total prices) do not include taxes (taxes come on top of the price amount). • TAX_INCLUDED: the price fields include the tax amounts. • If not specified, the default order tax mode of the platform is used | No | N/A | We want to use the default order tax mode of the platform, so we should not send any tax mode. | | refunds | | | | Yes | | | | | amount | | | Yes | Refund Row > Amount | Refund rows with type “item“ | | | currency_iso_code | | | No | Order > Order currency | | | | order_line_id | | | Yes | Order Item >Item Order Line ID | | | | quantity | | | No | Order Item > Quantity | Based on the Refund Row > Amount we need to calculate the quantity! • If we are doing partial line refund we specify the quantity as 0. • If we are doing full line refund (the full quantity) we specify the actual quantity we have for the item e.g. 1, 2, 3 etc. Note: For each full like we are having quantity = 1 For each partial like we will have quantity = 0 | | | reason_code | | Refund's reason code | Yes | Order Payment > Reason | We need to use the reason code. | | | shipping_amount | | The shipping charges part amount to be refunded | Yes | Refund Row > Amount | Refund rows with type “shipping “ | | | shipping_taxes | | The taxes to be refunded on the shipping price | No | N/A | | | | | amount | Tax amount | No | N/A | | | | | code | Tax code | No | N/A | |

Example Response: (one item in order)

{
  "order_tax_mode": "TAX_EXCLUDED",
  "refunds": [
    {
      "amount": 1.5,
      "currency_iso_code": "USD",
      "eco_contributions": [],
      "excluded_from_shipment": false,
      "order_line_id": "Order_00010-A-1",
      "purchase_information": {
        "purchase_commission_on_price": 0.50,
        "purchase_commission_on_shipping": 1.00,
        "purchase_price": 1.00,
        "purchase_shipping_price": 1.00
      },
      "quantity": 0,
      "reason_code": "15",
      "refund_id": "1130",
      "shipping_amount": 2,
      "shipping_taxes": [
        {
          "amount": 1,
          "code": "tax1",
          "purchase_tax": {
            "purchase_amount": 0.50,
            "purchase_rate": 7.5000
          },
          "rate": 17.5000
        },
        {
          "amount": 1,
          "code": "tax2",
          "purchase_tax": {
            "purchase_amount": 0.50,
            "purchase_rate": 7.5000
          }
        }
      ],
      "taxes": [
        {
          "amount": 1,
          "code": "tax1",
          "purchase_tax": {
            "purchase_amount": 0.50,
            "purchase_rate": 7.5000
          },
          "rate": 17.5000
        },
        {
          "amount": 1,
          "code": "tax2",
          "purchase_tax": {
            "purchase_amount": 0.50,
            "purchase_rate": 7.5000
          }
        }
      ]
    }
  ]
}

Response Mapping:

Integration Field Integration Notes Integration required Hemi Mapping Hemi Notes
order_tax_mode
refunds
amount N/A
currency_iso_code N/A
excluded_from_shipment N/A
order_line_id N/A This will be used for the mapping
purchase_information N/A
quantity N/A
reason_code N/A
refund_id Order Payment Details > Transaction ID Because each order item is pushed in a separate transaction we will receive more than one refund_id thus we will have to concatenate them with “-“
shipping_amount
shipping_amount_breakdown
shipping_taxes
taxes

<v1.5>

PUT OR29 - Cancel an order not yet debited from the customer

API Call: /api/orders/{order_id}/cancel

API Docs: https://help.mirakl.net/help/api-doc/seller/mmp.html#OR29

We have to specify in the URL our Orders > Marketplace Order Id

Cancel an order not yet debited from the customer. For LaRedoute all accepted but NOT shipped orders are treated as “Not Yet Debited” which means that if a cancellation has to happen, we need to use the OR29 call instead of OR30. Please note only full order cancellations for non debited orders are allowed and we would like to add internal validation and return internal error if not full cancellation is done.

Example call:

PUT https://mirakl.marketplace.laredoute.com/api/orders/12341231-A/cancel

Example Response: 204: No Content

Please note since there is no content in the response we wont have a transaction id for the refund so we need to make a OR11 - List Orders call for the specific order that we have cancelled and store the transaction ID. The request and response can be found Laredoute Get Orders . Please note that from the response we only need to store :

LaRedoute Field LaRedoute Notes Hemi Field
cancelations List of the cancellations on the order
id Refund's id Order Payment > Transaction ID

This needs to be done after each 204: No Content response for the specific order that we are cancelling.

</v1.5>

PUT OR30 - Request cancellations on order lines

API Call: /api/orders/cancel

API Docs: https://laredoutestg-stg.mirakl.net/help/api-doc/seller/mmp.html#OR30

*Note: Each payment row (type=refund) will need to be send as a separate request. <v1.1>Please note Partial order cancellation won't be available on the production environment thus we want to include additional validation when we try to make any partial cancellations.</v1.1><v1.7>**

We will not be able to make any cancellations since LaRedoute will be going with the "pay on acceptance" workflow which means that the order will be debited as soon as accepted and only refunds will be possible after that. So we don’t need to use this call at all.</v1.7>

Example call: (one item in order)

{
  "cancelations": [
    {
      "amount": 2,
      "currency_iso_code": "USD",
      "order_line_id": "Order_00013-A-1",
      "quantity": 0,
      "reason_code": 34,
      "shipping_amount": 0,
      "shipping_taxes": [
        {
          "amount": "1",
          "code": "tax1"
        },
        {
          "amount": "1",
          "code": "tax2"
        }
      ],
      "taxes": [
        {
          "amount": 1,
          "code": "tax1"
        },
        {
          "amount": 1,
          "code": "tax2"
        }
      ]
    }
  ],
  "order_tax_mode": "TAX_EXCLUDED"
}

Example call: (two items in order)

{
  "cancelations": [
    {
      "amount": 2,
      "currency_iso_code": "USD",
      "order_line_id": "Order_00013-A-1",
      "quantity": 0,
      "reason_code": 34,
      "shipping_amount": 0,
      "shipping_taxes": [
        {
          "amount": "1",
          "code": "tax1"
        },
        {
          "amount": "1",
          "code": "tax2"
        }
      ],
      "taxes": [
        {
          "amount": 1,
          "code": "tax1"
        },
        {
          "amount": 1,
          "code": "tax2"
        }
      ]
    }
    {
      "amount": 2,
      "currency_iso_code": "USD",
      "order_line_id": "Order_00013-A-2",
      "quantity": 0,
      "reason_code": 34,
      "shipping_amount": 0,
      "shipping_taxes": [
        {
          "amount": "1",
          "code": "tax1"
        },
        {
          "amount": "1",
          "code": "tax2"
        }
      ],
      "taxes": [
        {
          "amount": 1,
          "code": "tax1"
        },
        {
          "amount": 1,
          "code": "tax2"
        }
      ]
    }
  ],
  "order_tax_mode": "TAX_EXCLUDED"
}

MIRAKL allows us to cancel:

  • Full Orders

(v1.1)So we need to include additional validation when we try to make any partial cancellations.

Mapping:

Integration Field Integration Notes Integration required Hemi Mapping Hemi Notes
cancellations
amount Yes Refund Row > Amount Refund rows with type “item“
currency_iso_code No Order > Order currency
order_line_id Yes Product In Order >Item Order Line ID
quantity Yes Product In Order > Quantity Based on the Refund Row > Amount we need to calculate the quantity!

• If we are doing partial line refund we specify the quantity as 0. • If we are doing full line refund (the full quantity) we specify the actual quantity we have for the item e.g. 1, 2, 3 etc. | | | reason_code | | Yes | Order Payment > Reason | We need to use the reason code. | | | shipping_amount | | Yes | Refund Row > Amount | Refund rows with type “shipping “ | | | shipping_taxes | | No | N/A | | | | taxes | | No | N/A | | | | order_tax_mode | Please note: If the taxes are not specified, the prices with mode TAX_EXCLUDED and with mode TAX_INCLUDED will return the same amounts. Possible values: • TAX_EXCLUDED: the price fields (price, unit price, shipping price and order total prices) do not include taxes (taxes come on top of the price amount). • TAX_INCLUDED: the price fields include the tax amounts. • If not specified, the default order tax mode of the platform is used. | No | | We want to use the default order tax mode of the platform, so we should not send any tax mode. |

Example Response: (one item in order)

{
  "cancelations": [
    {
      "amount": 2,
      "cancelation_id": "1116",
      "currency_iso_code": "USD",
      "eco_contributions": [],
      "order_line_id": "Order_00013-A-1",
      "purchase_information": {
        "purchase_commission_on_price": 1.00,
        "purchase_commission_on_shipping": 1.50,
        "purchase_price": 1.00,
        "purchase_shipping_price": 1.50
      },
      "quantity": 0,
      "reason_code": "34",
      "shipping_amount": 3,
      "shipping_taxes": [
        {
          "amount": 1,
          "code": "tax1",
          "purchase_tax": {
            "purchase_amount": 0.50,
            "purchase_rate": 7.5000
          },
          "rate": 17.5000
        },
        {
          "amount": 1,
          "code": "tax2",
          "purchase_tax": {
            "purchase_amount": 0.50,
            "purchase_rate": 7.5000
          }
        }
      ],
      "taxes": [
        {
          "amount": 1,
          "code": "tax1",
          "purchase_tax": {
            "purchase_amount": 0.50,
            "purchase_rate": 7.5000
          },
          "rate": 17.5000
        },
        {
          "amount": 1,
          "code": "tax2",
          "purchase_tax": {
            "purchase_amount": 0.50,
            "purchase_rate": 7.5000
          }
        }
      ]
    }
  ],
  "order_tax_mode": "TAX_EXCLUDED"
}

Response Mapping:

Integration Field Integration Notes Integration required Hemi Mapping Hemi Notes
cancelations
amount N/A
cancellation_id Order Payment Details > Transaction ID Because each order item is pushed in a separate transaction we will receive more than one refund_id thus we will have to concatenate them with “-“
currency_iso_code N/A
eco_contributions N/A
order_line_id N/A This will be used for the mapping
purchase_information N/A
quantity N/A
reason_code N/A
shipping_amount N/A
shipping_taxes N/A
taxes N/A
order_tax_mode N/A

Specifics:

We will have couple of cases here because we will have one Order Payment row with type = refund but in MIRAKL there might be couple of transaction:

  • If all transaction are successfully and we receive the refund_id or the cancellation_id ,we need to mark the Order Payment > Status = "Completed" and Refund Row > Status = "Completed"
  • If not all transactions are completed but we have one or more completed we mark the Order Payment > Status = “Partially Completed“, Refund Row > Status = “Completed“ for the successful line and Refund Row > Status = “Error“.

Note: The error message stating which Order Item Line failed is stored in Order Error table.

  • If all transactions failed we mark Order Payment > Status = “Error“, Refund Row > Status = “Error“ and store the error in Order Error table.

<v1.7> How to decide when to make a cancellation or refund is stated on the order.

Since LaRedoute will be going with "pay on acceptance" workflow this will mean that as soon as we accept the order it will be marked as debited and it will become only refundable (can_cancel will be false and can_refund will be true). This will practically mean that we will be using only one call to make refunds, regardless if it is pre-shipment or post-shipment. This will be the OR28 call.

We don’t need any validations as practically we will have only the OR28 call to use for refunds. If we for some reason receive an error for whatever reason, we want to store it in the Order Errors table with type Refund Send

Please check the table below for better understanding.

</v1.7>

<v1.5><v1.6> For the sake of visibility : We have developed all possible scenarios even though they no longer will be needed due to LaRedoute going with the pay on acceptance flow, but if at any point they decide to change their policy we will be ready as we have covered the following cases :

  • If we have can_cancel = true and customer_debited_date = null and can_refund = false We need to use the OR29 to cancel the order
  • If we have can_cancel = true and customer_debited_date = date (in a 2023-12-04T12:26:07.043Z format) and can_refund = false we need to use the OR30 to cancel
  • If we have for some reason can_refund = true and can_cancel = true we need to use the OR30 to cancel (this is not an expected behavior anyways, if for some reason it happens for whatever reason we want to have it covered)

</v1.5></v1.6>

Is this article helpful?
0 0 0