line

Shopify & Recharge Refund Workflow

A flexible workflow that calculates and automates refunds through shopify and recharge for a meal delivery company. Refunds can be issued by a customer support rep through the shopify dashboard or through csv file.

Overview

This workflow is used by a meal delivery service that uses shopify for all of their order processing and recharge for monthly subscriptions.

Customers can purchase a one time meal which is processed through shopify or they can buy a monthly subscription for a quantity of meals per month which is processed and managed through recharge (different payment gateway) for subscriptions.

Recharge is a recurring billing app native to Shopify

Sometimes customers ask for refunds for individual meals due to a variety of reasons and customer service reps must check manually to see where the order was processed and manually create a refund - and sometimes they must also manually calculate the refund amount (somtimes there is a discount code and depending on the subscription level a refund for a single meal has varying value)

In one month, due to unexpected high demand, they were unable to provide meals to all of their customers and needed to issue refunds for a large quantity of orders that were not delivered. They created this workflow to be able to automate the refund process - checking to see where the charge originated, calculating the amount and issuing the refund to save time and insure that there are no manual errors.

Now, using Zenaton, refunds can be issued by customer service representatives directly in shopify by entering the refund amount and reason or manually 'in mass' by using a CSV file containing order IDs and the meal quantity to refund for each one.

Workflow Steps

Launch: A workflow instance can be launched automatically through a private shopify app that allows a customer sales rep to issue a refund to a customer through the shopify dashboard or manually using a CSV file with a list of order IDs and meal quantities to refund.

  • Get the order name or ID from shopify - we need to fetch the order to get the sku of what was purchased.
  • Check if the order is originally through shopify (1 time purchase) or recharge (part of a monthly subscription)
  • If the meal quantity is received in workflow input then calculate the refund amount, if refund amount is received then we don't need to run the calculation.
  • If the order was done through recharge, and the meal quantity was sent in the workflow input, calculate the refund amount (runs a task that takes the total monthly subscription, divides by the number of meals included and multiplies times the number of meals to be refunded)
  • Creates the refund through recharge or shopify (depending on where it was originally charged) using the amount from the calculation or the amount sent in the workflow input.
  • Notify the client

Workflow input

Depending on how the workflow is launched (from inside shopify or manually via a CSV file) the input will either contain the meal quantity OR amount to refund (but not both)

{
  "orderId": 1073459984,
  "orderName": null,
  "mealQty": 3,
  "amount": 35.90,
}

Workflow Code

module.exports.hande = function*({ orderId, orderName, mealQty, amount } = object) {
  this.orderId = orderId;
  this.orderName = orderName;
  this.amountToRefund = amount;
  this.mealQtyToRefund = mealQty;

  // Get the shopify order by name or id
  if (this.orderName) {
    this.shopifyOrder = (yield this.run.task("GetShopifyOrder", orderName)).orders[0];

    if (parseInt(this.orderName) !== parseInt(this.shopifyOrder.name)) {
      return false;
    }
  } else if (orderId) {
    this.shopifyOrder = (yield this.run.task( "GetShopifyOrderById", orderId )).order;
  }

  this.orderId = this.shopifyOrder.id;
  this.sku = this.shopifyOrder.line_items[0].sku;
  this.totalPrice = this.shopifyOrder.total_price;
  this.totalMealsQty = yield this.run.task("GetMealsQty", this.sku);

  // Calculate the amount of refund
  this.totalRefund = this.amountToRefund
    ? this.amountToRefund
    : yield this.run.task(
        "CalculateRefund",
        this.mealQtyToRefund,
        this.totalMealsQty,
        this.totalPrice
      );

  //Check to see if its a recharge order
  if (this.shopifyOrder.source_name === "294517") {
    this.charges = (yield this.run.task("GetRechargeCharge", this.orderId)).charges;

    //check to see if its a real run and that it doesnt have previous refunds
    if (!test && this.shopifyOrder.refunds.length === 0) {
      this.refund = yield this.run.task("CreateRefund", this.charges[0].id, this.totalRefund);
      this.processed = true;
    }
    //if order is from shopify
  } else if (
    this.shopifyOrder.source_name === "web" &&
    !test &&
    this.shopifyOrder.refunds.length === 0
  ) {
    //check to see if its a real run and that it doesnt have previous refunds

    this.shopifyTransactions = (yield this.run.task( "GetShopifyTransaction", this.shopifyOrder.id )).transactions;
    this.refund = yield this.run.task( "CreateShopifyRefund", this.orderId, this.shopifyTransactions[0].id, this.totalRefund, this.shopifyOrder.gateway );
    this.processed = true;
  }

  // Save to the database
  const record = {
    etail_order_id: this.orderId,
    shopify_order: this.shopifyOrder.name,
    charge_id: this.charges ? this.charges[0].id : "",
    refund_amount: this.totalRefund,
    total_price: this.totalPrice,
    total_meals: this.totalMealsQty,
    total_meals_shorted: this.mealQtyToRefund,
    sku: this.sku,
    email: this.shopifyOrder.email
  };

  this.dbInsert = yield this.run.task("LogToDb", record);
}

Watch David show what this workflow does in a live demo at the Zenaton user conference in January 2020.