callback 那篇講了一個帳差事故:重複點、想上 queue 但 log 沒規劃好、最後帳目對不起來不得不人工處理。這篇是那個故事的下集——那次事故之後,我才知道一定要做對帳。

對帳:金流的最後一道防線

先講一個誠實的:對帳系統(reconciliation)不是我一開始就規劃的,是被帳差咬過才知道要做的。

那次事故讓我學到一件事——你不能相信任何單一系統的帳是對的。你的系統說收了這麼多、金流商說撥了那麼多、發票系統開了幾張,這三邊在正常情況下應該一致,但只要中間任何一個環節出過事(callback 掉了、冪等有漏、金額被改),它們就會悄悄地對不起來。而且帳差不會自己跳出來喊「我這裡錯了」——它就是靜靜地差在那,直到有人月底結算才發現。

對帳就是那個「有人」的系統化版本:定期把你的帳和金流商的帳逐筆比對,把差異揪出來。

對帳要比對什麼:四種差異

真正做起來,對帳不是「總額對得上就好」。逐筆比對會冒出四種差異,每一種意義不同:

  1. 金額不符:同一筆,你記的金額和金流商記的不一樣——最可疑,可能是計算錯或被竄改。
  2. 狀態不符:你以為成功、金流商說失敗(或反過來)——通常是 callback 漏接。
  3. 內部遺漏:金流商有這筆、你系統沒有——callback 根本沒進來,或進來了沒處理成功(就是那個「回成功留 FAILED」的下場)。
  4. 外部遺漏:你系統有、金流商那邊查無——更少見,但代表你可能記了一筆根本沒發生的交易。

這四種差異一標出來,你就知道「差在哪、往哪查」。而對帳的資料來源,除了逐筆去金流商查,還要解析他們的對帳檔(CSV)——裡面才有手續費、實際撥款金額這些「你系統裡沒有、但影響最終入帳」的欄位。

手續費和實際撥款金額,是對帳最容易被忽略的一塊:金流商撥給你的錢,不等於訂單金額——中間扣了手續費。如果你的對帳只比「訂單金額」,永遠會差那筆手續費,然後你以為系統出錯,其實是沒把手續費算進去。

部分退款:加上促銷之後的逆向惡夢

退款單獨看不難:客人要退錢,你打金流商的退款 API,把錢退回去。難的是部分退款,而且是部分退款遇上促銷的時候。

先講部分退款本身的邊界:一張訂單可以退很多次(退其中一件、再退另一件),所以你要校驗「累計已退金額不能超過原付款金額」——退到全額才把付款標成 REFUNDED,退一半就是 PARTIAL_REFUNDED

真正會出問題的,是促銷的逆向計算。想像這個場景:一張訂單買三件、用了「滿千折百」的折扣、還折抵了點數、達到免運門檻。現在客人要退其中一件——這一件該退多少錢?

不是「這件的原價」。因為那張「滿千折百」是攤在三件上的,退一件要按比例攤回那個折扣;點數折抵也要按比例退還;更麻煩的是——退了這一件之後,剩下兩件可能就跌破免運門檻了,那原本免掉的運費要不要補收?

促銷是「正向」疊加上去的(折扣、點數、免運一層層加),退款是要把它「逆向」拆解回來。正向好加、逆向難拆——這就是為什麼部分退款 + 促銷是電商帳務最容易算錯的地方。算錯的下場,又是帳差,又繞回對帳來兜底。

收:退款對帳的三個要點

  1. 對帳是最後防線:不信任任何單一系統的帳,定期逐筆比對,四種差異各有各的病因。手續費/撥款金額別漏。
  2. 部分退款要校驗累計:一張單可退多次,累計不超過原額,退滿才標 REFUNDED。
  3. 促銷逆向最容易錯:折扣/點數要按比例攤回、退貨後跌破免運門檻要不要補運費——正向好加、逆向難拆。

接下來往哪走