-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy pathcancel-and-close-unpaid-orders-after-two-days.json
31 lines (31 loc) · 11 KB
/
cancel-and-close-unpaid-orders-after-two-days.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
"docs": "This task scans for orders that are more than X days or hours old that have a financial status of \"pending\", and ensures that they are all closed/archived and cancelled. Pending orders that are already closed will be cancelled, and pending orders that are already cancelled will be closed. Optionally, choose to add a tag to such orders, and whether to restock line items.\n\nIf configured with an interval in hours, this task will run hourly. If configured with an interval in days, the task will run every night at midnight, in your store's local timezone. Run this task manually to perform the scan on demand.\n\nRun first using test mode, to ensure expected results before running without it.",
"halt_action_run_sequence_on_error": true,
"name": "Cancel and close unpaid orders after x hours/days",
"online_store_javascript": null,
"options": {
"only_process_orders_having_this_tag": null,
"ignore_orders_having_this_tag": null,
"period_to_wait_before_checking_each_order__number_required": 1,
"period_to_wait_is_in_hours__boolean": false,
"period_to_wait_is_in_days__boolean": true,
"tag_to_add_to_the_order": null,
"cancellation_reason_to_set": "other",
"restock_line_items__boolean": null,
"send_cancellation_email_to_customer__boolean": false,
"test_mode__boolean": true
},
"order_status_javascript": null,
"perform_action_runs_in_sequence": true,
"script": "{% assign only_process_orders_having_this_tag = options.only_process_orders_having_this_tag %}\n{% assign ignore_orders_having_this_tag = options.ignore_orders_having_this_tag %}\n{% assign period_to_wait_before_checking_each_order = options.period_to_wait_before_checking_each_order__number_required %}\n{% assign period_to_wait_is_in_hours = options.period_to_wait_is_in_hours__boolean %}\n{% assign period_to_wait_is_in_days = options.period_to_wait_is_in_days__boolean %}\n{% assign tag_to_add_to_the_order = options.tag_to_add_to_the_order %}\n{% assign cancellation_reason_to_set = options.cancellation_reason_to_set | default: \"other\" %}\n{% assign restock_line_items = options.restock_line_items__boolean %}\n{% assign send_cancellation_email_to_customer = options.send_cancellation_email_to_customer__boolean %}\n{% assign test_mode = options.test_mode__boolean %}\n\n{% comment %}\n -- check that a valid cancellation reason has been configured; it will default to 'other' if left blank\n{% endcomment %}\n\n{% assign valid_cancellation_reasons = \"customer,declined,fraud,inventory,other,staff\" | split: \",\" %}\n\n{% unless valid_cancellation_reasons contains cancellation_reason_to_set %}\n {% error %}\n {{ \"Cancellation reason \" | append: cancellation_reason_to_set | append: \" - must be one of 'customer', 'declined', 'fraud', 'inventory', 'other', or 'staff'.\" | json }}\n {% enderror %}\n{% endunless %}\n\n{% comment %}\n -- check for positive waiting period and that only one unit type is chosen\n{% endcomment %}\n\n{% if period_to_wait_before_checking_each_order <= 0 %}\n {% error \"Period must be positive! :)\" %}\n\n{% elsif period_to_wait_is_in_hours == false and period_to_wait_is_in_days == false %}\n {% error \"Choose either 'Period to wait is in hours' or 'Period to wait is in days'.\" %}\n\n{% elsif period_to_wait_is_in_hours and period_to_wait_is_in_days %}\n {% error \"Choose exactly one of 'Period to wait is in hours' or 'Period to wait is in days'. :)\" %}\n{% endif %}\n\n{% assign period_unit = \"days\" %}\n\n{% if period_to_wait_is_in_hours %}\n {% assign period_unit = \"hours\" %}\n{% endif %}\n\n{% capture cutoff_period -%}\n -{{ period_to_wait_before_checking_each_order }} {{ period_unit }}\n{%- endcapture %}\n\n{% comment %}\n -- cutoff_date is used to limit the orders to those that were processed on or before the cutoff date\n -- cutoff_date_s is used later for a fine grain check of the order processed at date\n{% endcomment %}\n\n{% assign cutoff_date = \"now\" | date: \"%F\", advance: cutoff_period %}\n{% assign cutoff_date_s = \"now\" | date: \"%s\", advance: cutoff_period | times: 1 %}\n\n{% log\n cutoff_period: cutoff_period,\n cutoff_date: cutoff_date,\n cutoff_date_s: cutoff_date_s\n%}\n\n{% if test_mode %}\n {% assign test_mode_summaries = array %}\n{% endif %}\n\n{% comment %}\n -- create the query that will be used to filter orders\n{% endcomment %}\n\n{% capture orders_query %}\n financial_status:pending processed_at:<{{ cutoff_date }}\n {% if only_process_orders_having_this_tag != blank -%}\n tag:{{ only_process_orders_having_this_tag | strip | json }}\n {%- endif %}\n {% if ignore_orders_having_this_tag != blank -%}\n tag_not:{{ ignore_orders_having_this_tag | strip | json }}\n {%- endif %}\n{% endcapture %}\n\n{% assign orders_query = orders_query | strip_newlines | strip %}\n\n{% log orders_query: orders_query %}\n\n{% assign cursor = nil %}\n\n{% comment %}\n -- query and process all orders that meet the query filter\n -- reverse the creation order to get the most recent first (in case this instance is not configured to read all orders)\n{% endcomment %}\n\n{% for n in (0..100) %}\n {% capture query %}\n query {\n orders(\n first: 250\n after: {{ cursor | json }}\n query: {{ orders_query | json }}\n sortKey: CREATED_AT\n reverse: true\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n name\n processedAt\n cancelledAt\n closedAt\n tags\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"orders\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/Order/1234567890\",\n \"name\": \"#PREVIEW\",\n \"processedAt\": \"1990-01-01\",\n \"cancelledAt\": null,\n \"closedAt\": null,\n \"tags\": {{ only_process_orders_having_this_tag | strip | json }}\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% for order in result.data.orders.nodes %}\n {% comment %}\n -- exclude orders that occurred after the calculated cutoff date (in epoch seconds, to avoid timezone conversions)\n {% endcomment %}\n\n {% assign processed_at_s = order.processedAt | date: \"%s\" | times: 1 %}\n\n {% if processed_at_s > cutoff_date_s %}\n {% continue %}\n {% endif %}\n\n {% comment %}\n -- build a summary for output in the task event run logs, and to decide on which mutations to run\n {% endcomment %}\n\n {% assign summary = hash %}\n {% assign summary[\"order_name\"] = order.name %}\n {% assign summary[\"order_id\"] = order.id %}\n {% assign summary[\"order_processed_at\"] = order.processedAt | date: \"%FT%T%:z\" %}\n {% assign summary[\"threshold_processed_at\"] = cutoff_date_s | date: \"%FT%T%:z\" %}\n {% assign summary[\"order_cancelled_at\"] = order.cancelledAt | date: \"%FT%T%:z\" %}\n {% assign summary[\"order_closed_at\"] = order.closedAt | date: \"%FT%T%:z\" %}\n\n {% if order.cancelledAt == blank %}\n {% assign summary[\"qualifies_for_cancellation\"] = true %}\n {% else %}\n {% assign summary[\"qualifies_for_cancellation\"] = false %}\n {% endif %}\n\n {% if order.closedAt == blank %}\n {% assign summary[\"qualifies_for_closing\"] = true %}\n {% else %}\n {% assign summary[\"qualifies_for_closing\"] = false %}\n {% endif %}\n\n {% assign summary[\"qualifies_for_tagging\"] = false %}\n\n {% if tag_to_add_to_the_order != blank %}\n {% assign order_tags = order.tags | join: \",\" | downcase | split: \",\" %}\n {% assign order_tag_to_match = tag_to_add_to_the_order | downcase | strip %}\n\n {% unless order_tags contains order_tag_to_match %}\n {% assign summary[\"qualifies_for_tagging\"] = true %}\n {% endunless %}\n {% endif %}\n\n {% unless summary.qualifies_for_cancellation or summary.qualifies_for_closing or summary.qualifies_for_tagging %}\n {% continue %}\n {% endunless %}\n\n {% if test_mode %}\n {% assign test_mode_summaries[test_mode_summaries.size] = summary %}\n {% continue %}\n {% endif %}\n\n {% log summary %}\n\n {% if summary.qualifies_for_cancellation %}\n {% comment %}\n -- cancel the order with configured options; this will also automatically close the order\n -- if possible, the order will be voided; this is not configurable in this mutation\n -- refunds are set to false and not configurable in the task because these are unpaid orders\n {% endcomment %}\n\n {% action \"shopify\" %}\n mutation {\n orderCancel(\n orderId: {{ order.id | json }}\n notifyCustomer: {{ send_cancellation_email_to_customer | json }}\n reason: {{ cancellation_reason_to_set | upcase }}\n refund: false\n restock: {{ restock_line_items | json }}\n ) {\n job {\n id\n }\n orderCancelUserErrors {\n code\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n\n\n {% if summary.qualifies_for_closing and summary.qualifies_for_cancellation == false %}\n {% comment %}\n -- the orderCancel mutation will also close the order, so do not try to close it again if cancelling above as well\n {% endcomment %}\n\n {% action \"shopify\" %}\n mutation {\n orderClose(\n input: {\n id: {{ order.id | json }}\n }\n ) {\n order {\n closed\n closedAt\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n\n {% if summary.qualifies_for_tagging %}\n {% action \"shopify\" %}\n mutation {\n tagsAdd(\n id: {{ order.id | json }}\n tags: {{ tag_to_add_to_the_order | json }}\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n {% endfor %}\n\n {% if result.data.orders.pageInfo.hasNextPage %}\n {% assign cursor = result.data.orders.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n{% endfor %}\n\n{% if test_mode %}\n {% log\n orders_count: test_mode_summaries.size,\n order_summaries: test_mode_summaries\n %}\n{% endif %}\n",
"subscriptions": [
"mechanic/scheduler/daily",
"mechanic/user/trigger"
],
"subscriptions_template": "{% if options.period_to_wait_is_in_hours__boolean %}\n mechanic/scheduler/hourly\n{% elsif options.period_to_wait_is_in_days__boolean %}\n mechanic/scheduler/daily\n{% endif %}\nmechanic/user/trigger",
"tags": [
"Cancel",
"Orders",
"Unpaid"
]
}