callback 那篇講了一個帳差事故:重複點、想上 queue 但 log 沒規劃好、最後帳目對不起來不得不人工處理。這篇是那個故事的下集——那次事故之後,我才知道一定要做對帳。
對帳:金流的最後一道防線
先講一個誠實的:對帳系統(reconciliation)不是我一開始就規劃的,是被帳差咬過才知道要做的。
那次事故讓我學到一件事——你不能相信任何單一系統的帳是對的。你的系統說收了這麼多、金流商說撥了那麼多、發票系統開了幾張,這三邊在正常情況下應該一致,但只要中間任何一個環節出過事(callback 掉了、冪等有漏、金額被改),它們就會悄悄地對不起來。而且帳差不會自己跳出來喊「我這裡錯了」——它就是靜靜地差在那,直到有人月底結算才發現。
對帳就是那個「有人」的系統化版本:定期把你的帳和金流商的帳逐筆比對,把差異揪出來。
對帳要比對什麼:四種差異
真正做起來,對帳不是「總額對得上就好」。逐筆比對會冒出四種差異,每一種意義不同:
- 金額不符:同一筆,你記的金額和金流商記的不一樣——最可疑,可能是計算錯或被竄改。
- 狀態不符:你以為成功、金流商說失敗(或反過來)——通常是 callback 漏接。
- 內部遺漏:金流商有這筆、你系統沒有——callback 根本沒進來,或進來了沒處理成功(就是那個「回成功留 FAILED」的下場)。
- 外部遺漏:你系統有、金流商那邊查無——更少見,但代表你可能記了一筆根本沒發生的交易。
這四種差異一標出來,你就知道「差在哪、往哪查」。而對帳的資料來源,除了逐筆去金流商查,還要解析他們的對帳檔(CSV)——裡面才有手續費、實際撥款金額這些「你系統裡沒有、但影響最終入帳」的欄位。
手續費和實際撥款金額,是對帳最容易被忽略的一塊:金流商撥給你的錢,不等於訂單金額——中間扣了手續費。如果你的對帳只比「訂單金額」,永遠會差那筆手續費,然後你以為系統出錯,其實是沒把手續費算進去。
部分退款:加上促銷之後的逆向惡夢
退款單獨看不難:客人要退錢,你打金流商的退款 API,把錢退回去。難的是部分退款,而且是部分退款遇上促銷的時候。
先講部分退款本身的邊界:一張訂單可以退很多次(退其中一件、再退另一件),所以你要校驗「累計已退金額不能超過原付款金額」——退到全額才把付款標成 REFUNDED,退一半就是 PARTIAL_REFUNDED。
真正會出問題的,是促銷的逆向計算。想像這個場景:一張訂單買三件、用了「滿千折百」的折扣、還折抵了點數、達到免運門檻。現在客人要退其中一件——這一件該退多少錢?
不是「這件的原價」。因為那張「滿千折百」是攤在三件上的,退一件要按比例攤回那個折扣;點數折抵也要按比例退還;更麻煩的是——退了這一件之後,剩下兩件可能就跌破免運門檻了,那原本免掉的運費要不要補收?
促銷是「正向」疊加上去的(折扣、點數、免運一層層加),退款是要把它「逆向」拆解回來。正向好加、逆向難拆——這就是為什麼部分退款 + 促銷是電商帳務最容易算錯的地方。算錯的下場,又是帳差,又繞回對帳來兜底。
收:退款對帳的三個要點
- 對帳是最後防線:不信任任何單一系統的帳,定期逐筆比對,四種差異各有各的病因。手續費/撥款金額別漏。
- 部分退款要校驗累計:一張單可退多次,累計不超過原額,退滿才標 REFUNDED。
- 促銷逆向最容易錯:折扣/點數要按比例攤回、退貨後跌破免運門檻要不要補運費——正向好加、逆向難拆。
接下來往哪走
- 付款 callback(帳差事故的上集) — 對帳為什麼是被逼出來的,故事的前半在這
- 促銷疊加規則 — 促銷「正向怎麼疊」,才懂退款「逆向怎麼拆」
- 對帳作為後端 pattern — 對帳這個思維抽出電商、當通用的跨系統 drift detection