Automatically cancel orders with certain risk indicators, with Mechanic.

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

Automatically cancel orders with certain risk indicators

This task auto-cancels incoming orders when it finds all of a certain set of risk assessment facts. Risk assessment facts must match exactly, so double-check your configuration! 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 required risk assessment facts, 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 auto-cancels incoming orders when it finds all of a certain set of risk assessment facts. Risk assessment facts must match exactly, so double-check your configuration! 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
required risk assessment facts (array, required), 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 required_risk_assessment_facts = options.required_risk_assessment_facts__array_required %}
{% 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 {
          facts {
            description
          }
        }
      }
    }
  }
{% endcapture %}

{% assign result = query | shopify %}

{% if event.preview %}
  {% capture result_json %}
    {
      "data": {
        "order": {
          "id": "gid://shopify/Order/1234567890",
          "displayFinancialStatus": "PAID",
          "displayFulfillmentStatus": "UNFULFILLED",
          "risk": {
            "assessments": [
              {
                "facts": [
                  {% for required_risk_assessment_fact in required_risk_assessment_facts %}
                    {
                      "description": {{ required_risk_assessment_fact | json }}
                    }{% unless forloop.last %},{% endunless %}
                  {% endfor %}
                ]
              }
            ]
          }
        }
      }
    }
  {% 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 description from each fact in each risk assessment, and look for matches of all configured risk assessment facts
{% endcomment %}

{% assign order_risk_assessment_facts
  = order.risk.assessments
  | map: "facts"
  | map: "description"
%}

{% assign matched_risk_assessment_facts = array %}

{% for order_risk_assessment_fact in order_risk_assessment_facts %}
  {% if required_risk_assessment_facts contains order_risk_assessment_fact %}
    {% assign matched_risk_assessment_facts
      = matched_risk_assessment_facts
      | push: order_risk_assessment_fact
      | uniq
    %}
  {% endif %}
{% endfor %}

{% if matched_risk_assessment_facts.size != required_risk_assessment_facts.size %}
  {% log
    message: "Did not match all the risk assessment facts that are required.",
    matched_risk_assessment_facts: matched_risk_assessment_facts,
    order_risk_assessment_facts: order_risk_assessment_facts,
    required_risk_assessment_facts: required_risk_assessment_facts
  %}
  {% break %}
{% endif %}

{% comment %}
  -- all of the configured risk assessment facts have been matched; 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 aplly 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