你以為防線夠了,其實少了最後一道
做金流串接,該做的你都做了:callback 有驗簽(確認通知真的來自金流商)、有冪等(同一筆通知重複送不會入帳兩次)、失敗有重試。看起來滴水不漏。
但這些防的全是「單筆流程走歪」。還有一整類問題它們碰不到:
- 金流商那邊付款成功了,callback 卻一次都沒送到(網路斷、對方系統故障)——你的系統根本不知道有這筆錢
- 你收到通知標了已付款,金流商那邊事後取消或調帳了——你記的是舊帳
- 金額被中途改掉、或你自己的 bug 記錯數字——兩邊各記各的,誰也不知道
共同點:每一邊系統各自看自己都「沒有錯」,錯的是兩本帳對不起來。單筆防線抓不到「帳本級」的分歧——這就是對帳(reconciliation)存在的理由:定期把兩邊帳本攤開來逐筆比對。
對帳在比什麼:一張表講完
我自己的電商後端有一個獨立的對帳模組,做法是每天一批:把金流商的交易明細抓下來,跟自己資料庫的付款紀錄逐筆對。比對結果只會有四種差異,值得背起來:
| 差異類型 | 意思 | 典型原因 |
|---|---|---|
| 金額不符 | 兩邊都有這筆,但錢不一樣 | 手續費沒算、部分退款沒同步、bug |
| 狀態不符 | 兩邊都有,但一邊說已付、一邊說沒付 | callback 掉了、事後調帳 |
| 內部遺漏 | 金流商有、我沒有 | callback 全沒送到(最危險:錢收了你不知道) |
| 外部遺漏 | 我有、金流商沒有 | 通常是我這邊的假資料或測試單混進來 |
抓資料的管道通常有兩種,我的系統兩種都有:逐筆查詢 API(拿單號去問金流商「這筆現在什麼狀態」)和對帳檔(金流商每天產一份 CSV,裡面還會有你系統裡沒有的資訊——實收手續費、實際撥款金額)。對帳檔那兩個欄位很重要:訂單收 1000 不等於你拿到 1000,手續費扣完的撥款金額只有對帳檔會告訴你。
三個設計細節(從實作裡學到的)
- 每天一批、一批唯一。對帳批次用「日期+金流商」當唯一鍵,跑過就不能重複建——不然重跑一次就多一堆重複差異單。對帳系統自己也要冪等,很諷刺但很真實。
- 差異不是拿來自動修的,是拿來開單的。抓到不一致,系統該做的是產生一筆「待人工處理」的差異紀錄,而不是自動改帳——因為你不知道錯的是哪一邊。自動「修正」等於用猜的蓋掉證據。
- 對帳要有自己的排程。我的系統目前只有手動觸發按鈕,沒有每日自動跑——這是個誠實的洞:對帳做一半,等於「有保險但要記得自己去申請理賠」。(已列待補。)
把視野拉開:對帳是一個通用 pattern
雖然例子是金流,但「兩個系統各記各的帳,定期核對」這個 pattern 到處適用:
- 你的庫存數字 vs 倉庫系統(WMS)的庫存數字
- 你的訂閱狀態 vs Apple/Google 的訂閱狀態
- 主資料庫 vs 搬去搜尋引擎/快取的副本(資料漂移檢測)
- 微服務拆開後,A 服務的訂單數 vs B 服務的出貨數
判斷要不要做對帳的準則:只要有兩個系統各自持有「同一件事」的紀錄、中間靠非同步訊息同步,那些訊息就有掉的一天——對不起來只是時間問題。 事件驅動架構講「最終一致性」,對帳就是那個「最終」的執行者:平常靠事件同步,對帳兜底把漏網的抓回來。
相關
- callback 為什麼會掉、冪等防什麼 → 21 Consumer 冪等
- 訊息保證送出的另一半拼圖 → 20 Outbox pattern
- 事件跑掉了怎麼追 → 24 端到端追蹤