自訂觸發器

內建的觸發器涵蓋了多數商店所關注的變更。自訂觸發器則涵蓋其餘部分:您只需選擇一個 Shopify 事件,撰寫幾行程式碼來判斷該事件是否重要以及該傳送哪些資料,它便會成為您可以在 Shopify Flow 中使用的觸發器。

它能解決三項內建觸發器無法解決的問題:

  • **僅在您設定的條件下執行。**例如:「僅當訂單金額超過 500 歐元時,才為其加上 vip 標籤」這僅需一行程式碼,無需建立一個針對每筆訂單執行後再進行篩選的工作流程。
  • 在過渡過程中觸發,而非特定狀態。您的程式碼會看到變更前與變更後的值,因此「狀態從草稿變為活躍」或「庫存跌破 5」這類情況是可能發生的。若僅以當前值為條件,則無法表達這種情況。
  • **僅傳送您真正需要的欄位。**將事件重新塑造成工作流程實際使用的值,而非逐一讀取這些值。

自訂觸發器甚至不需要「Shopify」事件:它也可以 依照排程執行,並自行判斷哪些內容已變更。

運作原理

  1. 選擇其運作方式:一次「Shopify」事件,或依排程執行
  2. 針對某個事件,請選擇該事件所監聽的事件 - - 即應用程式已接收的任何 Shopify 事件 - - 或從範本開始,系統會自動選取該事件並填入相應程式碼。
  3. **擷取真實的載荷。**在您的商店中進行該變更後,應用程式便會擷取精確的事件內容。
  4. 撰寫一個轉換函式 - - 若要觸發,則傳回一個物件;若要跳過,則傳回 null
  5. 請將其與擷取的事件進行比對,以確切了解您的工作流程會收到什麼內容。
  6. 把它打開。
自訂觸發器編輯器,包含名稱、處理常式,以及可在「單一 Shopify 事件」與「排程」之間進行選擇,並可設定事件與範本
一個新的自訂觸發器:選擇觸發條件、選定事件,並可選擇以範本為基礎進行設定。

撰寫轉換函式

您的觸發器是一個 JavaScript 模組,該模組會匯出一個名為 transform 的函式。此函式會接收四個參數:

  • payload - 原始的 Shopify 事件主體,完全按照其傳入時的原樣呈現。
  • topic - 觸發了哪個事件,例如PRODUCTS_UPDATE
  • shop - 您的 myshopify 網域。
  • ctx - ctx.log(...) 會將結果輸出至編輯器旁邊的日誌面板,ctx.shopify(...) 會執行一個 Admin GraphQL 查詢,而 ctx.fetch(...) 則會呼叫公共網際網路上的某個 URL。

您回傳的內容將決定後續的發展:

  • 傳回一個物件,觸發器便會觸發,並攜帶該物件。
  • **回傳 ``null**,該事件即會被跳過。這就是篩選的運作方式 - - 無需額外學習任何篩選語言。
  • 若將該檔案留空,則每當發生該類型的事件時,它就會觸發。
flow-triggers/high-value-vip-order.jsjavascript
/**
 * Only fire for high-value orders carrying the vip tag.
 */
export async function transform(payload, topic, shop, ctx) {
  const total = parseFloat(payload.total_price || "0");
  const tags = (payload.tags || "").split(",").map(t => t.trim());

  if (total < 500) return null;
  if (!tags.includes("vip")) return null;

  ctx.log("firing for", payload.name, total);

  return {
    orderId: payload.admin_graphql_api_id,
    orderNumber: payload.name,
    total,
    currency: payload.currency,
    customerEmail: payload.email,
  };
}

變更前的數值

Shopify

在「產品更新」、「訂單更新」和「客戶更新」中,payload._changes 會列出此次更新所變更的所有追蹤欄位,並分別附上 oldValuenewValue

  • 產品:titlehandledescriptionstatusvendorproductTypetags
  • 訂單編號:financialStatusfulfillmentStatustagsnotelineItems 以及 customAttributes.<name>
  • 客戶:tagsnotestateENABLEDDISABLEDINVITEDDECLINED

當更新內容不在這些欄位範圍內(例如庫存變動)時,或是應用程式首次讀取該記錄時,該清單會是空的。您在編輯器中擷取的範例事件會顯示該欄位,因此您可以在對其進行寫入操作之前,先查看其實際結構。

flow-triggers/product-went-live.jsjavascript
/**
 * Fire only when a product BECOMES active - not on every later edit of an
 * active product, which is what a condition on the current status would do.
 */
export async function transform(payload, topic, shop, ctx) {
  const change = (payload._changes || []).find((c) => c.field === "status");
  if (!change) return null;
  if (change.oldValue !== "draft" || change.newValue !== "active") return null;

  return {
    productId: payload.admin_graphql_api_id,
    title: payload.title,
    oldStatus: change.oldValue,
    newStatus: change.newValue,
  };
}

較具體的事件各自具有新舊兩方面的價值,因此您無需在該處使用 _changes:價格變更具有 oldPricenewPricepercentChange;庫存變更具有 _oldAvailable_newAvailable_delta;元欄位變更則在 metafield.value 旁邊具有 previousValue

從範本開始

在編輯器中開啟「Fires on」後,選擇「從範本開始」會選取事件並填入可運作的程式碼。編輯頂端的常數、進行測試,然後儲存。每個範本都會讀取變更前後的數值:

  • 某項產品、訂單或客戶欄位的值從一個值變更為另一個值 - - 狀態從「草稿」變更為「有效」、訂單狀態變更為「已付款」、帳戶狀態變更為「已啟用」。
  • 價格下跌了超過 N % - - 這才是真正的降價,並非每次價格調整都是如此。
  • 庫存跌破閾值 - - 觸發一次,即在庫存跨越該線的當下觸發,而非在庫存已處於低位時每次銷售都觸發。
  • 產品元資料欄位數值超過閾值 - - 評分低於 3,利潤率高於 40。
  • 高價值客戶已付款訂單」 - - 此功能會讀取客戶在您店鋪的累計消費金額,並僅在金額超過您設定的門檻時觸發。
編輯器的「轉換」卡片,其中包含一個模板代碼,內容為 payload._changes
範本會自動填入可執行的程式碼。請編輯頂端的常數,進行測試,然後儲存。

使用 ctx.shopify 擷取額外資料

Webhook 的內容僅包含 Shopify 傳送的欄位。若您需要其他資料(例如客戶的訂單數量、變體的庫存或元欄位),請在轉換程式中直接查詢 Admin API:

Enrich the event before decidingjavascript
export async function transform(payload, topic, shop, ctx) {
  const data = await ctx.shopify(`
    query($id: ID!) {
      customer(id: $id) { numberOfOrders tags }
    }
  `, { id: payload.customer.admin_graphql_api_id });

  // Only fire for repeat customers
  if (data.customer.numberOfOrders < 5) return null;

  return {
    orderId: payload.admin_graphql_api_id,
    orderCount: data.customer.numberOfOrders,
  };
}

它會傳回查詢的 data,若查詢出現錯誤則會拋出例外,如此一來,測試中便會顯示錯誤訊息,而非默默不回傳任何結果。

三個值得了解的限制:

  • **每次執行最多 10 次呼叫。**資料增強只需少量資料;若超過這個數量,通常會形成迴圈。只要可行,請盡量透過單一查詢取得所需資料。
  • **它僅能讀取,無法寫入。**它會使用您已授予的權限,且該應用程式僅會請求讀取權限。除非在「權限」頁面中已授予「訂單資料存取」權限,否則查詢訂單資料的請求將會失敗。若要變更商店中的內容,請在觸發器後續的「流程」動作中進行操作。
  • **您的商店憑證絕不會傳遞至您的程式碼中。**該查詢是由應用程式代表您執行的,因此沙盒環境中並無存有會外洩的存取憑證。

ctx.fetch(url, options) 其運作方式類似瀏覽器的 fetch,適用於公開網際網路上的一切內容:供應商的庫存資料、匯率,或是您自己的 API。內部及私有網路位址將被拒絕。若您的觸發程式碼已儲存至 GitHub,請勿在其中放入 API 金鑰。

依排程執行的觸發器

Shopify Flow 當發生某件事時,它才會做出反應。若未發生任何事情,它便無法做出反應,且無法偵測到商店外的任何狀況。因此,請在**「觸發條件」下的「執行時機」中選擇依排程執行」**。此類觸發器背後並無「Shopify」事件:您的程式碼會以間隔方式執行,間隔時間從每 30 秒到每天一次不等,並由程式碼自行判斷何謂變更。

這與 transform 函式相同,只是 payload 不同,且回傳值也不同:

  • payload.state - 無論您的程式碼在先前執行時,state 傳回的值為何。首次執行時, 傳回的值為何。null
  • payload.now 以及 payload.lastRunAt - - 時間戳記。
  • 傳回 { state, events }:需儲存的新狀態(最多 32 KB)以及待觸發的事件清單(每次執行最多 100 項)。每個事件會觸發 Custom Trigger 一次;若您設定了事件的 resourceId,該 ID 將成為 Flow 中的記錄 ID。若無需回報任何內容,請傳回 null

**請讓首次執行僅進行記錄,切勿觸發任何動作。**在首次執行時,您的程式碼沒有可供比對的基準,因此一切都會顯得嶄新。請儲存您所觀察到的內容,並勿回傳任何事件;如此一來,即使啟用觸發器,也不會讓您的工作流程被大量事件淹沒。所有範本皆採用此做法。

在「依排程」模式下的自訂觸發器編輯器,其間隔時間及 30 天內的執行次數
依排程執行:選擇間隔時間 - - 編輯器會顯示總共會執行多少次 - - 並從排程範本開始。
flow-triggers/supplier-stock-changed.jsjavascript
// Fires when a value at a JSON URL changes, with the value before and after.
const URL = "https://api.example.com/stock/1042"

export async function transform(payload, topic, shop, ctx) {
  const res = await ctx.fetch(URL, { headers: { accept: "application/json" } })
  if (!res.ok) throw new Error("The URL answered with HTTP " + res.status)
  const value = String((await res.json()).stock)

  // The first run only remembers the value.
  const previous = payload.state ? payload.state.value : undefined
  if (previous === undefined || previous === value) return { state: { value } }

  return {
    state: { value },
    events: [{ oldValue: previous, newValue: value }],
  }
}

「排程」卡片中「從範本開始」下的排程觸發器範本:

  • 產品、訂單或客戶已 N 天未更新 - - 過期的商品評論、停滯的訂單、客戶回流。每個記錄會在每個「靜默期」觸發一次,而啟用此功能時已逾期的項目,不會一次性全部觸發。
  • Shopify 以外的值已變更 - - 供應商庫存資料、匯率、價格表,其中數值會顯示差異值及百分比。
  • RSS 或 Atom 訂閱源中的新項目 - - 供應商新聞、商品清單訂閱源、狀態頁面。
  • N 小時內無訂單 - - 這是您商店的「心跳」。當訂單停止時觸發一次,訂單恢復時再觸發一次。

**「測試」**功能會執行您的程式碼一次,過程中不會觸發任何操作,也不會儲存狀態,因此您可以隨意重複執行。

在 Shopify Flow 中使用它

每個自訂觸發器在 Flow 中出現時,都會顯示為相同的觸發器名稱:「自訂觸發器」。將其新增至工作流程後,請新增一個條件:「觸發器識別碼等於您的觸發器識別碼」。

該名稱會顯示在觸發器的頁面中,且永遠不會改變,即使您重新命名該觸發器也是如此 - - 因此您的工作流程仍能正常運作。

您的轉換所回傳的每個鍵都會成為觸發器的欄位,若您希望自行解析,整個物件亦可作為 JSON 格式取得。

將程式碼存放於 GitHub

您可以連結一個 GitHub 儲存庫,讓您的觸發程式碼納入版本控制。如此一來,變更內容將可透過拉取請求進行審查,您能查看誰修改了哪些內容,並可將不再運作的轉換操作還原。

請在「開發者」頁面的「連線」區段中進行設定。您可以選擇讓應用程式存取哪些儲存庫,並可隨時在 GitHub 上撤銷該存取權限。

一旦選取了儲存庫,所有現有的觸發器便會立即寫入該儲存庫,且在雙向同步時均能保持同步:

您的職責 會發生什麼事
在應用程式中建立、編輯或複製觸發器 該檔案已提交至您的儲存庫
在應用程式中刪除觸發器 該檔案已從您的儲存庫中移除
將變更推送到已連線的分支 應用程式中已更新觸發器的程式碼

每個觸發器都是一個以該觸發器識別碼命名的檔案,其中包含的正是您在編輯器中看到的模組 - - 沒有任何額外的封裝。這意味著您可以像處理其他 JavaScript 檔案一樣,在自己的編輯器中開啟它、執行它,並對其進行語法檢查。

還原至較早的版本

您無需了解 Git 即可撤銷變更。一旦儲存庫連線完成,編輯器便會顯示一個「版本」下拉選單,列出該檔案的所有先前版本,並附上日期與作者。選擇其中一個版本,系統便會將其以未儲存的變更形式載入編輯器中,讓您能先閱讀內容 - - 只有儲存後,系統才會將該版本還原。

在本地編輯時進行測試

如果您是在自己的編輯器中編輯該檔案,無需先將其儲存至應用程式,即可直接針對擷取的樣本事件執行該程式。請使用「開發者」頁面中具備「**執行」**權限等級的 API 金鑰:

Test the file you are editingbash
curl -X POST https://shopify.workflow-trigger-extensions.app/api/v1/triggers/custom/high-value-vip-order/test \
  -H "Authorization: Bearer ftk_your_key_here" \
  -H "Content-Type: application/json" \
  -d "$(jq -Rn --rawfile c flow-triggers/high-value-vip-order.js '{code:$c}')"

您會獲得與應用程式「測試」按鈕顯示相同的結果:包括是否會觸發、輸出物件、您的 ctx.log 指令,以及執行時間。

此端點不會觸發任何動作,也不會執行任何儲存操作,且不會消耗任何方案配額 - - 因此,在檔案監控器每次儲存時執行此端點都是安全的。(應用程式內的「執行測試」按鈕會觸發您的 Flow 工作流程,讓您能夠觀察其端到端的執行過程;此操作同樣免費。)

您也可以使用讀取金鑰單獨取得範例載荷,將其儲存至本機,並完全在離線狀態下執行該檔案:

Get the captured sample payloadbash
curl https://shopify.workflow-trigger-extensions.app/api/v1/triggers/custom/high-value-vip-order/test \
  -H "Authorization: Bearer ftk_your_key_here"
自訂觸發器會佔用我的方案配額嗎?

是的,而且它會統計所有經過檢查的事件 - - 而不僅僅是觸發該事件的那些。如果您的觸發器監聽「產品更新」事件,而您的商店每月有 60,000 次產品更新,那麼即使您的程式碼僅在其中 100 次觸發,這仍會被計為 60,000 次事件。

我們會接收、去重並將這些事件排入佇列,接著在隔離的沙盒中執行您的程式碼 - - 所有這些步驟都在您的程式碼決定是否觸發之前完成。該計數即反映了這項工作。

換句話說:其費用與同一事件的內建觸發器相同。您無需為篩選功能支付額外費用,且測試始終免費。排程觸發器每次執行僅計費一次。

檢測是免費的嗎?

是的。無論是應用程式中的「執行測試」按鈕,還是 /test API 端點,均不計入配額,儘管「執行測試」功能確實會觸發您的 Flow 工作流程,讓您能觀看其執行過程。只有實際執行才會消耗您的配額,因此您可以隨心所欲地對轉換進行反覆測試。

為什麼 payload._changes 是空的?

要麼是更新內容不在追蹤欄位範圍內 - - 例如產品的庫存或變體變更 - - 要麼是該應用程式目前尚未為該記錄建立基準值。基準值是在應用程式首次偵測到某筆記錄時儲存的,因此,對於從未見過的記錄,其首次更新將不會包含舊值;此後的每次更新則都會包含舊值。

我的程式碼能否修改儲存庫中的資料?

不,ctx.shopify 會以您授予的讀取權限執行,且該應用程式絕不會要求寫入權限。這是刻意設計的:若觸發器修改了其監聽的記錄,便會重新觸發自身,而這正是 Shopify 會因此停用工作流程的原因。請在緊接觸發器之後的 Flow 動作中修改資料。

我之後可以更換把手嗎?

不,這是刻意設計的。您的 Flow 工作流程會根據處理名稱進行篩選,因此若變更處理名稱,該工作流程將會在無任何提示的情況下停止執行。您可以自由重新命名觸發器 - - 處理名稱則保持不變。

這個名稱同時也是您 GitHub 儲存庫中的檔案名稱,因此它也不會移動。

如果我的程式碼有錯誤,會怎麼樣?

該事件將被跳過,且錯誤會記錄在觸發器上,因此您可以查看問題出在哪裡。失敗的轉換絕不會阻擋其他任何操作 - - 您的其他觸發器(無論是自訂或內建的)都會照常運作,不受影響。

我的程式碼會在哪裡執行?

在一個與應用程式其餘部分隔離的沙盒環境中,設有短暫的時間限制,且無法存取您商店的憑證。它僅能看到您擷取的事件有效載荷,以及您透過 ctx.shopifyctx.fetch 擷取的內容。

如果我同時在 GitHub 和應用程式中編輯同一個檔案,會怎樣?

最後儲存的那個版本即為勝出。在應用程式中儲存會將變更提交至檔案,而推送至已連線的分支則會覆寫應用程式中儲存的程式碼。若您主要在儲存庫中工作,請將應用程式的編輯器視為唯讀模式,以免造成意外。

我儲存庫中的檔案能否建立新的觸發器?

不。觸發器還需要知道它正在監聽哪個 Shopify 事件,而該檔案只包含程式碼 - - 若憑猜測來判斷事件,可能會將觸發器連接到錯誤的對象上。請先在應用程式中建立觸發器,然後再自由編輯其檔案。

它能否針對應用程式尚未接收的事件觸發?

不。自訂觸發器會監聽應用程式已為您的商店訂閱的事件,而這些事件取決於您所授予的權限。只要授予某個資源的權限,該資源的事件也會對自訂觸發器開放。至於其他情況,請透過排程來執行。

下一步