Automatically cancel high-risk orders, with Mechanic.

Mechanic is a development and ecommerce automation platform for Shopify. :)

Automatically cancel high-risk orders

This task cancels orders as soon as Shopify (or another risk-analysis app) determines it to be high risk. Optionally, this task can also auto-tag the order, email the customer, restock the inventory, and/or refund payment.

Runs Occurs whenever an order is updated. Configuration includes cancellation reason to set, ignore unpaid orders, refund payment for cancelled orders, restock inventory for cancelled orders, email customer when cancelling, staff note for timeline, and add this order tag when cancelling.

15-day free trial – unlimited tasks

Documentation

This task cancels orders as soon as Shopify (or another risk-analysis app) determines it to be high risk. Optionally, this task can also auto-tag the order, email the customer, restock the inventory, and/or refund payment.

Valid cancellation reasons to set:

  • customer: The customer wanted to cancel the order.
  • declined: Payment was declined.
  • fraud: The order was fraudulent.
  • inventory: There was insufficient inventory.
  • staff: Staff made an error.
  • other: The order was canceled for an unlisted reason.

NOTE: This task will not cancel orders that have been partially or fully fulfilled

Developer details

Mechanic is designed to benefit everybody: merchants, customers, developers, agencies, Shopifolks, everybody.

That’s why we make it easy to configure automation without code, why we make it easy to tweak the underlying code once tasks are installed, and why we publish it all here for everyone to learn from.

(By the way, have you seen our documentation? Have you joined the Slack community?)

Open source
View on GitHub to contribute to this task
Subscriptions
shopify/orders/updated
Tasks use subscriptions to sign up for specific kinds of events. Learn more
Options
cancellation reason to set, ignore unpaid orders (boolean), refund payment for cancelled orders (boolean), restock inventory for cancelled orders (boolean), email customer when cancelling (boolean), staff note for timeline, add this order tag when cancelling
Code
{% assign cancellation_reason = options.cancellation_reason_to_set | default: "other" %}
{% assign ignore_unpaid_orders = options.ignore_unpaid_orders__boolean %}
{% assign refund_payment = options.refund_payment_for_cancelled_orders__boolean %}
{% assign restock_inventory = options.restock_inventory_for_cancelled_orders__boolean %}
{% assign notify_customer = options.email_customer_when_cancelling__boolean %}
{% assign staff_note = options.staff_note_for_timeline %}
{% assign order_tag_to_apply = options.add_this_order_tag_when_cancelling %}

{% comment %}
  -- check that a valid cancellation reason has been configured; it will default to 'other' if left blank
{% endcomment %}

{% assign valid_cancellation_reasons = "customer,declined,fraud,inventory,other,staff" | split: "," %}

{% unless valid_cancellation_reasons contains cancellation_reason %}
  {% error %}
    {{ "Cancellation reason " | append: cancellation_reason | append: " - must be one of 'customer', 'declined', 'fraud', 'inventory', 'other', or 'staff'." | json }}
  {% enderror %}
{% endunless %}

{% comment %}
  -- get the order statuses and risk assessments
{% endcomment %}

{% capture query %}
  query {
    order(id: {{ order.admin_graphql_api_id | json }}) {
      id
      name
      cancelledAt
      displayFinancialStatus
      displayFulfillmentStatus
      risk {
        assessments {
          riskLevel
        }
      }
    }
  }
{% endcapture %}

{% assign result = query | shopify %}

{% if event.preview %}
  {% capture result_json %}
    {
      "data": {
        "order": {
          "id": "gid://shopify/Order/1234567890",
          "displayFinancialStatus": "PAID",
          "displayFulfillmentStatus": "UNFULFILLED",
          "risk": {
            "assessments": [
              {
                "riskLevel": "NONE"
              },
              {
                "riskLevel": "HIGH"
              }
            ]
          }
        }
      }
    }
  {% endcapture %}

  {% assign result = result_json | parse_json %}
{% endif %}

{% assign order = result.data.order %}

{% log order: order %}

{% if order.cancelledAt %}
  {% log "This order has already been cancelled." %}
  {% break %}
{% endif %}

{% if ignore_unpaid_orders and order.displayFinancialStatus != "PAID" %}
  {% log "This order has not been paid and the ignore unpaid orders option is enabled." %}
  {% break %}
{% endif %}

{% comment %}
  -- get the risk level from each risk assessment
{% endcomment %}

{% assign order_risk_levels = order.risk.assessments | map: "riskLevel" %}

{% unless order_risk_levels contains "HIGH" %}
  {% log "The current risk assessments for this order do not contain a HIGH risk level." %}
  {% break %}
{% endunless %}

{% comment %}
  -- check to make sure order is unfulfilled to avoid cancellation error
{% endcomment %}

{% if order.displayFinancialStatus == "FULFILLED" or order.displayFulfillmentStatus == "PARTIALLY_FULFILLED" %}
  {% log "This order has already been fulfilled or partially fulfilled and cannot be cancelled." %}
  {% break %}
{% endif %}

{% comment %}
  -- cancel the order with configured options
{% endcomment %}

{% action "shopify" %}
  mutation {
    orderCancel(
      orderId: {{ order.id | json }}
      notifyCustomer: {{ notify_customer | json }}
      reason: {{ cancellation_reason | upcase }}
      refund: {{ refund_payment | json }}
      restock: {{ restock_inventory | json }}
      staffNote: {{ staff_note | json }}
    ) {
      job {
        id
      }
      orderCancelUserErrors {
        code
        field
        message
      }
    }
  }
{% endaction %}

{% comment %}
  -- tag the order if there is a tag configured to apply on cancellation
{% endcomment %}

{% if order_tag_to_apply != blank %}
  {% action "shopify" %}
    mutation {
      tagsAdd(
        id: {{ order.id | json }}
        tags: {{ order_tag_to_apply | json }}
      ) {
        userErrors {
          field
          message
        }
      }
    }
  {% endaction %}
{% endif %}
Task code is written in Mechanic Liquid, an extension of open-source Liquid enhanced for automation. Learn more