Hero Image
- Mark

支付系統設計:訂單、支付、對帳的一致性與冪等

支付系統設計:訂單、支付、對帳的一致性與冪等

這篇文章是看了 s09g 在 YouTube 上發的「system design — payment system」(19:47)之後的整理筆記。

我必須先承認一件事:我在寫金流的時候,第一次看這種題目也是先翻 Stripe 文件、把 SDK 串起來、看到 payment_intent.succeeded 就覺得「啊我會接金流了」。但 s09g 影片一開始就潑了一盆冷水——你沒有金融牌照,你拿什麼去划帳?

金流系統設計的本質,不是「怎麼呼叫 PSP」,而是「怎麼把一筆支付的事實記下來,並且保證它永遠對」。這才是後端工程師真正要扛的事。

這篇文會順著影片的章節走:為什麼要透過 PSP、整合的 4 種方案、資料模型怎麼切、冪等怎麼做、狀態機怎麼設計、對帳、event sourcing、風控。每一段我都會加一點自己的觀察(也就是後端工程師半夜被叫醒時會想知道的那種)。

為什麼不是「呼叫 PSP 就好」

很多人以為支付系統就是「前端收信用卡 → 打到 Stripe → 收 webhook → 訂單完成」。但如果你真的畫出這個流程圖,會發現你只畫了 1/10 的參與者。

完整的支付參與者至少有:持卡人(customer)、發卡行(issuer bank)、卡組織(Visa、Mastercard、JCB 這些 card network)、收單行(acquirer bank)、商戶(merchant)。光是把這些角色對齊就已經頭昏了——使用者可能拿著 30 家不同銀行的卡,商戶也對接 5 家收單行,如果你要一家一家去接,工作量跟合規負擔會讓你直接關掉筆電。

所以實務上商戶會找一個 PSP(Payment Service Platform),像 Stripe、Adyen、綠界、藍新。PSP 給你一組乾淨的 API,你呼叫就好,剩下的路由、轉發、跨行清算,他們處理。當然,他們會抽 0.5%–1% 的手續費,這是合理的代價。

系統邊界與責任分離

影片裡 s09g 強調一件事:你的瓶頸不在你自己這裡,在外部 PSP。因為你只是呼叫 API,吞吐量被外部限制住,所以 scalability 暫時不用列為非功能性需求。

這是一個很重要的思維翻轉——大多數後端面試題會要求你談 QPS、談 sharding、談 cache,但支付題目反過來:你先別想 scale,先想合規、可靠性、一致性、正確性。後面三個是金融業務的命根子。

責任分離長這樣:

  • 商戶後端:發起支付請求、記帳、通知使用者
  • PSP:路由、轉發、風控初篩、與銀行對接
  • 銀行 / 卡組織:最終的扣款與清算

商戶負責「事實記錄」,PSP 負責「通路執行」,銀行負責「資金移動」。這個三段式的分工,後面所有的設計都是圍繞它展開的。

支付整合的 4 種方案

影片列了 4 種把使用者付費資料送到 PSP 的方式,我用台灣的脈絡重述並補充:

方案 1:Server-to-server(自建 PSP)

前端把卡號、CVV、到期日送到商戶後端,後端再轉給銀行或卡組織。

這條路等於你自己開一家 PSP。你需要拿到支付機構牌照、需要通過 PCI-DSS 完整稽核、需要保存敏感的 PAN(primary account number)。只有大型企業才會這樣做(像京東、淘寶早期、AWS 自己接的那種等級)。好處是通過 PCI 之後自主權大、能自訂分期分帳、可以省通道費。但初期投入與合規成本太高,一般新創絕對不要走這條路。

方案 2:JavaScript SDK + iframe + 客戶端加密(CSE)

這是 Stripe.js、TapPay、綠界 SDK 的標準做法。PSP 提供 JS library,商戶嵌入到自己的網站,付費時 SDK 把卡號直接送到 PSP(甚至不需要經過商戶伺服器),回傳一個不可逆的 token。商戶拿到 token 再去打 PSP 的扣款 API,永遠不接觸原始卡號。

但 SDK 仍然是跑在商戶頁面上的 JS,所以有兩個風險:

  1. XSS 注入:攻擊者在你的頁面注入腳本,攔截使用者輸入
  2. MageCart 攻擊:供應鏈攻擊,在第三方 SDK 裡偷卡號

所以前端必須做 CSP(Content Security Policy)、SRI(Sub-resource Integrity)、限制 script 來源、驗 hash。

進一步,現代 SDK 會在底層塞一個 iframe(hosted field),讓卡號輸入框實際上是 PSP 網域的子頁面,再用 PSP 的公鑰做 CSE(client-side encryption) 把卡號加密起來 post 出去。這樣即使商戶頁面被攻破,攻擊者也拿不到明文卡號。

方案 3:Hosted Redirect(託管頁面)

最簡單的方案:使用者點「結帳」→ 302 跳到 PayPal / Stripe Checkout / 綠界託管頁 → 付完跳回來。

PCI 負擔最低、運維最簡單、PSP 故障時全靠他們兜底。自由度也最低——UI 是別人的、品牌是別人的、轉換率會掉(多一次跳出)。台灣很多小店早期就是這樣接藍新、TapPay 的。

取捨總結

s09g 影片最後給了一句非常到位的結論:「可客製化越高,成本越高——不僅是技術成本,還有 PCI 責任和安全管控成本。」台灣的中小型電商我會建議從 方案 2 + iframe hosted field 開始,視需要再升到方案 1。千萬不要為了「接觸卡號比較帥」就直接走 server-to-server,那是把團隊推入合規深坑。

資料模型:三張表把責任切開

影片提到一個非常關鍵的設計:業務狀態、交易狀態、資金狀態,三種狀態應該放三張表。

原因很直接——支付資料是合規驅動,不可變更、必須可審計。如果你把「訂單狀態」跟「扣款狀態」混在同一張表,後端寫程式時會很容易不小心 UPDATE 錯欄位,導致審計人員追蹤時資料前後矛盾。

理論上至少要這三張表:

order table(業務狀態)

  • 訂單 ID、商品、金額(業務幣別)、使用者、狀態(待付款 / 已付款 / 已取消 / 已退款)

payment table(交易狀態)

  • charge_id(PSP 端 ID)
  • order_id
  • payment_method(信用卡 / ATM / LINE Pay)
  • psp(Stripe / 綠界 / 藍新)
  • status(state machine:created / start / processing / success / failed)
  • amount、currency
  • created_at、updated_at
  • idempotency_key ← 重點

ledger(資金狀態,append-only)

  • 帳戶、借方金額、貸方金額、交易 ID、時間戳
  • 永遠 append,不 UPDATE 不 DELETE

三張表讀寫模式完全不同:order table 是 CRUD;payment table 是「以 idempotency_key 為錨點的有限狀態機」;ledger 是純 append-only 的事件流。

ledger 為什麼是 append-only

金融審計的硬要求:「這筆帳寫入後就不能改」。如果你允許 UPDATE,理論上就有人可以掩蓋錯誤。所以 ledger 必須是 append-only。在 PostgreSQL 上你可以用觸發器或權限設定強制,在雲端可以用專用服務——AWS 有 QLDB(Quantum Ledger Database),阿里巴巴開源過 LedgerDB,都提供 SQL 接口但底層專為不可變特性優化。

雙式記帳

影片提到一個會計學名詞:double-entry bookkeeping(雙式記帳)。每筆交易至少影響兩個帳號、產生一組「金額相等、方向相反」的紀錄。借方 100、貸方 100,加總永遠是 0。這是會計幾百年來的鐵律——任何單邊記帳最終都會出錯。

實務上你可以在 ledger table 用兩筆 row 來表達一筆交易:

-- 持卡人帳戶 -100
INSERT INTO ledger (account_id, amount, txn_id, direction) VALUES ('user:42', 100, 'txn:abc', 'debit');
-- 商戶帳戶 +100
INSERT INTO ledger (account_id, amount, txn_id, direction) VALUES ('merchant:1', 100, 'txn:abc', 'credit');

只要加總為 0,內部就一定自洽。

冪等性:exactly-once 的真正意思

金流最怕的事:使用者被扣兩次錢。

這個事故怎麼發生的?想像一下:使用者在結帳頁面狂點按鈕(手機網路 lag、按鈕沒 disable、前端重試邏輯有 bug),結果伺服器收到 5 個相同的請求,每個都去 PSP 扣款一次,使用者崩潰、客服崩潰、法務也崩潰。

要避免這個,必須做到 exactly-once——同一筆邏輯交易,不管被打幾次,只會被處理一次。

idempotency key 的生命週期

s09g 的影片把這個流程講得很清楚:

  1. 使用者打開 checkout 頁面
  2. 伺服器生成一把 idempotency_key(例如 UUID),回傳給前端
  3. 前端把這個 key 放在後續所有 payment 請求裡
  4. 伺服器收到請求時,先去 payment table 用 key 查重
  5. 如果已經有 record,就回傳上次結果;如果沒有,就開始處理

這個 key 是 exactly-once 的核心。它讓「重試」變成安全操作,因為重試會拿到跟第一次相同的結果。

內部一致性 vs 外部一致性

影片把這兩個拆得很乾淨:

  • 內部一致性:你的系統內部(例如 order 跟 payment 的狀態對齊)—— 用關聯式資料庫的交易就能搞定
  • 外部一致性:你的系統跟 PSP 之間的狀態對齊—— 必須靠 idempotency

因為你不能預期 PSP 會乖乖地「只執行一次」。網路會抖動、PSP 會重試、你的 callback 會丟失。所以你的 idempotency key 必須穿透到 PSP,跟他們說:「這個 key 我已經處理過了,請不要再扣一次」。Stripe 文件裡的 Idempotency-Key header 就是這個意思。

狀態機與非同步通訊

PSP 是非同步的。你送出一個扣款請求,PSP 會回你「我收到了」(200 OK),但實際處理結果可能要幾秒甚至幾分鐘才會回。所以 payment 的狀態機至少要有:

created → start → processing → success
                          ↓
                        failed

每個狀態轉換都要落 payment table,並且只能往「終態」前進,不能倒退。

retry 的四要素

當 PSP 沒回應時,你要重試。但不能無腦重試,必須有紀律:

  1. timeout:超過多久才重試
  2. exponential backoff:1s → 2s → 4s → 8s,每次翻倍
  3. jitter:每次加 ±15% 隨機抖動,避免雪崩——多個 client 同時重試時打散流量
  4. dead letter queue (DLQ):超過最大重試次數就丟進 DLQ,不要再撞牆

jitter 這件事 s09g 講得特別細:如果你只有 backoff 沒有 jitter,很多 client 會在同一時間點同步 retry,瞬間把 PSP 打死。jitter 是保護對方的設計,不是保護你自己。

callback + 輪詢

PSP 處理完會呼叫你給的 callback URL(webhook)。但 callback 會丟——網路會斷、你的 server 會重啟、防火牆會擋。所以 callback 不能當唯一的事實來源。

實務上兩條路並行:

  • Webhook:PSP 主動通知
  • Polling:超過 N 秒沒收到 callback,主動打 PSP 查詢

兩個都收到時,用 idempotency_key 去重,不會出錯。

狀態機跳變

還有一個討厭的 race condition:你的 server 已經 timeout 把狀態寫成 failed,結果 PSP 後來處理成功、callback 送過來說「其實是 success」。怎麼辦?

兩個解法:

  • 單調狀態機:進入 failed 後不再允許改成 success
  • 樂觀鎖 / 版本號:每次更新帶 version,UPDATE 時檢查 WHERE version = ?

最壞的情況,就是等日終對帳時由 settlement file 兜底。所以對帳才是金流的最終仲裁,不是 webhook、不是 polling。

對帳(Reconciliation)

為什麼還需要對帳?不是已經有 webhook + polling 了嗎?

因為 任何線上鏈路都可能遇到 bug。PSP 可能漏發 webhook、你的 server 可能丟訊息、網路可能 silent drop。一筆交易可能被記成 success 但其實銀行沒收到錢;也可能被記成 failed 但其實已經扣款。

這時候就要靠日終對帳:銀行(或 PSP)每天早上會送一份 settlement file 給你,列出昨天所有交易的最終狀態。你拿這份檔案跟自己的 payment table 比對——

  • 在自己這邊成功、settlement 也成功:沒事
  • 在自己這邊失敗、settlement 卻成功:使用者被扣款但訂單沒成立 → 自動退款或人工補單
  • 在自己這邊成功、settlement 卻失敗:可能是 PSP bug → 人工審核
  • 完全沒出現在 settlement:可能是交易被 PSP 風控擋下但忘了通知你

對帳出來的差異要觸發告警、自動補償、或轉人工審核。這是一條獨立的 batch job,不能跟線上即時流程混在一起,否則你會把 production 弄掛。

Event Sourcing 與風控

s09g 影片最後兩段是「加分題」:如果你還有時間,可以秀一下 event sourcing 跟 risk engine。

為什麼互聯網公司用 event sourcing

Stripe、Coinbase、Square 都用過 event store(Kafka / Pulsar) 作為 source of truth:

  • 所有支付事件(payment.created / payment.captured / payment.refunded)都 append 到 event store
  • 用 projection / view 從事件流計算出查詢用的 view(加速前端查詢)
  • 任何糾錯都以「反向事件」或「補償事件」寫入,不會覆蓋歷史

這樣做的代價是系統複雜度高、開發與運維成本高、團隊要同時熟悉流系統與金融系統。但好處是:拿到金融正確性 + 互聯網可擴展性——可以重放事件、可以對帳時重新計算、可以接多個下游 consumer。

削峰填谷

如果把 MQ 放到最前面,Payment Service 變成 pure broadcaster,把每個事件投放到不同 DB:

  • 一份進 payment table(線上查詢用)
  • 一份進 ledger(金融記錄用)
  • 一份進下游分析系統(BI 用)

每次對帳時,可以在 MQ 前端做事件重放,重新計算所有 view。這對偶發的資料錯誤非常有救。

Risk Engine

風控放在 Payment Service 前面。影片列出 4 種等級:

  1. 規則引擎:黑名單、白名單、規則樹——最簡單、合規友善、可解釋
  2. 統計 / ML:LR、XGBoost 之類的反詐欺模型——反應慢、需要標註資料
  3. 無監督學習:解決冷啟動問題——會有誤報、需人工介入
  4. 圖模型 / GNN:捕捉複雜詐騙網路——維運成本高、開發難

台灣中小型電商我會建議從規則引擎 + 統計模型起步,等到交易量到一定規模(單日 10 萬筆以上)再考慮上 GNN。風控模型最大的坑是誤報成本——擋下太多正常訂單會直接傷害營收,所以一開始寧可寬鬆再調嚴,也不要一開始就過度嚴格。

結語:給金流工程師的一句話

把整支影片看完,我最大的感觸是:金流系統設計的難度不在於「技術棧很新」,而在於「你的每一行程式碼都在碰真實的金錢」。

當你寫一般的後端服務時,bug 通常只是「使用者看到錯誤畫面」。當你寫支付系統時,bug 可能是「使用者被扣款但沒拿到商品」、「錢被轉到錯的帳戶」、「帳務報表對不上」——每一個都是會上新聞的等級。

所以金流工程師的核心素養,不是會串 SDK,而是對 合規邊界、資金一致性、非同步協作、可審計性 的直覺。

s09g 這支影片是個很好的起點,但他講得很保守(給面試用)。實際上線你還要對 PCI-DSS、PSP SLA、當地法規(台灣還有洗錢防制法、金管會的電子支付機構管理條例)。如果你是金流工程師,下一個值得挑戰自己的題目是:怎麼把對帳與金流觀測做成 dashboard,讓 ops 半夜被叫醒時不用翻 SQL。

金流這條路很硬,但相對地也比較少人走得深。值得走。

八、架構師怎麼看:金流系統在 2026 年的業界實作

影片從 PSP 整合、ledger 雙式記帳、冪等到 event sourcing 都講得很系統。但我有 4 個地方會做不同選擇,加上幾個影片沒講但生產上一定會遇到的細節。

1. 「不要自己做 PSP」的 trade-off 反轉

影片建議一律透過 PSP,這在 2018 年是對的。但 2024 之後 Stripe / Adyen 推出了「platform-of-platform」模式:

  • Stripe Connect:你可以「成為下一層 PSP」,幫你平台上的小商家收款,分潤自動處理
  • Stripe Issuing:你可以發行實體卡,繞過傳統發卡行流程
  • Stripe Treasury:你可以開銀行帳戶給你的使用者(需美國 MSB 牌照)

這代表什麼?「自己接銀行」這條路徑的門檻其實降了。原本需要 PCI-DSS Level 1 + 完整稽核(數百萬 USD + 6-12 個月),現在透過 Stripe Issuing 可以在 2-4 週上線。

但這也有代價:

  • Stripe 抽成更高(Issuing 部分通常 0.2% + 0.2 USD per transaction)
  • 業務綁定:你發出去的卡只能在 Stripe 生態用
  • 合規仍存在:Stripe Treasury 還是要 MSB 牌照,只是流程簡化

架構師判斷:

  • 一般電商 / SaaS:用 PSP 就好(影片建議)
  • 平台型業務(marketplace、creator economy):評估 Stripe Connect
  • 金融科技 / 加密貨幣:直接走 Issuing + Treasury,不要跟傳統銀行打交道

2. QLDB 不是答案,PostgreSQL 觸發器就夠

影片提到 AWS QLDB / LedgerDB 是「不可變 ledger」的解法。我有不同看法:

QLDB 太貴:

  • AWS QLDB pricing:$0.014 per 1K ledger writes,加上 storage + read cost
  • 對一個中型支付系統(單月 1M 筆交易)來說,月費 $500-1500 USD
  • 而且你要 lock-in 到 AWS 帳號

PostgreSQL + 觸發器 + revoke 權限就能達到同樣效果:

-- 強制 append-only
REVOKE UPDATE, DELETE ON ledger FROM PUBLIC;

-- 觸發器防止 truncate
CREATE OR REPLACE FUNCTION prevent_ledger_modification()
RETURNS TRIGGER AS $$
BEGIN
    RAISE EXCEPTION 'Ledger is append-only';
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER ledger_immutable
BEFORE UPDATE OR DELETE OR TRUNCATE ON ledger
FOR EACH STATEMENT EXECUTE FUNCTION prevent_ledger_modification();

-- 定期 hash chain 驗證(防止有人繞過觸發器直接改檔案)
ALTER TABLE ledger ADD COLUMN row_hash CHAR(64);
ALTER TABLE ledger ADD COLUMN prev_hash CHAR(64);

業界現況:支付量 < 10M/月 用 PostgreSQL 觸發器就夠,> 100M/月 才需要考慮 QLDB 或自研的 event store。

我自己做過的支付系統,PostgreSQL 撐到單月 5M 筆完全沒問題。Partitioning + 觸發器的組合比 QLDB 便宜 90%。

3. Idempotency Key 的 window:24 小時是怎麼來的

影片沒講這個細節,但這是冪等設計的核心:

Stripe 內部 Idempotency Key 的保留時間是 24 小時,理由是:

  • 業務場景:使用者刷卡後 24 小時內 retry 是合理的(網路問題、手機重啟、卡機問題)
  • 24 小時後:retry 已經是新的 payment intent,使用者已經重新評估訂單
  • 儲存成本:保留 24 小時的 key × 1KB × 100M 交易/月 = 100GB,可以接受

架構師實作建議(沿用我之前 POS 那篇提的 schema):

CREATE TABLE idempotency_keys (
    key VARCHAR(255) PRIMARY KEY,
    payment_id UUID NOT NULL,
    status VARCHAR(20),  -- pending / completed / failed
    response JSONB,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at TIMESTAMPTZ NOT NULL  -- created_at + 24h
);
CREATE INDEX idx_idempotency_expires ON idempotency_keys(expires_at);

-- 背景 job 每小時跑一次
DELETE FROM idempotency_keys WHERE expires_at < NOW();

Key 是要 expired 的,永久保留是浪費。但 24 小時是 Stripe 的內部數字,你的系統可以用 1 小時也可以用 7 天——重點是要有 expiry。

4. Event Sourcing 的真實成本:不是技術是組織

影片打槍「不要為了潮做 ES」,這個觀點在 2026 年其實要看團隊規模:

  • 小團隊(< 5 人):ES 維運成本太高,Saga 就好
  • 中型團隊(5-20 人):ES 開始划算,特別是有 BI / 風控 / 對帳三個需求時
  • 大型團隊(> 20 人):ES 是標配,分工可以拆 stream team / core team / BI team

但 ES 真正的成本不是技術,是組織:

  • 需要 stream-first 的工程師文化:新人不熟 Kafka、debug 事件流比 debug SQL 難 5 倍
  • 需要 schema 治理:event schema 一改,所有下游 consumer 都要更新
  • 需要 replay 工具:偶發事件錯誤要能 replay 才能修

架構師判斷邏輯:不是「Stripe 所以 ES」,也不是「小公司所以不用 ES」,而是「我們的工程師有沒有 stream-first 思維 + 我們的事件 schema governance 做得好不好」。前者靠培養,後者靠工具(Confluent Schema Registry / Apicurio)。

5. 對帳(Reconciliation)的現代化

影片講對帳是「日終 batch job 跑 settlement file 比對」,這個流程沒變,但實作方式可以現代化:

  • 過去:shell script + cron + grep diff + email alert
  • 現在:streaming reconciliation(Kafka Streams / Apache Flink)即時對帳,差異 < 1 分鐘就 alert

架構師現在會設計:

[PSP webhook] → [Kafka] → [streaming reconciliation]
                              ↓
                         [對得上] → payment.status = confirmed
                              ↓
                         [對不上] → Kafka topic: reconciliation.diff
                              ↓
                         [alert + 自動補償 / 人工 review queue]

好處:差異不用等到隔天才發現,可能 1 分鐘內就抓到。

但這條路徑只有大流量(> 100K 筆/天)才划算。小流量用 shell script + cron 就好,過度工程反而是浪費。

6. Trade-off 反轉:Jitter 不一定要 Full Jitter

影片講 jitter 但沒給業界標準做法。AWS Architecture Blog 在 2015 年提出 "Exponential Backoff and Jitter" 論文後,業界有 3 種主流做法:

# 1. Full Jitter(AWS 推薦)
sleep_time = random.uniform(0, min(cap, base * 2 ** attempt))

# 2. Equal Jitter
sleep_time = (base * 2 ** attempt) / 2 + random.uniform(0, (base * 2 ** attempt) / 2)

# 3. Decorrelated Jitter
sleep_time = min(cap, random.uniform(base, prev_sleep_time * 3))

哪個最好?這要看場景:

  • Full Jitter:平均延遲最低,但 variance 最高。適合「我希望 retry 越快完成越好」
  • Equal Jitter:保留最低保證,適合「我有 deadline 不能太晚」
  • Decorrelated Jitter:最均勻,但有極端值風險

業界實際:AWS SDK / Google Cloud SDK / Stripe SDK 全部用 Full Jitter——因為它在大多數情況下表現最好。Decorrelated Jitter 看起來 fancy,但 Netflix 自己後來也改回 Full Jitter。

架構師預設用 Full Jitter,除非有特殊 deadline 需求。

7. 風控模型的權重:規則 60% + ML 40%

影片列了 4 種風控等級,我的實務配比是:

  • 規則引擎:60% 攔截率 — 黑名單、白名單、地區限制、金額上限、velocity check
  • 統計 / ML 模型:30% 攔截率 — XGBoost / LightGBM 跑歷史 fraud pattern
  • 無監督學習:5% 攔截率 — 冷啟動場景
  • GNN:5% 攔截率 — 高價值帳號、VIP 帳號保護

理由:誤報成本遠比漏報成本高。一個正常使用者被擋下,他可能就不再來了;一個 fraud 漏報,最多損失一筆訂單金額。所以 ML 模型的 threshold 要偏嚴格,寧可讓 fraud 多過也不要誤殺正常使用者**。

8. 一個面試官會問的反問

如果面試官聽完你講 ledger 雙式記帳反問:「如果你的 ledger 寫到一半伺服器掛了,transactions table 已經 UPDATE 成 success,但 ledger 沒寫到,怎麼辦?」

正確答案不是「用分散式交易」(太重),也不是「這不會發生」(太天真),而是:

「Two-phase write with reconciliation:

  1. 先寫 ledger(status='pending')— 保證 ledger 一定有 record
  2. 然後 UPDATE transactions.status = 'success' — 業務狀態才推進
  3. 背景 worker 每分鐘掃 ledger WHERE status='pending' AND created_at < NOW() - 5min
  4. 對每筆 pending:查 transactions 表,如果已經 success 就 UPDATE ledger 為 success;如果還是 processing 就 alert + 人工介入

這就是為什麼對帳是金流的最終仲裁——不是 webhook、不是 polling、不是 idempotency,是 reconciliation。」

這個答案把「reconciliation 是仲裁機制」講出來了——面試官想聽的是這個設計哲學,不是「我會用分散式交易」這種沒經驗的答案。


架構師 takeaway:金流系統設計的難度不是「哪個技術比較潮」,而是「每個設計決定都有真實的金錢後果」。架構師的責任是「在合規與成本之間找到對的甜蜜點」——大部分團隊的甜蜜點是 PSP + PostgreSQL ledger + Saga + 24h idempotency window,不是 Event Sourcing,也不是 QLDB。Event Sourcing 是給「已經有 stream 維運能力、且業務量夠大」的公司準備的,不是給「覺得很潮所以想試」的人準備的。

Other Related Posts:

POS 支付系統設計:三階段時序、Idempotency Key,以及和 Stripe 的差別

POS 支付系統設計:三階段時序、Idempotency Key,以及和 Stripe 的差別

這篇文章整理自 s09g 頻道的《[系統設計 Mock] POS payment service》系統設計 mock interview。整支影片只有十分鐘,但資訊密度很高——它講的不是「POS 是什麼」這種白話文,而是面試官會一直追問的設計細節:三階段時序、跨系統一致性、Idempotency Key 的物理意義。如果你是後端工程師要準備 system design interview,這幾個點一定要搞懂。


一、POS 不是「一個 API 收錢」——先選變體

影片一開頭就把題目切成三個變體:

  1. 全部在線變體(online-only):使...
17th Aug 2026 - Mark
建一個 Job Scheduler 之前要釐清的 11 個取捨點

建一個 Job Scheduler 之前要釐清的 11 個取捨點

任務調度系統(Task Scheduler / Job Scheduler)這種東西,看起來每家公司都有,用起來都差不多,但只要你認真去看一次原始碼,就會發現每個團隊的取捨都不一樣。

這篇文章不是要告訴你「任務調度是什麼」。網路上的科普文已經太多了。我要講的是:當我們真的坐下來要畫一張架構圖,每一個節點為什麼要這樣選。參考的影片是 s09g 的《system design — 任務調度系統》(約 29 分鐘),我把裡面 12 個章節的取捨整理成一條線,並加上我自己做後端這些年的看法。

我們的場景先講清楚:假設要替一個內部系統(10k 工程師規模)做一個任務調度平台。

...
17th Aug 2026 - Mark