網店與市集
每張訂單只入帳一次
網店收到付款,幾分鐘之後,帳簿上應該出現一張發票。難的不是入帳這一步,而是同一張訂單可以到達兩次。Shopify 認為送不到就會重試,而中途當機的執行,自己也不知道發票有沒有寫成功。多出來的第二張發票不是顯示問題:收入會被高估,而再跑一次並不會還原。工作流工具在這裡開始不夠用。
- 觸發
- 兩個
- 執行頻率
- 每 15 分鐘
- 工具
- 5 件
- 需要 durable execution
- 兩者擇一
流程
上面那條把這一單記在你自己的表上,不寫帳簿。下面那條定時入帳,並且拒絕處理已經認領過的訂單。
訂單付款完成
Shopify Admin API
組出分錄
n8n · Make
寫入入帳鍵
Sheets · Airtable
每 15 分鐘
n8n 排程
撈出未入帳
n8n
帳簿上是否還未有?
Temporal · Inngest
寫入發票
Xero API
不作處理
—
逐步拆解
- 01
訂單付款完成
Shopify Admin API付款一刻 webhook 觸發。把它當成「有一張訂單存在」的信號,而不是當成可以入帳的許可——同一次送達可以再來一次,Shopify 文件自己就是這樣寫。
- 02
組出分錄
n8n · Make把訂單換算成帳簿要的欄位:淨額、稅、平台費、收款帳戶。這一步不寫任何東西,純粹計算,所以重跑沒有代價——正因如此,它才適合留在工作流工具裡面。
- 03
寫入入帳鍵
Sheets · Airtable一張訂單一行,以訂單編號做鍵,附入帳狀態。決定一張訂單是否還要入帳的是這一行,不是那個 webhook。寫入之前先認領,寫入之後才確認。
- 04
每 15 分鐘
n8n 排程無論有沒有收到 webhook 都會跑。從來沒有送到的通知不會留下任何痕跡,而月結那天才發現,已經太遲。
- 05
撈出未入帳
n8n取出已認領但未確認入帳的行。當中包括在寫入帳簿與寫入確認之間死掉的執行——正是工作流工具會靜靜漏掉的那一種。
- 06
帳簿上是否還未有?
Temporal · Inngest寫入之前先向帳簿查一次這個訂單編號,並且把「查」與「寫」放在同一次可以整段重試的執行裡。這一步 n8n 與 Make 承擔不了:兩者都不會記住那次寫入已經成功,所以回應遺失之後的重試會再寄一次發票,收入就記了兩次。要麼用 durable execution,要麼由帳簿本身強制 idempotency key。
- 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 HTTPSXero 文件寫明,會透過 Idempotency-Key HTTP header,為會改動資料的 POST、PUT、PATCH 請求提供由客戶端給出的 idempotency。key 以 app 為範圍,最多 128 字元;同一個完全相同的請求,快取回應保留六分鐘。
Xero API — Idempotent requestsXero 的 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 requestsGraphQL Admin API 計的是查詢成本,不是請求次數:標準方案每秒 100 點,單一查詢不得超過 1,000 點。月結補做、需要重讀大量訂單時,速度由這個點數預算決定,而不是由流程跑得多快決定。
Shopify — API rate limits
什麼情況不值得做
以下三種情況,維護成本會高於重複入帳的代價。
你現時人手入一張每日總數,而會計師接受這種做法。每日一張分錄只是一行;逐張訂單自動化換來的精細度沒有人要求過,卻多了一個可以重複入帳的系統。
你的帳簿沒有可以做鍵的對外參照欄位。發票上無處記下訂單編號,這個流程的檢查就沒有東西可以查,只能單靠你自己那張表不出錯。
沒有人對數。月結時如果沒有人把帳簿與收款報表比對一次,重複入帳會一直留在裡面,而自動化只是令錯的數字更快出現。