Auto-cancel orders with too many of a certain SKU, with Mechanic.

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

Auto-cancel orders with too many of a certain SKU

Useful for enforcing order limits on the backend, use this task to ensure that orders with too many of certain products will be automatically cancelled.

Runs Occurs whenever an order is created. Configuration includes skus and quantity cancellation thresholds, email customer when cancelling, refund payment for cancelled orders, restock inventory for cancelled orders, cancellation reason to set, and staff note for timeline.

15-day free trial – unlimited tasks

Documentation

Useful for enforcing order limits on the backend, use this task to ensure that orders with too many of certain products will be automatically cancelled.

Configure the product SKUs to monitor on the left and the quantities that will trigger a cancellation on the right. Optionally, choose whether to email the customer, refund the payment, or restock the inventory upon cancellation. The configured cancellation reason must be one of - 'customer', 'declined', 'fraud', 'inventory', 'other', or 'staff'.

Notes:

  • SKUs are case-sensitive!
  • Any refunds generated by this task will be for the maximum amount, for the order being cancelled.

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/create
Tasks use subscriptions to sign up for specific kinds of events. Learn more
Options
skus and quantity cancellation thresholds (keyval, number, required), email customer when cancelling (boolean), refund payment for cancelled orders (boolean), restock inventory for cancelled orders (boolean), cancellation reason to set, staff note for timeline
Code
{% assign skus_and_quantity_cancellation_thresholds = options.skus_and_quantity_cancellation_thresholds__keyval_number_required %}
{% assign notify_customer = options.email_customer_when_cancelling__boolean %}
{% assign refund_payment = options.refund_payment_for_cancelled_orders__boolean %}
{% assign restock_inventory = options.restock_inventory_for_cancelled_orders__boolean %}
{% assign cancellation_reason = options.cancellation_reason_to_set | default: "other" %}
{% assign staff_note = options.staff_note_for_timeline %}

{% assign skus_to_monitor = skus_and_quantity_cancellation_thresholds | keys %}

{% 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 order details and line items to tally
{% endcomment %}

{% capture query %}
  query {
    order(id: {{ order.admin_graphql_api_id | json }}) {
      id
      name
      cancelledAt
      displayFulfillmentStatus
      lineItems(first: 250) {
        nodes {
          sku
          quantity
        }
      }
    }
  }
{% endcapture %}

{% assign result = query | shopify %}

{% if event.preview %}
  {% capture result_json %}
    {
      "data": {
        "order": {
          "id": "gid://shopify/Order/1234567890",
          "name": "#PREVIEW",
          "displayFulfillmentStatus": "UNFULFILLED",
          "lineItems": {
            "nodes": [
              {
                "sku": {{ skus_and_quantity_cancellation_thresholds.first.first | json }},
                "quantity": {{ skus_and_quantity_cancellation_thresholds.first.last | json }}
              }
            ]
          }
        }
      }
    }
  {% endcapture %}

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

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

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

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

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

{% comment %}
  -- tally quantities of the configured skus across all line items
{% endcomment %}

{% assign skus_and_line_item_quantities = hash %}

{% for line_item in order.lineItems.nodes %}
  {% if line_item.sku == blank %}
    {% continue %}
  {% endif %}

  {% if skus_to_monitor contains line_item.sku %}
    {% assign skus_and_line_item_quantities[line_item.sku]
      = skus_and_line_item_quantities[line_item.sku]
      | default: 0
      | plus: line_item.quantity
    %}
  {% endif %}
{% endfor %}

{% assign order_qualifies = nil %}

{% for keyval in skus_and_line_item_quantities %}
  {% assign sku = keyval[0] %}
  {% assign count = keyval[1] %}

  {% if count >= skus_and_quantity_cancellation_thresholds[sku] %}
    {% assign order_qualifies = true %}
    {% break %}
  {% endif %}
{% endfor %}

{% unless order_qualifies %}
  {% break %}
{% endunless %}

{% log
  message: "Order qualifies to be cancelled due to having one or more SKUs hit their quantity thresholds.",
  skus_and_line_item_quantities: skus_and_line_item_quantities,
  skus_and_quantity_cancellation_thresholds: skus_and_quantity_cancellation_thresholds
%}

{% 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 %}
Task code is written in Mechanic Liquid, an extension of open-source Liquid enhanced for automation. Learn more