電商下單背後的資金流:代賣家收款、再匯款給賣家。難點不在吞吐量,而在正確處理每一筆交易——失敗、重試、重複扣款與跨系統一致性。
📋 Requirements
- 功能:收款 (pay-in) 代賣家向顧客收錢;付款 (pay-out) 把錢匯給賣家。
- 非功能:可靠性與容錯(失敗付款要謹慎處理)、對帳 (reconciliation) 驗證內外部系統一致。
- 規模:每天 100 萬筆 ≈ 10 TPS,數字不大——重點是正確,不是高吞吐。
- 不自存卡號:敏感卡片資訊交由第三方 PSP 處理(PCI DSS 合規)。
🧠 Core Ideas
TIP
支付流程串起 payment service(協調+風險檢查)、payment executor(執行單筆 payment order)、PSP(實際搬錢)。核心是保證每筆訂單恰好一次 (exactly-once) 執行,並用對帳當最後一道防線。
金額欄位用 string 而非 double,避免序列化的捨入誤差;只在顯示或計算時才解析成數字。
⚖️ Reference Design
收款流程 (Pay-in)
- Payment service 收下支付事件、先做風險檢查(AML/CFT),存 DB,再為每筆 payment order 呼叫 executor。
- Payment executor 透過 PSP 執行單筆訂單;PSP 把錢從買家卡片移到電商銀行帳戶。
- 成功後依序更新 wallet(賣家餘額)與 ledger(帳本)。
- 帳本採複式記帳 (double-entry):每筆交易同額借記一方、貸記另一方,總和為 0,提供端對端可追溯性。
PSP 整合與託管頁面
- 多數公司用 PSP 託管支付頁面 (hosted page / iframe / SDK),敏感卡資訊直接進 PSP,不經過自家系統。
- 註冊時送一個 UUID (nonce,通常即 payment order ID) 確保「恰好一次」註冊;PSP 回傳 token 存 DB。
- 付款完成後前端導向 redirect URL,PSP 另以 webhook 非同步通知支付狀態。
失敗處理與內部溝通
- 支付狀態存 append-only 表,任何階段都能判斷當前狀態、決定重試或退款。
- 可重試錯誤進重試佇列;反覆失敗落入死信佇列待調查。
- 內部服務偏好非同步溝通:單一接收者用共享訊息佇列,多接收者(通知、分析、帳單)用 Kafka——以一致性換取擴展性與失敗韌性。
🔑 Takeaways
- 恰好一次 = at-least-once + at-most-once:用重試(建議指數退避、附
Retry-After)達成至少一次;用冪等達成至多一次。 - 冪等鍵(客戶端產生的 UUID,電商常用購物車/checkout ID)防重複扣款;可用 DB 唯一鍵約束實作:重複插入失敗即代表已處理,回傳前次狀態。
- 對帳是最後一道防線:每晚解析 PSP 結算檔比對帳本;不一致依可否分類/可否自動化分三類處理。
- 一致性:內部靠 exactly-once(見分散式交易),對外靠冪等+對帳(別假設外部永遠正確);複製延遲可只讀寫主庫,或用 Raft/Paxos 類共識資料庫。
No notes yet — jot your takeaways or Q&A here.