Mechanic is a development and ecommerce automation platform for Shopify. :)
This task searches for draft orders having the configured tag, and duplicates each one (minus the tag used for searching). Optionally, this task can automatically send an invoice to the customer on file, after the new draft order is created. Or, this task can auto-complete the draft order, which will mark it as paid unless the original draft order being duplicated has NET or EVENT payment terms.
Runs Occurs when a user manually triggers the task and Occurs every day at midnight (in local time). Configuration includes draft order tag, cycle start date, number of days in cycle, complete the order after creating, send email invoice after creating, email invoice subject, email invoice bcc, and email invoice custom message.
This task searches for draft orders having the configured tag, and duplicates each one (minus the tag used for searching). Optionally, this task can automatically send an invoice to the customer on file, after the new draft order is created. Or, this task can auto-complete the draft order, which will mark it as paid unless the original draft order being duplicated has NET or EVENT payment terms.
Use the "Cycle start date" and "Number of days in cycle" options to control the frequency of the recurring invoices. The task may also be run manually, but the cycle will still be checked to make sure the current day is valid for the cycle.
IMPORTANT:
- When duplicating invoices, this task will include most discount applications from the original draft order, including custom order and line item discounts, discount codes, and whether or not automatic discounts can be applied.
- Auto-completed draft orders which do not have payment terms will result in the standard Shopify order confirmation email being sent to the customer on file for the original draft order.
- EVENT payment terms include "Payment on receipt" and "Payment on fulfillment"
- FIXED payment terms are not supported by this task
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?)
mechanic/user/trigger
mechanic/scheduler/daily
{% if options.send_email_invoice_after_creating__boolean or options.complete_the_order_after_creating__boolean %}
shopify/draft_orders/create
{% endif %}{% assign draft_order_tag = options.draft_order_tag__required %}
{% assign cycle_start_date = options.cycle_start_date__date_required %}
{% assign number_of_days_in_cycle = options.number_of_days_in_cycle__number_required %}
{% assign complete_the_order_after_creating = options.complete_the_order_after_creating__boolean %}
{% assign send_email_invoice_after_creating = options.send_email_invoice_after_creating__boolean %}
{% assign email_invoice_subject = options.email_invoice_subject %}
{% assign email_invoice_bcc = options.email_invoice_bcc__email_array %}
{% assign email_invoice_custom_message = options.email_invoice_custom_message__multiline %}
{% if send_email_invoice_after_creating and complete_the_order_after_creating %}
{% error "Choose either an email invoice or completing the order - not both. :)" %}
{% endif %}
{% if event.topic == "mechanic/user/trigger" or event.topic contains "mechanic/scheduler/" %}
{% comment %}
-- see if today is valid run day based on the task cycle configuration
{% endcomment %}
{% assign run_qualifies = false %}
{% assign cycle_start_date = cycle_start_date %}
{% assign cycle_start_d = cycle_start_date | date: "%s" | times: 1 | divided_by: 60 | divided_by: 60 | divided_by: 24 %}
{% assign now_d = "now" | date: "%s" | times: 1 | divided_by: 60 | divided_by: 60 | divided_by: 24 %}
{% assign day_gap = now_d | minus: cycle_start_d | modulo: number_of_days_in_cycle %}
{% if day_gap == 0 or event.preview %}
{% assign run_qualifies = true %}
{% else %}
{% log %}
{{ number_of_days_in_cycle | minus: day_gap | prepend: "Waiting another " | append: " day(s) for cycle to complete" | json }}
{% endlog %}
{% endif %}
{% if run_qualifies %}
{% comment %}
-- get all payment terms templates available in shop, to support duplicating draft orders with payment terms
{% endcomment %}
{% capture query %}
query {
paymentTermsTemplates {
id
name
description
paymentTermsType
dueInDays
}
}
{% endcapture %}
{% assign result = query | shopify %}
{% if event.preview %}
{% capture result_json %}
{
"data": {
"paymentTermsTemplates": [
{
"id": "gid://shopify/PaymentTermsTemplate/4",
"name": "Net 30",
"description": "Within 30 days",
"paymentTermsType": "NET",
"dueInDays": 30
}
]
}
}
{% endcapture %}
{% assign result = result_json | parse_json %}
{% endif %}
{% assign payment_terms_templates = result.data.paymentTermsTemplates %}
{% comment %}
-- get all draft orders with the configured tag
{% endcomment %}
{% assign cursor = nil %}
{% for n in (0..10) %}
{% capture query %}
query {
draftOrders(
first: 100
after: {{ cursor | json }}
query: {{ "tag:" | append: draft_order_tag | json }}
sortKey: NUMBER
) {
pageInfo {
hasNextPage
endCursor
}
nodes {
id
name
createdAt
acceptAutomaticDiscounts
allowDiscountCodesInCheckout
appliedDiscount {
amountSet {
shopMoney {
amount
currencyCode
}
}
description
title
value
valueType
}
billingAddress {
address1
address2
city
company
countryCodeV2
firstName
lastName
phone
provinceCode
zip
}
customer {
id
}
customAttributes {
key
value
}
discountCodes
email
lineItems(first: 150) {
nodes {
appliedDiscount {
amountSet {
shopMoney {
amount
currencyCode
}
}
description
title
value
valueType
}
customAttributes {
key
value
}
originalUnitPriceWithCurrency {
amount
currencyCode
}
quantity
sku
taxable
title
variant {
id
}
weight {
unit
value
}
}
}
note2
paymentTerms {
id
paymentTermsName
paymentTermsType
paymentSchedules(first: 1) {
nodes {
id
}
}
}
shippingAddress {
address1
address2
city
company
countryCodeV2
firstName
lastName
phone
provinceCode
zip
}
shippingLine {
title
shippingRateHandle
originalPriceSet {
shopMoney {
amount
currencyCode
}
}
}
tags
taxExempt
}
}
}
{% endcapture %}
{% assign result = query | shopify %}
{% if event.preview %}
{% capture result_json %}
{
"data": {
"draftOrders": {
"nodes": [
{
"id": "gid://shopify/DraftOrder/1234567890",
"billingAddress": {
"address1": "123 Main Street",
"address2": "",
"city": "Springfield",
"company": "Widgets Inc",
"countryCodeV2": "US",
"firstName": "Marge",
"lastName": "Simpson",
"phone": null,
"provinceCode": "IL",
"zip": "62701"
},
"customer": {
"id": "gid://shopify/Customer/1234567890"
},
"email": "customer@example.com",
"lineItems": {
"nodes": [
{
"originalUnitPriceWithCurrency": {
"amount": "5.0",
"currencyCode": "USD"
},
"quantity": 2,
"requiresShipping": false,
"sku": "WIDGET-BEST",
"taxable": true,
"title": "Widget, Our Best",
"weight": {
"unit": "Grams",
"value": 5
}
}
]
},
"paymentTerms": {
"paymentTermsName": "Net 30",
"paymentTermsType": "NET"
},
"tags": {{ array | push: draft_order_tag, "preview-tag" | json }}
}
]
}
}
}
{% endcapture %}
{% assign result = result_json | parse_json %}
{% endif %}
{% for draft_order in result.data.draftOrders.nodes %}
{% log
message: "Draft order qualifies to be duplicated",
original_draft_order: draft_order
%}
{% comment %}
-- marking draft orders as pending upon completion has been deprecated; added support for NET and EVENT payment terms
{% endcomment %}
{% assign payment_terms_input = nil %}
{% if draft_order.paymentTerms != blank %}
{% assign payment_terms_template_id
= payment_terms_templates
| where: "name", draft_order.paymentTerms.paymentTermsName
| map: "id"
| first
%}
{% if draft_order.paymentTerms.paymentTermsType == "FIXED" %}
{% log
message: "FIXED payment terms are not supported by this task because arbitrary due dates cannot be calculated. The new draft order will not be created with any payment terms.",
original_draft_order_name: draft_order.name
%}
{% elsif payment_terms_template_id == blank %}
{% log
message: "Payment terms on original draft order could not be matched to available payment terms in shop.",
payment_terms_templates: payment_terms_templates,
original_draft_order: draft_order
%}
{% else %}
{% capture payment_terms_input %}
paymentTerms: {
paymentTermsTemplateId: {{ payment_terms_template_id | json }}
{% if draft_order.paymentTerms.paymentTermsType == "NET" %}
paymentSchedules: {
issuedAt: {{ "now" | date: "%Y-%m-%dT%H:%M:%SZ", tz: "UTC" | json }}
}
{% endif %}
}
{% endcapture %}
{% endif %}
{% endif %}
{% comment %}
-- create new recurring draft order with as much of the original draft order parameters as possible
{% endcomment %}
{% action "shopify" %}
mutation {
draftOrderCreate(
input: {
acceptAutomaticDiscounts: {{ draft_order.acceptAutomaticDiscounts | json }}
allowDiscountCodesInCheckout: {{ draft_order.allowDiscountCodesInCheckout | json }}
{% if draft_order.appliedDiscount %}
appliedDiscount: {
description: {{ draft_order.appliedDiscount.description | json }}
title: {{ draft_order.appliedDiscount.title | json }}
value: {{ draft_order.appliedDiscount.value }}
valueType: {{ draft_order.appliedDiscount.valueType }}
}
{% endif %}
{% if draft_order.billingAddress %}
billingAddress: {
address1: {{ draft_order.billingAddress.address1 | json }}
address2: {{ draft_order.billingAddress.address2 | json }}
city: {{ draft_order.billingAddress.city | json }}
company: {{ draft_order.billingAddress.company | json }}
countryCode: {{ draft_order.billingAddress.countryCodeV2 }}
firstName: {{ draft_order.billingAddress.firstName | json }}
lastName: {{ draft_order.billingAddress.lastName | json }}
phone: {{ draft_order.billingAddress.phone | json }}
provinceCode: {{ draft_order.billingAddress.provinceCode | json }}
zip: {{ draft_order.billingAddress.zip | json }}
}
{% endif %}
{% if draft_order.customer %}
purchasingEntity: {
customerId: {{ draft_order.customer.id | json }}
}
{% endif %}
{% if draft_order.customAttributes != empty %}
customAttributes: [
{% for custom_attribute in draft_order.customAttributes %}
{
key: {{ custom_attribute.key | json }}
value: {{ custom_attribute.value | json }}
}
{% endfor %}
]
{% endif %}
{% if draft_order.discountCodes != blank %}
discountCodes: {{ draft_order.discountCodes | json }}
{% endif %}
{% if draft_order.email %}
email: {{ draft_order.email | json }}
{% endif %}
lineItems: [
{% for line_item in draft_order.lineItems.nodes %}
{
{% if line_item.appliedDiscount %}
appliedDiscount: {
description: {{ line_item.appliedDiscount.description | json }}
title: {{ line_item.appliedDiscount.title | json }}
value: {{ line_item.appliedDiscount.value | json }}
valueType: {{ line_item.appliedDiscount.valueType }}
}
{% endif %}
{% if line_item.customAttributes != empty %}
customAttributes: [
{% for custom_attribute in line_item.customAttributes %}
{
key: {{ custom_attribute.key | json }}
value: {{ custom_attribute.value | json }}
}
{% endfor %}
]
{% endif %}
{% if line_item.originalUnitPriceWithCurrency.amount %}
originalUnitPriceWithCurrency: {
amount: {{ line_item.originalUnitPriceWithCurrency.amount | json }}
currencyCode: {{ line_item.originalUnitPriceWithCurrency.currencyCode }}
}
{% endif %}
quantity: {{ line_item.quantity | json }}
{% if line_item.requiresShipping != nil %}
requiresShipping: {{ line_item.requiresShipping | json }}
{% endif %}
{% if line_item.sku %}
sku: {{ line_item.sku | json }}
{% endif %}
taxable: {{ line_item.taxable | json }}
title: {{ line_item.title | json }}
{% if line_item.variant %}
variantId: {{ line_item.variant.id | json }}
{% endif %}
{% if line_item.weight and line_item.weight.value != 0 %}
weight: {
value: {{ line_item.weight.value | json }}
unit: {{ line_item.weight.unit }}
}
{% endif %}
}
{% endfor %}
]
{% if send_email_invoice_after_creating or complete_the_order_after_creating %}
metafields: [
{
namespace: "mechanic"
key: {% if send_email_invoice_after_creating %}"autoinvoice"{% else %}"autocomplete"{% endif %}
value: "true"
type: "boolean"
}
]
{% endif %}
{% if payment_terms_input != blank %}
{{ payment_terms_input }}
{% endif %}
{% if draft_order.note2 != blank %}
note: {{ draft_order.note2 | json }}
{% endif %}
{% if draft_order.shippingAddress %}
shippingAddress: {
address1: {{ draft_order.shippingAddress.address1 | json }}
address2: {{ draft_order.shippingAddress.address2 | json }}
city: {{ draft_order.shippingAddress.city | json }}
company: {{ draft_order.shippingAddress.company | json }}
countryCode: {{ draft_order.shippingAddress.countryCodeV2 }}
firstName: {{ draft_order.shippingAddress.firstName | json }}
lastName: {{ draft_order.shippingAddress.lastName | json }}
phone: {{ draft_order.shippingAddress.phone | json }}
provinceCode: {{ draft_order.shippingAddress.provinceCode | json }}
zip: {{ draft_order.shippingAddress.zip | json }}
}
{% endif %}
{% if draft_order.shippingLine %}
shippingLine: {
priceWithCurrency: {
amount: {{ draft_order.shippingLine.originalPriceSet.shopMoney.amount | json }}
currencyCode: {{ draft_order.shippingLine.originalPriceSet.shopMoney.currencyCode }}
}
shippingRateHandle: {{ draft_order.shippingLine.shippingRateHandle | json }}
title: {{ draft_order.shippingLine.title | json }}
}
{% endif %}
tags: {{ draft_order.tags | remove_tag: draft_order_tag | json }}
taxExempt: {{ draft_order.taxExempt | json }}
useCustomerDefaultAddress: false
}
) {
draftOrder {
id
name
}
userErrors {
field
message
}
}
}
{% endaction %}
{% endfor %}
{% if result.data.draftOrders.pageInfo.hasNextPage %}
{% assign cursor = result.data.draftOrders.pageInfo.endCursor %}
{% else %}
{% break %}
{% endif %}
{% endfor %}
{% endif %}
{% elsif event.topic == "shopify/draft_orders/create" and send_email_invoice_after_creating %}
{% comment %}
-- check to see if this newly created draft order has the Mechanic autoinvoice metafield
{% endcomment %}
{% capture query %}
query {
draftOrder(
id: {{ draft_order.admin_graphql_api_id | json }}
) {
metafield(
namespace: "mechanic"
key: "autoinvoice"
) {
value
}
}
}
{% endcapture %}
{% assign result = query | shopify %}
{% if draft_order.email == blank or result.data.draftOrder.metafield == blank %}
{% unless event.preview %}
{% log "Draft order does not contain the Mechanic autoinvoice metafield or the draft order does not have an email; no invoice will be sent." %}
{% break %}
{% endunless %}
{% endif %}
{% action "shopify" %}
mutation {
draftOrderInvoiceSend(
id: {{ draft_order.admin_graphql_api_id | json }}
email: {
{% if email_invoice_subject != blank %}
subject: {{ email_invoice_subject | json }}
{% endif %}
bcc: {{ email_invoice_bcc | json }}
customMessage: {{ email_invoice_custom_message | json }}
}
) {
userErrors {
field
message
}
}
}
{% endaction %}
{% elsif event.topic == "shopify/draft_orders/create" and complete_the_order_after_creating %}
{% comment %}
-- check to see if this newly created draft order has the Mechanic autocomplete metafield
{% endcomment %}
{% capture query %}
query {
draftOrder(
id: {{ draft_order.admin_graphql_api_id | json }}
) {
metafield(
namespace: "mechanic"
key: "autocomplete"
) {
value
}
}
}
{% endcapture %}
{% assign result = query | shopify %}
{% if result.data.draftOrder.metafield == blank %}
{% unless event.preview %}
{% log "Draft order does not contain the Mechanic autocomplete metafield." %}
{% break %}
{% endunless %}
{% endif %}
{% action "shopify" %}
mutation {
draftOrderComplete(
id: {{ draft_order.admin_graphql_api_id | json }}
) {
draftOrder {
order {
id
}
}
userErrors {
field
message
}
}
}
{% endaction %}
{% endif %}
7