From bd8c2edb01160d30710faec37404cb399d712837 Mon Sep 17 00:00:00 2001 From: Brad Hover Date: Fri, 11 Oct 2024 08:04:23 -0700 Subject: [PATCH] convert price rules to discounts (#410) --- .../README.md | 27 +- .../script.liquid | 261 +++++++++-------- .../README.md | 57 ++-- .../script.liquid | 264 +++++++++++++----- ...e-when-a-certain-product-is-purchased.json | 40 ++- ...t-when-a-voucher-product-is-purchased.json | 34 ++- 6 files changed, 432 insertions(+), 251 deletions(-) diff --git a/docs/generate-a-discount-code-when-a-certain-product-is-purchased/README.md b/docs/generate-a-discount-code-when-a-certain-product-is-purchased/README.md index 331ab86b..aab46f81 100644 --- a/docs/generate-a-discount-code-when-a-certain-product-is-purchased/README.md +++ b/docs/generate-a-discount-code-when-a-certain-product-is-purchased/README.md @@ -31,6 +31,7 @@ This task watches for newly-paid orders, and if the configured product is purcha ```liquid shopify/orders/paid +mechanic/actions/perform ``` [Learn about event subscriptions in Mechanic](https://learn.mechanic.dev/core/tasks/subscriptions) @@ -39,19 +40,19 @@ shopify/orders/paid This task watches for newly-paid orders, and if the configured product is purchased, sends the customer a discount code that's just for them. Optionally, configure the discounts to only apply to a certain collection of products, and to only last for a certain number of days. -This task watches for newly-paid orders, and if the configured product is purchased, sends the customer a discount code that's just for them. If a customer purchases more than one qualified product, they will receive more than one email, each containing a unique discount code. - -### Options - -* **Required product ID:** The ID of the product that the customer must purchase, in order to qualify for the discount. ([Learn how to find the product ID.](https://help.usemechanic.com/articles/2946120-how-do-i-find-an-id-for-a-product-collection-order-or-something-else)) -* **Discount collection ID (optional):** The ID of a specific collection of products that the discount code should be good for. ([Learn how to find the collection ID.](https://help.usemechanic.com/articles/2946120-how-do-i-find-an-id-for-a-product-collection-order-or-something-else)) -* **Discount code prefix (optional):** A small piece of text to add to the beginning of the generated discount code. -* **Discount fixed amount:** The money value to be subtracted. If you choose this option, you cannot choose a discount percentage. -* **Discount percentage:** The percentage to be subtracted. If you choose this option, you cannot choose a fixed discount amount. -* **Discount applies to each line item individually:** If enabled, the discount will apply to each item ordered. If disabled, the discount will only apply once per order. -* **Discount lifetime in days:** How long the discount should be active. -* **Discount can be used by anyone:** If enabled, the discount code can be used by anyone. If disabled, the discount code can only be used by the purchasing customer. -* **Email subject, body:** The content to email to the customer. Use "DISCOUNT_CODE" as a placeholder for the generated discount code. +If a customer purchases more than one qualified product, they will receive more than one email, each containing a unique discount code. + +### Options + +- **Required product ID:** The ID of the product that the customer must purchase, in order to qualify for the discount. ([Learn how to find the product ID.](https://learn.mechanic.dev/techniques/finding-a-resource-id)) +- **Discount collection ID (optional):** The ID of a specific collection of products that the discount code should be good for. ([Learn how to find the collection ID.](https://learn.mechanic.dev/techniques/finding-a-resource-id)) +- **Discount code prefix (optional):** A small piece of text to add to the beginning of the generated discount code. +- **Discount fixed amount:** The money value to be subtracted. If you choose this option, you cannot choose a discount percentage. +- **Discount percentage:** The percentage to be subtracted (e.g. 15). If you choose this option, you cannot choose a fixed discount amount. +- **Discount applies to each line item individually:** If enabled and if a collection ID is configured, the discount will apply to each item from the collection on the order. If disabled, the discount will only apply once per order. If no collection ID is configured, this setting will be overridden to apply once oer order. +- **Discount lifetime in days:** How long the discount should be active. +- **Discount can be used by anyone:** If enabled, the discount code can be used by anyone. If disabled, the discount code can only be used by the purchasing customer. +- **Email subject, body:** The content to email to the customer. Use "DISCOUNT_CODE" as a placeholder for the generated discount code. ## Installing this task diff --git a/docs/generate-a-discount-code-when-a-certain-product-is-purchased/script.liquid b/docs/generate-a-discount-code-when-a-certain-product-is-purchased/script.liquid index 8a0344f4..80926c0e 100644 --- a/docs/generate-a-discount-code-when-a-certain-product-is-purchased/script.liquid +++ b/docs/generate-a-discount-code-when-a-certain-product-is-purchased/script.liquid @@ -1,140 +1,169 @@ -{% comment %} - Options order: - - {{ options.required_product_id__number_required }} - {{ options.discount_collection_id__number }} - {{ options.discount_code_prefix }} - {{ options.discount_fixed_amount__number }} - {{ options.discount_percentage__number }} - {{ options.discount_applies_to_each_line_item_individually__boolean }} - {{ options.discount_lifetime_in_days__number }} - {{ options.discount_can_be_used_by_anyone__boolean }} - {{ options.email_subject__required }} - {{ options.email_body__multiline_required }} -{% endcomment %} - -{% if options.discount_percentage__number == blank and options.discount_fixed_amount__number == blank %} +{% assign required_product_id = options.required_product_id__number_required %} +{% assign discount_collection_id = options.discount_collection_id__number %} +{% assign discount_code_prefix = options.discount_code_prefix %} +{% assign discount_fixed_amount = options.discount_fixed_amount__number %} +{% assign discount_percentage = options.discount_percentage__number %} +{% assign discount_applies_to_each_line_item_individually = options.discount_applies_to_each_line_item_individually__boolean %} +{% assign discount_lifetime_in_days = options.discount_lifetime_in_days__number %} +{% assign discount_can_be_used_by_anyone = options.discount_can_be_used_by_anyone__boolean %} +{% assign email_subject = options.email_subject__required %} +{% assign email_body = options.email_body__multiline_required %} + +{% if discount_percentage == blank and discount_fixed_amount == blank %} {% error "Please fill either the discount percentage or discount fixed amount." %} -{% elsif options.discount_percentage__number != blank and options.discount_fixed_amount__number != blank %} + +{% elsif discount_percentage != blank and discount_fixed_amount != blank %} {% error "Please choose between the discount percentage and discount fixed amount - only one is permitted." %} {% endif %} -{% if event.preview %} - {% capture order_json %} - { - "email": "customer@example.com", - "customer": { - "admin_graphql_api_id": "gid://shopify/Customer/1234567890" - }, - "line_items": [ - { - "id": 1234567890, - "product_id": {{ options.required_product_id__number_required | json }}, - "quantity": 1 - } - ] - } - {% endcapture %} - - {% assign order = order_json | parse_json %} +{% if discount_collection_id != blank %} + {% assign discount_collection_id = discount_collection_id | prepend: "gid://shopify/Collection/" %} {% endif %} -{% for line_item in order.line_items %} - {% if line_item.product_id != options.required_product_id__number_required %} - {% continue %} +{% if event.topic == "shopify/orders/paid" %} + {% if event.preview %} + {% capture order_json %} + { + "email": "customer@example.com", + "customer": { + "id": 1234567890 + }, + "line_items": [ + { + "id": 1234567890, + "product_id": {{ required_product_id }}, + "quantity": 1 + } + ] + } + {% endcapture %} + + {% assign order = order_json | parse_json %} {% endif %} - {% if order.customer == nil %} + {% if order.customer == blank %} {% log "This order has no related customer. Skipping" %} - {% continue %} + {% break %} {% endif %} {% if order.email == blank %} {% log "This order has no email address. Skipping" %} - {% continue %} + {% break %} {% endif %} - {% for n in (1..line_item.quantity) %} - {% assign discount_code = line_item.id | append: n | split: "" | reverse | join: "" | slice: 0, 4 | append: order.order_number | base64 | replace: "=", "" | upcase | prepend: options.discount_code_prefix %} + {% assign customer_id = order.customer.id | prepend: "gid://shopify/Customer/" %} - {% assign email_subject = options.email_subject__required | replace: 'DISCOUNT_CODE', discount_code %} - {% assign email_body = options.email_body__multiline_required | replace: 'DISCOUNT_CODE', discount_code %} + {% comment %} + -- create a discount code for each quantity of matching line items + {% endcomment %} - {% action "email" %} - { - "to": {{ order.email | json }}, - "subject": {{ email_subject | strip | json }}, - "body": {{ email_body | strip | newline_to_br | json }}, - "reply_to": {{ shop.customer_email | json }}, - "from_display_name": {{ shop.name | json }} - } - {% endaction %} - - {% action "shopify" %} - mutation { - priceRuleCreate( - priceRule: { - allocationMethod: {% if options.discount_applies_to_each_line_item_individually__boolean %}EACH{% else %}ACROSS{% endif %} - customerSelection: { - {% if options.discount_can_be_used_by_anyone__boolean %} - forAllCustomers: true - {% else %} - customerIdsToAdd: [ - {{ order.customer.admin_graphql_api_id | json }} - ] - {% endif %} - } - itemEntitlements: { - {% if options.discount_collection_id__number != blank %} - collectionIds: [ - {{ shop.collections[options.discount_collection_id__number].admin_graphql_api_id | json }} - ] - {% else %} - targetAllLineItems: true + {% for line_item in order.line_items %} + {% if line_item.product_id != required_product_id %} + {% continue %} + {% endif %} + + {% for n in (1..line_item.quantity) %} + {% assign discount_code = line_item.id | append: n | split: "" | reverse | join: "" | slice: 0, 4 | append: order.order_number | base64 | replace: "=", "" | upcase | prepend: discount_code_prefix %} + + {% action "shopify" %} + mutation { + discountCodeBasicCreate( + basicCodeDiscount: { + code: {{ discount_code | json }} + customerSelection: { + {% if discount_can_be_used_by_anyone %} + all: true + {% else %} + customers: { + add: {{ array | push: customer_id | json }} + } + {% endif %} + } + title: {{ discount_code | json }} + startsAt: {{ "now" | date: "%FT%T%:z" | json }} + {% if discount_lifetime_in_days != blank %} + {% assign validity_days_s = 60 | times: 60 | times: 24 | times: discount_lifetime_in_days %} + endsAt: {{ "now" | date: "%s" | plus: validity_days_s | date: "%FT%T%:z" | json }} {% endif %} + customerGets: { + items: { + {% if discount_collection_id != blank %} + collections: { + add: {{ array | push: discount_collection_id | json }} + } + {% else %} + all: true + {% endif %} + } + value: { + {% if discount_percentage != blank %} + percentage: {{ discount_percentage | abs | divided_by: 100.0 | json }} + {% else %} + discountAmount: { + amount: {{ discount_fixed_amount | abs | times: 1.0 | json }} + {% if discount_collection_id != blank %} + appliesOnEachItem: {{ discount_applies_to_each_line_item_individually }} + {% else %} + appliesOnEachItem: false + {% endif %} + } + {% endif %} + } + } } - target: LINE_ITEM - title: {{ discount_code | json }} - validityPeriod: { - start: {{ "now" | date: "%FT%T%:z" | json }} - - {% if options.discount_lifetime_in_days__number != blank %} - {% assign validity_days_s = 60 | times: 60 | times: 24 | times: options.discount_lifetime_in_days__number %} - end: {{ "now" | date: "%s" | plus: validity_days_s | date: "%FT%T%:z" | json }} - {% endif %} + ) { + codeDiscountNode { + id + codeDiscount { + ... on DiscountCodeBasic { + codes(first: 1) { + nodes { + id + code + } + } + endsAt + summary + } + } } - value: { - {% if options.discount_percentage__number != blank %} - percentageValue: {{ options.discount_percentage__number | abs | times: -1 | json }} - {% endif %} - - {% if options.discount_fixed_amount__number != blank %} - fixedAmountValue: {{ options.discount_fixed_amount__number | abs | times: -1 | append: "" | json }} - {% endif %} + userErrors { + code + extraInfo + field + message } } - priceRuleDiscountCode: { - code: {{ discount_code | json }} - } - ) { - priceRule { - id - allocationMethod - endsAt - target - summary - } - priceRuleUserErrors { - code - field - message - } - priceRuleDiscountCode { - code - id - } } - } - {% endaction %} + {% endaction %} + {% endfor %} {% endfor %} -{% endfor %} + +{% elsif event.topic == "mechanic/actions/perform" %} + {% comment %} + -- only process a successful discountCodeBasicCreate shopify action; the Error reporter task should be used to respond to failures + {% endcomment %} + + {% unless action.type == "shopify" and action.run.ok == true %} + {% break %} + {% endunless %} + + {% comment %} + -- send a customer email for the created discount code + {% endcomment %} + + {% assign discount_code = action.run.result.data.discountCodeBasicCreate.codeDiscountNode.codeDiscount.codes.nodes.first.code %} + + {% assign email_subject = email_subject | replace: 'DISCOUNT_CODE', discount_code %} + {% assign email_body = email_body | replace: 'DISCOUNT_CODE', discount_code %} + + {% action "email" %} + { + "to": {{ event.parent.data.email | json }}, + "subject": {{ email_subject | strip | json }}, + "body": {{ email_body | strip | newline_to_br | json }}, + "reply_to": {{ shop.customer_email | json }}, + "from_display_name": {{ shop.name | json }} + } + {% endaction %} +{% endif %} diff --git a/docs/generate-a-product-discount-when-a-voucher-product-is-purchased/README.md b/docs/generate-a-product-discount-when-a-voucher-product-is-purchased/README.md index 2e61890e..1e2a61cf 100644 --- a/docs/generate-a-product-discount-when-a-voucher-product-is-purchased/README.md +++ b/docs/generate-a-product-discount-when-a-voucher-product-is-purchased/README.md @@ -2,7 +2,7 @@ Tags: Discounts, Products, Watch -Use this task to sell vouchers for future purchases. Buy a product, receive an emailed discount code for a discount on another specific product. +Use this task to generate discounts for future purchases. When a customer buys a "voucher" product, they will be emailed a single-use discount code for a fixed amount of money off a purchase on a corresponding "entitled" product". * View in the task library: [tasks.mechanic.dev/generate-a-product-discount-when-a-voucher-product-is-purchased](https://tasks.mechanic.dev/generate-a-product-discount-when-a-voucher-product-is-purchased) * Task JSON, for direct import: [task.json](../../tasks/generate-a-product-discount-when-a-voucher-product-is-purchased.json) @@ -14,9 +14,9 @@ Use this task to sell vouchers for future purchases. Buy a product, receive an e { "voucher_product_ids_and_entitled_product_ids__keyval_number_required": {}, "discount_code_prefix__required": "HOORAY", - "discount_value__number_required": "-5", - "email_subject__required": "[Order {{ order.name }}] Thanks! Your discount is attached.", - "email_body__multiline_required": "Hi {{ order.customer.first_name | default: \"there\" }},\n\nThanks for your order! Here's your discount code:\n\nDISCOUNT_CODE\n\nYour purchase of VOUCHER_PRODUCT_TITLE has earned you a one-time discount on a future order of ENTITLED_PRODUCT_TITLE.\n\nThanks,\nThe team at {{ shop.name }}" + "discount_value__number_required": "5", + "email_subject__required": "[Order ORDER_NAME] Thanks! Your discount is attached.", + "email_body__multiline_required": "Hi CUSTOMER_FIRST_NAME,\n\nThanks for your order! Here's your discount code:\n\nDISCOUNT_CODE\n\nYour purchase of VOUCHER_PRODUCT_TITLE has earned you a one-time discount on a future order of ENTITLED_PRODUCT_TITLE.\n\nThanks,\nThe team at {{ shop.name }}" } ``` @@ -26,45 +26,28 @@ Use this task to sell vouchers for future purchases. Buy a product, receive an e ```liquid shopify/orders/paid +mechanic/actions/perform ``` [Learn about event subscriptions in Mechanic](https://learn.mechanic.dev/core/tasks/subscriptions) ## Documentation -Use this task to sell vouchers for future purchases. Buy a product, receive an emailed discount code for a discount on another specific product. - -### Getting started - -Make sure each of your voucher products has a SKU filled in. This is important: the SKU will be a part of each generated discount code. - -In Mechanic, fill in the "Voucher product IDs and entitled product IDs" option with the IDs of the voucher products you're selling on the left, and the IDs of products you want to be discounted on the right. - -To find a product ID, open up each product in the Shopify admin, and look at the URL in your browser. The numbers at the very end are your product ID. For example, if this is the URL you're seeing: - -``` -https://example.myshopify.com/admin/products/1234567890 -``` - -... then your product ID is `1234567890`. - -### Background - -For the purposes of this task, a "voucher product" is a product you sell to earn a discount to an "entitled product". - -This task builds discount coupon codes that are single-use, for a fixed amount of money off a purchase on a specific entitled product. - -To make unique discount codes, this task assembles codes using this format: - -``` -[PREFIX][ORDER NUMBER][VOUCHER SKU][QUANTITY COUNTER][CUSTOMER ID] -``` - -For example, discount codes might look like: - -* `HOORAY1027SIMPLE1806736592949` -* `FLASH381EARLYRISER2914633758241` -* `PRESALE9912EAS135947168211735` +Use this task to generate discounts for future purchases. When a customer buys a "voucher" product, they will be emailed a single-use discount code for a fixed amount of money off a purchase on a corresponding "entitled" product". + +If a customer purchases more than one voucher product, they will receive more than one email, each containing a unique discount code. + +### Options + +- **Voucher product IDs and entitled product IDs:** Enter the IDs of the voucher products you're selling on the left, and the IDs of products you want to be discounted on the right. ([Learn how to find the product IDs.](https://learn.mechanic.dev/techniques/finding-a-resource-id)) +- **Discount code prefix:** A small piece of text to add to the beginning of the generated discount code. +- **Discount value:** The money value to be subtracted. +- **Email subject, body:** The content to email to the customer. May use the following variables: + - CUSTOMER_FIRST_NAME + - DISCOUNT_CODE + - ENTITLED_PRODUCT_TITLE + - ORDER_NAME + - VOUCHER_PRODUCT_TITLE ## Installing this task diff --git a/docs/generate-a-product-discount-when-a-voucher-product-is-purchased/script.liquid b/docs/generate-a-product-discount-when-a-voucher-product-is-purchased/script.liquid index 907159b9..3aeeaf7e 100644 --- a/docs/generate-a-product-discount-when-a-voucher-product-is-purchased/script.liquid +++ b/docs/generate-a-product-discount-when-a-voucher-product-is-purchased/script.liquid @@ -1,96 +1,210 @@ {% assign ve_product_ids_keyval = options.voucher_product_ids_and_entitled_product_ids__keyval_number_required %} +{% assign discount_code_prefix = options.discount_code_prefix__required %} +{% assign discount_value = options.discount_value__number_required %} +{% assign email_subject = options.email_subject__required %} +{% assign email_body = options.email_body__multiline_required %} -{% if event.preview or order.customer %} - {% for order_line_item in order.line_items %} - {% assign voucher_product_id = order_line_item.product_id %} +{% if event.topic == "shopify/orders/paid" %} + {% if event.preview %} + {% capture order_json %} + { + "email": "customer@example.com", + "customer": { + "id": 1234567890, + "first_name": "TEST" + }, + "line_items": [ + { + "id": 1234567890, + "product_id": {{ ve_product_ids_keyval.first.first }}, + "quantity": 1 + } + ] + } + {% endcapture %} + + {% assign order = order_json | parse_json %} + {% endif %} + + {% if order.customer == blank %} + {% log "This order has no related customer. Skipping" %} + {% break %} + {% endif %} + + {% if order.email == blank %} + {% log "This order has no email address. Skipping" %} + {% break %} + {% endif %} + + {% assign customer_id = order.customer.id | prepend: "gid://shopify/Customer/" %} + + {% for line_item in order.line_items %} + {% assign voucher_product_title = line_item.title %} + {% assign voucher_product_id = line_item.product_id %} {% assign voucher_product_id_string = voucher_product_id | append: "" %} {% assign entitled_product_id = ve_product_ids_keyval[voucher_product_id_string] %} - {% if event.preview or entitled_product_id %} - {% for order_line_item_n in (1..order_line_item.quantity) %} - {% assign discount_code = options.discount_code_prefix__required | append: order.order_number | append: order_line_item.sku | append: order_line_item_n | append: order.customer.id %} + {% if entitled_product_id == blank %} + {% continue %} + {% endif %} - {% if event.preview or shop.discount_codes[discount_code] == nil %} - {% assign voucher_product = shop.products[voucher_product_id] %} - {% assign entitled_product = shop.products[entitled_product_id] %} + {% comment %} + -- make sure the entitled product exists in the shop and get its title + {% endcomment %} - {% assign voucher_product_title = voucher_product.title | default: "(voucher product title)" %} - {% assign entitled_product_title = entitled_product.title | default: "(entitled product title)" %} - {% assign entitled_product_admin_graphql_api_id = entitled_product.admin_graphql_api_id %} + {% capture query %} + query { + product(id: {{ entitled_product_id | prepend: "gid://shopify/Product/" | json }}) { + id + title + } + } + {% endcapture %} - {% assign customer_admin_graphql_api_id = shop.customers[order.customer.id].admin_graphql_api_id %} + {% assign result = query | shopify %} - {% capture graphql_query %} - mutation { - priceRuleCreate( - priceRule: { - allocationMethod: ACROSS - customerSelection: { - customerIdsToAdd: [{{ customer_admin_graphql_api_id | json }}] - } - itemEntitlements:{ - productIds: [{{ entitled_product_admin_graphql_api_id | json }}] - } - target: LINE_ITEM - title: {{ discount_code | json }} - usageLimit: 1 - validityPeriod: { - start: {{ "now" | date: "%FT%T%:z" | json }} - } - value: { - fixedAmountValue: {{ options.discount_value__number_required | json }} - } - } - priceRuleDiscountCode: { - code: {{ discount_code | json }} + {% if event.preview %} + {% capture result_json %} + { + "data": { + "product": { + "id": "gid://shopify/Product/1234567890", + "title": "Widget" + } + } + } + {% endcapture %} + + {% assign result = result_json | parse_json %} + {% endif %} + + {% assign entitled_product = result.data.product %} + + {% if entitled_product == blank %} + {% action "echo" + __error: "Entitled product not found in the shop. Discount code will not be created.", + entitled_product_id: entitled_product_id, + voucher_product_id: voucher_product_id + %} + {% continue %} + {% endif %} + + {% for line_item_n in (1..line_item.quantity) %} + {% assign discount_code = line_item.id | append: n | split: "" | reverse | join: "" | slice: 0, 4 | append: order.order_number | base64 | replace: "=", "" | upcase | prepend: discount_code_prefix %} + + {% capture mutation %} + mutation { + discountCodeBasicCreate( + basicCodeDiscount: { + code: {{ discount_code | json }} + customerSelection: { + customers: { + add: {{ array | push: customer_id | json }} } - ) { - priceRule { - id + } + title: {{ discount_code | json }} + startsAt: {{ "now" | date: "%FT%T%:z" | json }} + customerGets: { + items: { + products: { + productsToAdd: {{ array | push: entitled_product.id | json }} + } } - priceRuleUserErrors { - code - field - message + value: { + discountAmount: { + amount: {{ discount_fixed_amount | abs | times: 1.0 | json }} + appliesOnEachItem: false + } } - priceRuleDiscountCode { - code - id + } + usageLimit: 1 + } + ) { + codeDiscountNode { + id + codeDiscount { + ... on DiscountCodeBasic { + codes(first: 1) { + nodes { + id + code + } + } + summary } } } - {% endcapture %} - - { - "action": { - "type": "shopify", - "options": {{ graphql_query | json }} + userErrors { + code + extraInfo + field + message } } + } + {% endcapture %} - {% assign email_subject = options.email_subject__required | replace: 'DISCOUNT_CODE', discount_code | replace: 'VOUCHER_PRODUCT_TITLE', voucher_product_title | replace: 'ENTITLED_PRODUCT_TITLE', entitled_product_title %} - {% assign email_body = options.email_body__multiline_required | replace: 'DISCOUNT_CODE', discount_code | replace: 'VOUCHER_PRODUCT_TITLE', voucher_product_title | replace: 'ENTITLED_PRODUCT_TITLE', entitled_product_title %} - - { - "action": { - "type": "email", - "options": { - "to": {{ order.email | json }}, - "subject": {{ email_subject | strip | json }}, - "body": {{ email_body | newline_to_br | json }}, - "reply_to": {{ shop.customer_email | json }}, - "from_display_name": {{ shop.name | json }} - } - } + {% action %} + { + "type": "shopify", + "options": { + "query": {{ mutation | json }} + }, + "meta": { + "customer_first_name": {{ order.customer.first_name | default: "there" | json }}, + "order_email": {{ order.email | json }}, + "order_name": {{ order.name | json }}, + "discount_code": {{ discount_code | json }}, + "entitled_product_title": {{ entitled_product.title | json }}, + "voucher_product_title": {{ voucher_product_title | json }} } - {% else %} - {"log": {{ discount_code | append: " already exists; skipping" | json }}} - {% endif %} - {% endfor %} - {% endif %} - - {% if event.preview %} - {% break %} - {% endif %} + } + {% endaction %} + {% endfor %} {% endfor %} + +{% elsif event.topic == "mechanic/actions/perform" %} + {% comment %} + -- only process a successful discountCodeBasicCreate shopify action; the Error reporter task should be used to respond to failures + {% endcomment %} + + {% unless action.type == "shopify" and action.run.ok == true %} + {% break %} + {% endunless %} + + {% assign customer_first_name = action.meta.customer_first_name %} + {% assign order_email = action.meta.order_email %} + {% assign order_name = action.meta.order_name %} + {% assign discount_code = action.meta.discount_code %} + {% assign entitled_product_title = action.meta.entitled_product_title %} + {% assign voucher_product_title = action.meta.voucher_product_title %} + + {% comment %} + -- send a customer email for the created discount code + {% endcomment %} + + {% assign email_subject = email_subject + | replace: 'CUSTOMER_FIRST_NAME', customer_first_name + | replace: 'DISCOUNT_CODE', discount_code + | replace: 'ENTITLED_PRODUCT_TITLE', entitled_product_title + | replace: 'ORDER_NAME', order_name + | replace: 'VOUCHER_PRODUCT_TITLE', voucher_product_title + %} + {% assign email_body = email_body + | replace: 'CUSTOMER_FIRST_NAME', customer_first_name + | replace: 'DISCOUNT_CODE', discount_code + | replace: 'ENTITLED_PRODUCT_TITLE', entitled_product_title + | replace: 'ORDER_NAME', order_name + | replace: 'VOUCHER_PRODUCT_TITLE', voucher_product_title + %} + + {% action "email" %} + { + "to": {{ order_email | json }}, + "subject": {{ email_subject | strip | json }}, + "body": {{ email_body | strip | newline_to_br | json }}, + "reply_to": {{ shop.customer_email | json }}, + "from_display_name": {{ shop.name | json }} + } + {% endaction %} {% endif %} diff --git a/tasks/generate-a-discount-code-when-a-certain-product-is-purchased.json b/tasks/generate-a-discount-code-when-a-certain-product-is-purchased.json index 4b151de2..7b021fed 100644 --- a/tasks/generate-a-discount-code-when-a-certain-product-is-purchased.json +++ b/tasks/generate-a-discount-code-when-a-certain-product-is-purchased.json @@ -1,5 +1,5 @@ { - "docs": "This task watches for newly-paid orders, and if the configured product is purchased, sends the customer a discount code that's just for them. Optionally, configure the discounts to only apply to a certain collection of products, and to only last for a certain number of days.\n\nThis task watches for newly-paid orders, and if the configured product is purchased, sends the customer a discount code that's just for them. If a customer purchases more than one qualified product, they will receive more than one email, each containing a unique discount code.\r\n\r\n### Options\r\n\r\n* **Required product ID:** The ID of the product that the customer must purchase, in order to qualify for the discount. ([Learn how to find the product ID.](https://help.usemechanic.com/articles/2946120-how-do-i-find-an-id-for-a-product-collection-order-or-something-else))\r\n* **Discount collection ID (optional):** The ID of a specific collection of products that the discount code should be good for. ([Learn how to find the collection ID.](https://help.usemechanic.com/articles/2946120-how-do-i-find-an-id-for-a-product-collection-order-or-something-else))\r\n* **Discount code prefix (optional):** A small piece of text to add to the beginning of the generated discount code.\r\n* **Discount fixed amount:** The money value to be subtracted. If you choose this option, you cannot choose a discount percentage.\r\n* **Discount percentage:** The percentage to be subtracted. If you choose this option, you cannot choose a fixed discount amount.\r\n* **Discount applies to each line item individually:** If enabled, the discount will apply to each item ordered. If disabled, the discount will only apply once per order.\r\n* **Discount lifetime in days:** How long the discount should be active.\r\n* **Discount can be used by anyone:** If enabled, the discount code can be used by anyone. If disabled, the discount code can only be used by the purchasing customer.\r\n* **Email subject, body:** The content to email to the customer. Use \"DISCOUNT_CODE\" as a placeholder for the generated discount code.", + "docs": "This task watches for newly-paid orders, and if the configured product is purchased, sends the customer a discount code that's just for them. Optionally, configure the discounts to only apply to a certain collection of products, and to only last for a certain number of days.\n\nIf a customer purchases more than one qualified product, they will receive more than one email, each containing a unique discount code.\n\n### Options\n\n- **Required product ID:** The ID of the product that the customer must purchase, in order to qualify for the discount. ([Learn how to find the product ID.](https://learn.mechanic.dev/techniques/finding-a-resource-id))\n- **Discount collection ID (optional):** The ID of a specific collection of products that the discount code should be good for. ([Learn how to find the collection ID.](https://learn.mechanic.dev/techniques/finding-a-resource-id))\n- **Discount code prefix (optional):** A small piece of text to add to the beginning of the generated discount code.\n- **Discount fixed amount:** The money value to be subtracted. If you choose this option, you cannot choose a discount percentage.\n- **Discount percentage:** The percentage to be subtracted (e.g. 15). If you choose this option, you cannot choose a fixed discount amount.\n- **Discount applies to each line item individually:** If enabled and if a collection ID is configured, the discount will apply to each item from the collection on the order. If disabled, the discount will only apply once per order. If no collection ID is configured, this setting will be overridden to apply once oer order.\n- **Discount lifetime in days:** How long the discount should be active.\n- **Discount can be used by anyone:** If enabled, the discount code can be used by anyone. If disabled, the discount code can only be used by the purchasing customer.\n- **Email subject, body:** The content to email to the customer. Use \"DISCOUNT_CODE\" as a placeholder for the generated discount code.", "halt_action_run_sequence_on_error": false, "name": "Generate a discount code when a certain product is purchased", "online_store_javascript": null, @@ -17,11 +17,43 @@ }, "order_status_javascript": null, "perform_action_runs_in_sequence": false, - "script": "{% comment %}\n Options order:\n\n {{ options.required_product_id__number_required }}\n {{ options.discount_collection_id__number }}\n {{ options.discount_code_prefix }}\n {{ options.discount_fixed_amount__number }}\n {{ options.discount_percentage__number }}\n {{ options.discount_applies_to_each_line_item_individually__boolean }}\n {{ options.discount_lifetime_in_days__number }}\n {{ options.discount_can_be_used_by_anyone__boolean }}\n {{ options.email_subject__required }}\n {{ options.email_body__multiline_required }}\n{% endcomment %}\n\n{% if options.discount_percentage__number == blank and options.discount_fixed_amount__number == blank %}\n {% error \"Please fill either the discount percentage or discount fixed amount.\" %}\n{% elsif options.discount_percentage__number != blank and options.discount_fixed_amount__number != blank %}\n {% error \"Please choose between the discount percentage and discount fixed amount - only one is permitted.\" %}\n{% endif %}\n\n{% if event.preview %}\n {% capture order_json %}\n {\n \"email\": \"customer@example.com\",\n \"customer\": {\n \"admin_graphql_api_id\": \"gid://shopify/Customer/1234567890\"\n },\n \"line_items\": [\n {\n \"id\": 1234567890,\n \"product_id\": {{ options.required_product_id__number_required | json }},\n \"quantity\": 1\n }\n ]\n }\n {% endcapture %}\n\n {% assign order = order_json | parse_json %}\n{% endif %}\n\n{% for line_item in order.line_items %}\n {% if line_item.product_id != options.required_product_id__number_required %}\n {% continue %}\n {% endif %}\n\n {% if order.customer == nil %}\n {% log \"This order has no related customer. Skipping\" %}\n {% continue %}\n {% endif %}\n\n {% if order.email == blank %}\n {% log \"This order has no email address. Skipping\" %}\n {% continue %}\n {% endif %}\n\n {% for n in (1..line_item.quantity) %}\n {% assign discount_code = line_item.id | append: n | split: \"\" | reverse | join: \"\" | slice: 0, 4 | append: order.order_number | base64 | replace: \"=\", \"\" | upcase | prepend: options.discount_code_prefix %}\n\n {% assign email_subject = options.email_subject__required | replace: 'DISCOUNT_CODE', discount_code %}\n {% assign email_body = options.email_body__multiline_required | replace: 'DISCOUNT_CODE', discount_code %}\n\n {% action \"email\" %}\n {\n \"to\": {{ order.email | json }},\n \"subject\": {{ email_subject | strip | json }},\n \"body\": {{ email_body | strip | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n\n {% action \"shopify\" %}\n mutation {\n priceRuleCreate(\n priceRule: {\n allocationMethod: {% if options.discount_applies_to_each_line_item_individually__boolean %}EACH{% else %}ACROSS{% endif %}\n customerSelection: {\n {% if options.discount_can_be_used_by_anyone__boolean %}\n forAllCustomers: true\n {% else %}\n customerIdsToAdd: [\n {{ order.customer.admin_graphql_api_id | json }}\n ]\n {% endif %}\n }\n itemEntitlements: {\n {% if options.discount_collection_id__number != blank %}\n collectionIds: [\n {{ shop.collections[options.discount_collection_id__number].admin_graphql_api_id | json }}\n ]\n {% else %}\n targetAllLineItems: true\n {% endif %}\n }\n target: LINE_ITEM\n title: {{ discount_code | json }}\n validityPeriod: {\n start: {{ \"now\" | date: \"%FT%T%:z\" | json }}\n\n {% if options.discount_lifetime_in_days__number != blank %}\n {% assign validity_days_s = 60 | times: 60 | times: 24 | times: options.discount_lifetime_in_days__number %}\n end: {{ \"now\" | date: \"%s\" | plus: validity_days_s | date: \"%FT%T%:z\" | json }}\n {% endif %}\n }\n value: {\n {% if options.discount_percentage__number != blank %}\n percentageValue: {{ options.discount_percentage__number | abs | times: -1 | json }}\n {% endif %}\n\n {% if options.discount_fixed_amount__number != blank %}\n fixedAmountValue: {{ options.discount_fixed_amount__number | abs | times: -1 | append: \"\" | json }}\n {% endif %}\n }\n }\n priceRuleDiscountCode: {\n code: {{ discount_code | json }}\n }\n ) {\n priceRule {\n id\n allocationMethod\n endsAt\n target\n summary\n }\n priceRuleUserErrors {\n code\n field\n message\n }\n priceRuleDiscountCode {\n code\n id\n }\n }\n }\n {% endaction %}\n {% endfor %}\n{% endfor %}", + "preview_event_definitions": [ + { + "description": "Discount code successfully created", + "event_attributes": { + "data": { + "run": { + "ok": true, + "result": { + "data": { + "discountCodeBasicCreate": { + "codeDiscountNode": { + "codeDiscount": { + "codes": { + "nodes": [ + { + "code": "ABCD1234" + } + ] + } + } + } + } + } + } + }, + "type": "shopify" + }, + "topic": "mechanic/actions/perform" + } + } + ], + "script": "{% assign required_product_id = options.required_product_id__number_required %}\n{% assign discount_collection_id = options.discount_collection_id__number %}\n{% assign discount_code_prefix = options.discount_code_prefix %}\n{% assign discount_fixed_amount = options.discount_fixed_amount__number %}\n{% assign discount_percentage = options.discount_percentage__number %}\n{% assign discount_applies_to_each_line_item_individually = options.discount_applies_to_each_line_item_individually__boolean %}\n{% assign discount_lifetime_in_days = options.discount_lifetime_in_days__number %}\n{% assign discount_can_be_used_by_anyone = options.discount_can_be_used_by_anyone__boolean %}\n{% assign email_subject = options.email_subject__required %}\n{% assign email_body = options.email_body__multiline_required %}\n\n{% if discount_percentage == blank and discount_fixed_amount == blank %}\n {% error \"Please fill either the discount percentage or discount fixed amount.\" %}\n\n{% elsif discount_percentage != blank and discount_fixed_amount != blank %}\n {% error \"Please choose between the discount percentage and discount fixed amount - only one is permitted.\" %}\n{% endif %}\n\n{% if discount_collection_id != blank %}\n {% assign discount_collection_id = discount_collection_id | prepend: \"gid://shopify/Collection/\" %}\n{% endif %}\n\n{% if event.topic == \"shopify/orders/paid\" %}\n {% if event.preview %}\n {% capture order_json %}\n {\n \"email\": \"customer@example.com\",\n \"customer\": {\n \"id\": 1234567890\n },\n \"line_items\": [\n {\n \"id\": 1234567890,\n \"product_id\": {{ required_product_id }},\n \"quantity\": 1\n }\n ]\n }\n {% endcapture %}\n\n {% assign order = order_json | parse_json %}\n {% endif %}\n\n {% if order.customer == blank %}\n {% log \"This order has no related customer. Skipping\" %}\n {% break %}\n {% endif %}\n\n {% if order.email == blank %}\n {% log \"This order has no email address. Skipping\" %}\n {% break %}\n {% endif %}\n\n {% assign customer_id = order.customer.id | prepend: \"gid://shopify/Customer/\" %}\n\n {% comment %}\n -- create a discount code for each quantity of matching line items\n {% endcomment %}\n\n {% for line_item in order.line_items %}\n {% if line_item.product_id != required_product_id %}\n {% continue %}\n {% endif %}\n\n {% for n in (1..line_item.quantity) %}\n {% assign discount_code = line_item.id | append: n | split: \"\" | reverse | join: \"\" | slice: 0, 4 | append: order.order_number | base64 | replace: \"=\", \"\" | upcase | prepend: discount_code_prefix %}\n\n {% action \"shopify\" %}\n mutation {\n discountCodeBasicCreate(\n basicCodeDiscount: {\n code: {{ discount_code | json }}\n customerSelection: {\n {% if discount_can_be_used_by_anyone %}\n all: true\n {% else %}\n customers: {\n add: {{ array | push: customer_id | json }}\n }\n {% endif %}\n }\n title: {{ discount_code | json }}\n startsAt: {{ \"now\" | date: \"%FT%T%:z\" | json }}\n {% if discount_lifetime_in_days != blank %}\n {% assign validity_days_s = 60 | times: 60 | times: 24 | times: discount_lifetime_in_days %}\n endsAt: {{ \"now\" | date: \"%s\" | plus: validity_days_s | date: \"%FT%T%:z\" | json }}\n {% endif %}\n customerGets: {\n items: {\n {% if discount_collection_id != blank %}\n collections: {\n add: {{ array | push: discount_collection_id | json }}\n }\n {% else %}\n all: true\n {% endif %}\n }\n value: {\n {% if discount_percentage != blank %}\n percentage: {{ discount_percentage | abs | divided_by: 100.0 | json }}\n {% else %}\n discountAmount: {\n amount: {{ discount_fixed_amount | abs | times: 1.0 | json }}\n {% if discount_collection_id != blank %}\n appliesOnEachItem: {{ discount_applies_to_each_line_item_individually }}\n {% else %}\n appliesOnEachItem: false\n {% endif %}\n }\n {% endif %}\n }\n }\n }\n ) {\n codeDiscountNode {\n id\n codeDiscount {\n ... on DiscountCodeBasic {\n codes(first: 1) {\n nodes {\n id\n code\n }\n }\n endsAt\n summary\n }\n }\n }\n userErrors {\n code\n extraInfo\n field\n message\n }\n }\n }\n {% endaction %}\n {% endfor %}\n {% endfor %}\n\n{% elsif event.topic == \"mechanic/actions/perform\" %}\n {% comment %}\n -- only process a successful discountCodeBasicCreate shopify action; the Error reporter task should be used to respond to failures\n {% endcomment %}\n\n {% unless action.type == \"shopify\" and action.run.ok == true %}\n {% break %}\n {% endunless %}\n\n {% comment %}\n -- send a customer email for the created discount code\n {% endcomment %}\n\n {% assign discount_code = action.run.result.data.discountCodeBasicCreate.codeDiscountNode.codeDiscount.codes.nodes.first.code %}\n\n {% assign email_subject = email_subject | replace: 'DISCOUNT_CODE', discount_code %}\n {% assign email_body = email_body | replace: 'DISCOUNT_CODE', discount_code %}\n\n {% action \"email\" %}\n {\n \"to\": {{ event.parent.data.email | json }},\n \"subject\": {{ email_subject | strip | json }},\n \"body\": {{ email_body | strip | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n{% endif %}\n", "subscriptions": [ - "shopify/orders/paid" + "shopify/orders/paid", + "mechanic/actions/perform" ], - "subscriptions_template": "shopify/orders/paid", + "subscriptions_template": "shopify/orders/paid\nmechanic/actions/perform", "tags": [ "Discounts", "Marketing", diff --git a/tasks/generate-a-product-discount-when-a-voucher-product-is-purchased.json b/tasks/generate-a-product-discount-when-a-voucher-product-is-purchased.json index 7d13d69b..eff2a43f 100644 --- a/tasks/generate-a-product-discount-when-a-voucher-product-is-purchased.json +++ b/tasks/generate-a-product-discount-when-a-voucher-product-is-purchased.json @@ -1,22 +1,44 @@ { - "docs": "Use this task to sell vouchers for future purchases. Buy a product, receive an emailed discount code for a discount on another specific product.\n\n### Getting started\r\n\r\nMake sure each of your voucher products has a SKU filled in. This is important: the SKU will be a part of each generated discount code.\r\n\r\nIn Mechanic, fill in the \"Voucher product IDs and entitled product IDs\" option with the IDs of the voucher products you're selling on the left, and the IDs of products you want to be discounted on the right.\r\n\r\nTo find a product ID, open up each product in the Shopify admin, and look at the URL in your browser. The numbers at the very end are your product ID. For example, if this is the URL you're seeing:\r\n\r\n```\r\nhttps://example.myshopify.com/admin/products/1234567890\r\n```\r\n\r\n... then your product ID is `1234567890`.\r\n\r\n### Background\r\n\r\nFor the purposes of this task, a \"voucher product\" is a product you sell to earn a discount to an \"entitled product\".\r\n\r\nThis task builds discount coupon codes that are single-use, for a fixed amount of money off a purchase on a specific entitled product.\r\n\r\nTo make unique discount codes, this task assembles codes using this format:\r\n\r\n```\r\n[PREFIX][ORDER NUMBER][VOUCHER SKU][QUANTITY COUNTER][CUSTOMER ID]\r\n```\r\n\r\nFor example, discount codes might look like:\r\n\r\n* `HOORAY1027SIMPLE1806736592949`\r\n* `FLASH381EARLYRISER2914633758241`\r\n* `PRESALE9912EAS135947168211735`", + "docs": "Use this task to generate discounts for future purchases. When a customer buys a \"voucher\" product, they will be emailed a single-use discount code for a fixed amount of money off a purchase on a corresponding \"entitled\" product\".\n\nIf a customer purchases more than one voucher product, they will receive more than one email, each containing a unique discount code.\n\n### Options\n\n- **Voucher product IDs and entitled product IDs:** Enter the IDs of the voucher products you're selling on the left, and the IDs of products you want to be discounted on the right. ([Learn how to find the product IDs.](https://learn.mechanic.dev/techniques/finding-a-resource-id))\n- **Discount code prefix:** A small piece of text to add to the beginning of the generated discount code.\n- **Discount value:** The money value to be subtracted.\n- **Email subject, body:** The content to email to the customer. May use the following variables:\n - CUSTOMER_FIRST_NAME\n - DISCOUNT_CODE\n - ENTITLED_PRODUCT_TITLE\n - ORDER_NAME\n - VOUCHER_PRODUCT_TITLE", "halt_action_run_sequence_on_error": false, "name": "Generate a product discount when a voucher product is purchased", "online_store_javascript": null, "options": { "voucher_product_ids_and_entitled_product_ids__keyval_number_required": {}, "discount_code_prefix__required": "HOORAY", - "discount_value__number_required": "-5", - "email_subject__required": "[Order {{ order.name }}] Thanks! Your discount is attached.", - "email_body__multiline_required": "Hi {{ order.customer.first_name | default: \"there\" }},\n\nThanks for your order! Here's your discount code:\n\nDISCOUNT_CODE\n\nYour purchase of VOUCHER_PRODUCT_TITLE has earned you a one-time discount on a future order of ENTITLED_PRODUCT_TITLE.\n\nThanks,\nThe team at {{ shop.name }}" + "discount_value__number_required": "5", + "email_subject__required": "[Order ORDER_NAME] Thanks! Your discount is attached.", + "email_body__multiline_required": "Hi CUSTOMER_FIRST_NAME,\n\nThanks for your order! Here's your discount code:\n\nDISCOUNT_CODE\n\nYour purchase of VOUCHER_PRODUCT_TITLE has earned you a one-time discount on a future order of ENTITLED_PRODUCT_TITLE.\n\nThanks,\nThe team at {{ shop.name }}" }, "order_status_javascript": null, "perform_action_runs_in_sequence": false, - "script": "{% assign ve_product_ids_keyval = options.voucher_product_ids_and_entitled_product_ids__keyval_number_required %}\n\n{% if event.preview or order.customer %}\n {% for order_line_item in order.line_items %}\n {% assign voucher_product_id = order_line_item.product_id %}\n {% assign voucher_product_id_string = voucher_product_id | append: \"\" %}\n {% assign entitled_product_id = ve_product_ids_keyval[voucher_product_id_string] %}\n\n {% if event.preview or entitled_product_id %}\n {% for order_line_item_n in (1..order_line_item.quantity) %}\n {% assign discount_code = options.discount_code_prefix__required | append: order.order_number | append: order_line_item.sku | append: order_line_item_n | append: order.customer.id %}\n\n {% if event.preview or shop.discount_codes[discount_code] == nil %}\n {% assign voucher_product = shop.products[voucher_product_id] %}\n {% assign entitled_product = shop.products[entitled_product_id] %}\n\n {% assign voucher_product_title = voucher_product.title | default: \"(voucher product title)\" %}\n {% assign entitled_product_title = entitled_product.title | default: \"(entitled product title)\" %}\n {% assign entitled_product_admin_graphql_api_id = entitled_product.admin_graphql_api_id %}\n\n {% assign customer_admin_graphql_api_id = shop.customers[order.customer.id].admin_graphql_api_id %}\n\n {% capture graphql_query %}\n mutation {\n priceRuleCreate(\n priceRule: {\n allocationMethod: ACROSS\n customerSelection: {\n customerIdsToAdd: [{{ customer_admin_graphql_api_id | json }}]\n }\n itemEntitlements:{\n productIds: [{{ entitled_product_admin_graphql_api_id | json }}]\n }\n target: LINE_ITEM\n title: {{ discount_code | json }}\n usageLimit: 1\n validityPeriod: {\n start: {{ \"now\" | date: \"%FT%T%:z\" | json }}\n }\n value: {\n fixedAmountValue: {{ options.discount_value__number_required | json }}\n }\n }\n priceRuleDiscountCode: {\n code: {{ discount_code | json }}\n }\n ) {\n priceRule {\n id\n }\n priceRuleUserErrors {\n code\n field\n message\n }\n priceRuleDiscountCode {\n code\n id\n }\n }\n }\n {% endcapture %}\n\n {\n \"action\": {\n \"type\": \"shopify\",\n \"options\": {{ graphql_query | json }}\n }\n }\n\n {% assign email_subject = options.email_subject__required | replace: 'DISCOUNT_CODE', discount_code | replace: 'VOUCHER_PRODUCT_TITLE', voucher_product_title | replace: 'ENTITLED_PRODUCT_TITLE', entitled_product_title %}\n {% assign email_body = options.email_body__multiline_required | replace: 'DISCOUNT_CODE', discount_code | replace: 'VOUCHER_PRODUCT_TITLE', voucher_product_title | replace: 'ENTITLED_PRODUCT_TITLE', entitled_product_title %}\n\n {\n \"action\": {\n \"type\": \"email\",\n \"options\": {\n \"to\": {{ order.email | json }},\n \"subject\": {{ email_subject | strip | json }},\n \"body\": {{ email_body | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n }\n }\n {% else %}\n {\"log\": {{ discount_code | append: \" already exists; skipping\" | json }}}\n {% endif %}\n {% endfor %}\n {% endif %}\n\n {% if event.preview %}\n {% break %}\n {% endif %}\n {% endfor %}\n{% endif %}", + "preview_event_definitions": [ + { + "description": "Discount code successfully created", + "event_attributes": { + "data": { + "meta": { + "customer_first_name": "Customer", + "discount_code": "ABCD1234", + "entitled_product_title": "Widget B", + "order_email": "customer@example.com", + "order_name": "#PREVIEW", + "voucher_product_title": "Widget A" + }, + "run": { + "ok": true + }, + "type": "shopify" + }, + "topic": "mechanic/actions/perform" + } + } + ], + "script": "{% assign ve_product_ids_keyval = options.voucher_product_ids_and_entitled_product_ids__keyval_number_required %}\n{% assign discount_code_prefix = options.discount_code_prefix__required %}\n{% assign discount_value = options.discount_value__number_required %}\n{% assign email_subject = options.email_subject__required %}\n{% assign email_body = options.email_body__multiline_required %}\n\n{% if event.topic == \"shopify/orders/paid\" %}\n {% if event.preview %}\n {% capture order_json %}\n {\n \"email\": \"customer@example.com\",\n \"customer\": {\n \"id\": 1234567890,\n \"first_name\": \"TEST\"\n },\n \"line_items\": [\n {\n \"id\": 1234567890,\n \"product_id\": {{ ve_product_ids_keyval.first.first }},\n \"quantity\": 1\n }\n ]\n }\n {% endcapture %}\n\n {% assign order = order_json | parse_json %}\n {% endif %}\n\n {% if order.customer == blank %}\n {% log \"This order has no related customer. Skipping\" %}\n {% break %}\n {% endif %}\n\n {% if order.email == blank %}\n {% log \"This order has no email address. Skipping\" %}\n {% break %}\n {% endif %}\n\n {% assign customer_id = order.customer.id | prepend: \"gid://shopify/Customer/\" %}\n\n {% for line_item in order.line_items %}\n {% assign voucher_product_title = line_item.title %}\n {% assign voucher_product_id = line_item.product_id %}\n {% assign voucher_product_id_string = voucher_product_id | append: \"\" %}\n {% assign entitled_product_id = ve_product_ids_keyval[voucher_product_id_string] %}\n\n {% if entitled_product_id == blank %}\n {% continue %}\n {% endif %}\n\n {% comment %}\n -- make sure the entitled product exists in the shop and get its title\n {% endcomment %}\n\n {% capture query %}\n query {\n product(id: {{ entitled_product_id | prepend: \"gid://shopify/Product/\" | json }}) {\n id\n title\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"product\": {\n \"id\": \"gid://shopify/Product/1234567890\",\n \"title\": \"Widget\"\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% assign entitled_product = result.data.product %}\n\n {% if entitled_product == blank %}\n {% action \"echo\"\n __error: \"Entitled product not found in the shop. Discount code will not be created.\",\n entitled_product_id: entitled_product_id,\n voucher_product_id: voucher_product_id\n %}\n {% continue %}\n {% endif %}\n\n {% for line_item_n in (1..line_item.quantity) %}\n {% assign discount_code = line_item.id | append: n | split: \"\" | reverse | join: \"\" | slice: 0, 4 | append: order.order_number | base64 | replace: \"=\", \"\" | upcase | prepend: discount_code_prefix %}\n\n {% capture mutation %}\n mutation {\n discountCodeBasicCreate(\n basicCodeDiscount: {\n code: {{ discount_code | json }}\n customerSelection: {\n customers: {\n add: {{ array | push: customer_id | json }}\n }\n }\n title: {{ discount_code | json }}\n startsAt: {{ \"now\" | date: \"%FT%T%:z\" | json }}\n customerGets: {\n items: {\n products: {\n productsToAdd: {{ array | push: entitled_product.id | json }}\n }\n }\n value: {\n discountAmount: {\n amount: {{ discount_fixed_amount | abs | times: 1.0 | json }}\n appliesOnEachItem: false\n }\n }\n }\n usageLimit: 1\n }\n ) {\n codeDiscountNode {\n id\n codeDiscount {\n ... on DiscountCodeBasic {\n codes(first: 1) {\n nodes {\n id\n code\n }\n }\n summary\n }\n }\n }\n userErrors {\n code\n extraInfo\n field\n message\n }\n }\n }\n {% endcapture %}\n\n {% action %}\n {\n \"type\": \"shopify\",\n \"options\": {\n \"query\": {{ mutation | json }}\n },\n \"meta\": {\n \"customer_first_name\": {{ order.customer.first_name | default: \"there\" | json }},\n \"order_email\": {{ order.email | json }},\n \"order_name\": {{ order.name | json }},\n \"discount_code\": {{ discount_code | json }},\n \"entitled_product_title\": {{ entitled_product.title | json }},\n \"voucher_product_title\": {{ voucher_product_title | json }}\n }\n }\n {% endaction %}\n {% endfor %}\n {% endfor %}\n\n{% elsif event.topic == \"mechanic/actions/perform\" %}\n {% comment %}\n -- only process a successful discountCodeBasicCreate shopify action; the Error reporter task should be used to respond to failures\n {% endcomment %}\n\n {% unless action.type == \"shopify\" and action.run.ok == true %}\n {% break %}\n {% endunless %}\n\n {% assign customer_first_name = action.meta.customer_first_name %}\n {% assign order_email = action.meta.order_email %}\n {% assign order_name = action.meta.order_name %}\n {% assign discount_code = action.meta.discount_code %}\n {% assign entitled_product_title = action.meta.entitled_product_title %}\n {% assign voucher_product_title = action.meta.voucher_product_title %}\n\n {% comment %}\n -- send a customer email for the created discount code\n {% endcomment %}\n\n {% assign email_subject = email_subject\n | replace: 'CUSTOMER_FIRST_NAME', customer_first_name\n | replace: 'DISCOUNT_CODE', discount_code\n | replace: 'ENTITLED_PRODUCT_TITLE', entitled_product_title\n | replace: 'ORDER_NAME', order_name\n | replace: 'VOUCHER_PRODUCT_TITLE', voucher_product_title\n %}\n {% assign email_body = email_body\n | replace: 'CUSTOMER_FIRST_NAME', customer_first_name\n | replace: 'DISCOUNT_CODE', discount_code\n | replace: 'ENTITLED_PRODUCT_TITLE', entitled_product_title\n | replace: 'ORDER_NAME', order_name\n | replace: 'VOUCHER_PRODUCT_TITLE', voucher_product_title\n %}\n\n {% action \"email\" %}\n {\n \"to\": {{ order_email | json }},\n \"subject\": {{ email_subject | strip | json }},\n \"body\": {{ email_body | strip | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n{% endif %}\n", "subscriptions": [ "shopify/orders/paid" ], - "subscriptions_template": "shopify/orders/paid", + "subscriptions_template": "shopify/orders/paid\nmechanic/actions/perform", "tags": [ "Discounts", "Products",