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.

Visual of Workflow

This flowchart shows a visual representation of the workflow tasks.
line

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

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.

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

Presentation at the Zenaton User Conference

David Turton shows us how he used Zenaton to write a workflow to automate a refund process for a shopify store with 1500 refunds and saved hours of work and errors from manually calculating and issuing refunds.

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);
}

Workflow Executions

A view of a workflow instance for a specific refund. Note you can see each step and could view the data by clicking on each row.
line