Backend Engineering·2026年5月18日·11 分鐘閱讀

金流串接實戰:冪等、對帳與 Webhook 的那些坑

L
Louis Wu

後端工程師,整合過多家金流(信用卡、第三方支付)的下單、退款與對帳流程。

金流是那種「平常沒事、出事就是錢」的系統。我整合過好幾家金流商,從信用卡到第三方支付,最深的體會是:金流串接真正的難度不在「打 API 送出付款」,而在處理各種「我不確定到底成功了沒」的中間狀態。這篇講幾個你一定會遇到的坑。

冪等是金流的第一原則

使用者在結帳頁狂點「付款」、手機網路斷線後重試、前端 timeout 但後端其實成功了——這些都會讓同一筆訂單的付款請求送出多次。如果你沒做冪等,使用者就會被重複扣款,然後你就會收到客訴和退款工單。

我的作法:

  • 每筆付款請求在進來時就帶一個 idempotency key(通常用訂單編號,或前端產生的 UUID)
  • 後端在建立付款前,先用這個 key 嘗試寫入一筆「付款意圖」記錄,並對 key 做唯一索引
  • 如果 key 已存在,直接回傳上一次的結果,不再向金流商送第二次

關鍵在於「先佔位、再執行」:唯一索引的衝突就是你的併發保護。不要用「先查有沒有、沒有才寫」這種 check-then-act,那在併發下一定會破。

Webhook 一定會晚到、重送、亂序

金流商的付款結果常常是非同步的:你送出請求拿到「處理中」,真正的結果透過 webhook 回拋。而 webhook 有三個你必須假設一定會發生的特性:

  • 會重送:同一個事件可能收到好幾次
  • 會晚到:有時候使用者都已經在結果頁等了,webhook 還沒到
  • 可能亂序:付款成功和退款的通知順序不保證

對應的設計原則:

  • Webhook handler 必須冪等,用金流商給的交易序號去重
  • 不要相信 webhook 的「順序」,要用狀態機判斷合法的狀態轉移(例如已退款的單收到「付款成功」要忽略)
  • Webhook 收到後先落地再處理:先把原始通知存起來回 200,再非同步處理。不要在 handler 裡做一堆事讓金流商等到 timeout,它會把你的成功通知判定為失敗而重送

還有一個血淚教訓:一定要驗簽。Webhook 端點是公開的,任何人都能打。沒驗簽等於讓別人偽造「付款成功」通知。

主動查詢,不要只依賴 webhook

Webhook 會掉。網路抖動、你的服務剛好在部署、對方系統異常,通知就這樣不見了。如果你的付款狀態完全依賴 webhook,就會有訂單永遠卡在「處理中」。

所以一定要有一個主動對帳機制:對於停在中間狀態超過 N 分鐘的訂單,定期主動去金流商查詢真實狀態,把狀態補正。Webhook 是「快」,主動查詢是「保證最終一致」,兩個都要有。

每日對帳:抓出帳不平的單

就算上面都做了,你還是需要每天跟金流商的對帳檔比對一次。原因很簡單:你系統認為的成交,和金流商實際請款的金額、筆數,中間任何一個環節的 bug 都可能造成落差。

對帳要比對的至少有:

  • 筆數是否一致
  • 每筆金額是否一致
  • 你有、對方沒有的單(可能你誤判成功)
  • 對方有、你沒有的單(可能 webhook 全掉了)

把這個做成每日自動跑、有差異就告警,你才能在「使用者發現」之前先發現問題。

金額用整數

跟撮合引擎一樣,金流的金額也是用最小單位的整數來存(例如台幣的「分」,或直接用 decimal 型別),絕對不要用 float。對帳時一分錢的誤差都是大事。

小結

金流系統的可靠性不是來自「正常流程寫得多漂亮」,而是來自你對異常流程的處理:

  • 冪等用唯一索引「先佔位」
  • Webhook 假設它會重送、晚到、亂序,且一定要驗簽
  • Webhook 之外要有主動查詢補狀態
  • 每日對帳是最後一道防線

做金流久了會養成一種職業病:看到任何外部呼叫,都會先問「如果這裡斷掉、重送、回應遺失,會怎樣?」這個習慣後來讓我寫任何分散式系統都受用。

#金流#冪等#對帳#Webhook#後端

相關文章