跳至主要內容

網店與市集

退款回倉,只回一次

退款發出,那件貨應該重新變成可售。回倉本身只是一次呼叫,難處在於它必須剛好發生一次:重試的任務如果再加回一件,就會憑空製造出並不存在的庫存,而事後減一是修正,不是還原。Shopify 對自己的庫存變更做了同樣的判斷,把 idempotency key 改為必填。

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

流程

上面那條負責回覆客戶。下面那條定時調整庫存,前面有一道檢查擋住:同一筆退款是否已經回過倉。

平台

退款成立

Shopify Admin API

資料

認領退款編號

Sheets · Airtable

通知

通知客戶

Slack · WhatsApp

編排

每 10 分鐘

n8n 排程

編排

撈出未回倉

n8n

編排

回倉是否尚未執行?

Temporal · Inngest

平台

調整庫存,附 key

Shopify Admin API

編排

不作處理

逐步拆解

  1. 01

    退款成立

    Shopify Admin API

    退款 webhook 觸發,內含退款編號,以及退款當時選定的回倉方式。兩樣都讀進來,但先不要寫回任何系統。

  2. 02

    認領退款編號

    Sheets · Airtable

    一個退款編號一行,並在該編號上設唯一約束。寫得入這一行就是認領成功;如果行已經存在,代表這筆退款處理過,沒有欠下任何動作。這張表就是 idempotency 紀錄,而它便宜,因為它屬於你自己。

  3. 03

    通知客戶

    Slack · WhatsApp

    向客戶確認退款已經發出,同時通知處理退貨的同事。這條訊息講的是錢,不是庫存——兩件事不同,也不必同時到達。

  4. 04

    每 10 分鐘

    n8n 排程

    回倉依排程執行,不依 webhook 執行,所以流程停機期間收到的退款,下一輪仍然會被撿起來。

  5. 05

    撈出未回倉

    n8n

    取出已認領但未確認回倉的紀錄。認領寫得入、確認沒有寫入,正正就是當機那一種情況——所以認領與確認要分開兩個欄位,不是一個開關。

  6. 06

    回倉是否尚未執行?

    Temporal · Inngest

    先確認這筆退款未回過倉,然後才執行,而且兩者放在同一次可以整段重試的執行裡。n8n 與 Make 都不會記住那次調整已經生效,所以回應遺失之後的重試會再加一件貨,而庫存數字沒有辦法指出哪一件是多出來的。要麼用 durable execution,要麼用平台強制的 idempotency key。

  7. 07

    調整庫存,附 key

    Shopify Admin API

    這一步只適用於記錄為不回倉的退款,或者經由退貨退款介面發出、本身不含回倉指令的退款。以取消或退貨方式建立的退款,Shopify 已經回過倉;在這裡再加一件,正正就是這個案例要防止的幽靈庫存。調整時用一條由退款編號推導出來的 key,重試就不會再加一件。

平台不容許的事

回倉只是一次呼叫,但這次呼叫代表什麼、可以安全地做多少次,由 Shopify 定。

  • refundCreate 的說明是「creates a refund for an order, allowing you to process returns and issue payments back to customers」,而它正是可以指定行項目回倉方式的那個 mutation;相對之下 returnRefund「focuses solely on handling the financial refund without any restocking input」。回倉是退款當下就要決定的事,不是事後補上去的一步。

    Shopify Admin GraphQL API — refundCreate
  • 回倉方式是列舉值,不是是與否。CANCEL 的說明是「use this when restocking unfulfilled line items」;RETURN 是「use this when restocking line items that were fulfilled」;NO_RESTOCK 記錄的是該行「was not restocked」。舊有的 LEGACY_RESTOCK 已標為棄用,「is not accepted when creating new refunds」。選錯的話,那件貨會由錯誤的狀態回來,或者根本不回來。

    Shopify Admin GraphQL API — RefundLineItemRestockType
  • inventoryAdjustQuantities 調整的是差額,不是絕對數量。Shopify 文件寫明:「as of version 2026-01, this mutation supports an optional idempotency key using the @idempotent directive」,以及「as of version 2026-04, the idempotency key is required and must be provided using the @idempotent directive」。平台把這個 key 由選填改為必填,理由與這個流程要解的問題一樣:差額加了兩次,不能靠再加一次還原。

    Shopify Admin GraphQL API — inventoryAdjustQuantities
  • WhatsApp 的客服視窗,由用戶主動發訊息或致電開始計算,到期前再聯絡會重置;「when the window closes, you can only send pre-approved template messages」。所以客戶上次聯絡之後隔了幾日才發出的退款確認,必須用模板訊息,不能用自由文字。

    WhatsApp Cloud API — Send messages

什麼情況不值得做

以下三種情況,維護成本會高於多出來的假庫存。

  • 你根本不會把退款的貨回倉——退回來的貨會報廢、送檢,或者改由另一個渠道賣。這樣退款與庫存本來就是兩件事,硬要接成一條流程,等於製造一種你的營運上並不存在的關聯。

  • 退款少到一個人逐筆處理,而且記得住自己處理過。整個流程的存在意義就是保證只做一次;一星期只碰五筆退款的人,本來就做得到,而且更便宜。

  • 你沒有地方記錄回倉發生過。沒有那一行認領紀錄,這裡每一步都只是猜測:流程分不出一筆未處理的退款,與一筆處理過但忘記了的退款。

延伸閱讀

聯絡我們

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

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

開始對談