跳至主要內容

網店與市集

每張訂單只入帳一次

網店收到付款,幾分鐘之後,帳簿上應該出現一張發票。難的不是入帳這一步,而是同一張訂單可以到達兩次。Shopify 認為送不到就會重試,而中途當機的執行,自己也不知道發票有沒有寫成功。多出來的第二張發票不是顯示問題:收入會被高估,而再跑一次並不會還原。工作流工具在這裡開始不夠用。

觸發
兩個
執行頻率
每 15 分鐘
工具
5 件
需要 durable execution
兩者擇一

流程

上面那條把這一單記在你自己的表上,不寫帳簿。下面那條定時入帳,並且拒絕處理已經認領過的訂單。

平台

訂單付款完成

Shopify Admin API

編排

組出分錄

n8n · Make

資料

寫入入帳鍵

Sheets · Airtable

編排

每 15 分鐘

n8n 排程

編排

撈出未入帳

n8n

編排

帳簿上是否還未有?

Temporal · Inngest

平台

寫入發票

Xero API

編排

不作處理

逐步拆解

  1. 01

    訂單付款完成

    Shopify Admin API

    付款一刻 webhook 觸發。把它當成「有一張訂單存在」的信號,而不是當成可以入帳的許可——同一次送達可以再來一次,Shopify 文件自己就是這樣寫。

  2. 02

    組出分錄

    n8n · Make

    把訂單換算成帳簿要的欄位:淨額、稅、平台費、收款帳戶。這一步不寫任何東西,純粹計算,所以重跑沒有代價——正因如此,它才適合留在工作流工具裡面。

  3. 03

    寫入入帳鍵

    Sheets · Airtable

    一張訂單一行,以訂單編號做鍵,附入帳狀態。決定一張訂單是否還要入帳的是這一行,不是那個 webhook。寫入之前先認領,寫入之後才確認。

  4. 04

    每 15 分鐘

    n8n 排程

    無論有沒有收到 webhook 都會跑。從來沒有送到的通知不會留下任何痕跡,而月結那天才發現,已經太遲。

  5. 05

    撈出未入帳

    n8n

    取出已認領但未確認入帳的行。當中包括在寫入帳簿與寫入確認之間死掉的執行——正是工作流工具會靜靜漏掉的那一種。

  6. 06

    帳簿上是否還未有?

    Temporal · Inngest

    寫入之前先向帳簿查一次這個訂單編號,並且把「查」與「寫」放在同一次可以整段重試的執行裡。這一步 n8n 與 Make 承擔不了:兩者都不會記住那次寫入已經成功,所以回應遺失之後的重試會再寄一次發票,收入就記了兩次。要麼用 durable execution,要麼由帳簿本身強制 idempotency key。

  7. 07

    寫入發票

    Xero API

    寫入發票,把訂單編號放在發票 reference,同時用同一條穩定訂單鍵送入 Xero 的 Idempotency-Key header。reference 用來查找與對帳;header 才是 Xero 用作重試防線的部分。如果另一套帳簿不強制這類 key,reference 加上上一步檢查就必須留在同一次 durable 執行之內。

平台不容許的事

上面的謹慎不是個人偏好。每一部分都是在回應平台自己寫明的行為。

  • Webhook 的送達「isn't always guaranteed」,而且 Shopify「doesn't guarantee ordering within a topic, or across different topics for the same resource」。同一頁亦要求用 X-Shopify-Webhook-Id 忽略重複送達——這個 header 之所以存在,就是因為同一個訂單事件確實會到達多過一次。

    Shopify — About webhooks (best practices)
  • Shopify 給一秒連線、五秒完成整個請求,之後在四小時內重試八次;連續八次失敗,經 Admin API 建立的訂閱會被自動刪除。文件本身的建議是先回應、再把工作排入佇列——所以把入帳寫入塞在 webhook 處理程序裡面,會超時,而超時換來的就是同一張訂單的第二次送達。

    Shopify — Deliver webhooks through HTTPS
  • Xero 文件寫明,會透過 Idempotency-Key HTTP header,為會改動資料的 POST、PUT、PATCH 請求提供由客戶端給出的 idempotency。key 以 app 為範圍,最多 128 字元;同一個完全相同的請求,快取回應保留六分鐘。

    Xero API — Idempotent requests
  • Xero 的 invoice model 有 Reference 欄位,所以網店訂單編號可以寫到發票上,用於查找與對帳;但這個欄位本身不是 idempotency key。

    Xero Accounting API — Invoices
  • 「由接收方強制 idempotency」實際是什麼樣子,可以看有寫明的系統:Stripe 會保存同一個 key 第一次請求的狀態碼與內容,之後同 key 的請求「return the same result, including 500 errors」。key 最長 255 字元,「at least 24 hours old」之後可以被清走;同一個 key 配上不同參數會直接報錯,而不是寫第二次。如果你的帳簿沒有對應機制,防線就只剩你自己那張鍵表,加上一次 durable 執行。

    Stripe API reference — Idempotent requests
  • GraphQL Admin API 計的是查詢成本,不是請求次數:標準方案每秒 100 點,單一查詢不得超過 1,000 點。月結補做、需要重讀大量訂單時,速度由這個點數預算決定,而不是由流程跑得多快決定。

    Shopify — API rate limits

什麼情況不值得做

以下三種情況,維護成本會高於重複入帳的代價。

  • 你現時人手入一張每日總數,而會計師接受這種做法。每日一張分錄只是一行;逐張訂單自動化換來的精細度沒有人要求過,卻多了一個可以重複入帳的系統。

  • 你的帳簿沒有可以做鍵的對外參照欄位。發票上無處記下訂單編號,這個流程的檢查就沒有東西可以查,只能單靠你自己那張表不出錯。

  • 沒有人對數。月結時如果沒有人把帳簿與收款報表比對一次,重複入帳會一直留在裡面,而自動化只是令錯的數字更快出現。

延伸閱讀

聯絡我們

這些流程,你真正應該做哪一條?

我們每星期都在為香港的營運團隊跑這些流程。告訴我們哪個環節正在耗你的時間,我們會告訴你自動化划不划得來。

開始對談