這篇文章整理自 s09g 頻道的《[系統設計 Mock] POS payment service》系統設計 mock interview。整支影片只有十分鐘,但資訊密度很高——它講的不是「POS 是什麼」這種白話文,而是面試官會一直追問的設計細節:三階段時序、跨系統一致性、Idempotency Key 的物理意義。如果你是後端工程師要準備 system design interview,這幾個點一定要搞懂。
一、POS 不是「一個 API 收錢」——先選變體
影片一開頭就把題目切成三個變體:
- 全部在線變體(online-only):使用者從下單到付款到出貨,所有事都在一條 HTTP 鏈路上跑完。這是電商網站最常見的情境。
- 刷卡變體(card-present):今天的主角——實體店面、用戶拿信用卡刷 POS 終端。這個變體必須拆成線上跟線下兩段。
- P2P 變體:用戶對用戶的轉帳(像 PayPal 朋友轉帳、Venmo)。今天不展開。
為什麼要先選變體?因為變體決定架構。Online-only 變體你可以用同步 transaction 處理完;刷卡變體就沒辦法,因為你不知道用戶什麼時候會把卡拿開、什麼時候會按取消、銀行的清算又是另一個時段。
面試的時候,如果你沒先講「我們今天討論的是刷卡變體」,面試官會直接假設你是 online-only——這題就解掉了,沒什麼好設計的。
二、三階段時序:Authorize → Processing → Complete
這是整個設計的脊椎。三個階段不是「123 三步」,而是發生在三個完全不同的時段:
| 階段 | 時機 | 發生什麼 | 延遲特性 |
|---|---|---|---|
| Authorize | 用戶刷卡瞬間 | 終端直接 call 銀行,銀行 200ms 內回應卡是否有效 | 同步 |
| Processing | 當天避典後(通常是凌晨) | 系統把當天所有 authorize 打包成 batch file,丟給銀行做清算 | 批次、非同步 |
| Complete | 銀行清算完後 | 銀行回 statement file,商家做最終對帳 | 非同步,可能隔天 |
關鍵點:Authorize 在白天即時發生,但商家帳戶真正收到錢是 Complete 之後的事。影片裡面試官特別點出——很多面試者寫成「123 三步驟」就直接被挑戰,因為這三件事不是連續的,是跨時段的。
這也是為什麼「authorize 成功」不等於「款項到帳」——你早上看到刷卡成功,只是銀行說「這筆錢我先幫你鎖住」,真正的撥款要等晚上批次清算完。
實務上這個時序對商家很重要:你不能假設用戶刷完卡你就可以出貨——你要等到 Complete 階段確認款項真的進來。但對消費者體驗來說,Authorize 成功就會印收據、開發票,所以內部系統的狀態機至少要追蹤這三個狀態。
三、Idempotency Key:這是整個設計的核心
這是影片裡講得最細、也是最容易踩坑的一段。
內部 payment ID 絕對不能曝露
你的內部系統會有 payment_id,但這個 ID 絕對不能丟到 idempotency key 裡面,原因是:
- Idempotency Key 會跨系統流通(商家 → Bank processor → PSP → 銀行)
- 它是給外部系統去重用的(同一筆交易別 retry 兩次扣款)
- 如果你把內部 ID 放進去,等於把內部資料表結構曝露給外部
正確做法:用 hash 處理過再放進 idempotency key。例如 sha256(payment_id + internal_secret)。或者更乾脆,idempotency key 就是另一套獨立的命名空間。
Idempotency Key 對應的物理意義
這是面試官會追問的第二層:
「Idempotency Key 到底對應什麼?一次 payment?一次 order?一次刷卡?」
影片的答案是:對應用戶的一次支付意圖(payment intent)。
為什麼這很重要?因為現實世界裡:
- 一個 order 可以有多個 idempotency key:你和朋友去吃飯 split the bill,你刷一張卡、朋友刷一張卡。同一個 order 兩次刷卡 → 兩個 idempotency key。
- 同一個 intent 可以有多個 idempotency key:你刷卡後取消,然後再刷一次——同一個支付意圖(我要付這筆錢),但產生了兩次 idempotency key(第一次失敗、第二次成功)。
所以 idempotency key 不是 outer-level(整個 order 一次),也不是刷卡一次 level(每次刷卡機械動作一次)——它是支付意圖這個抽象層。
為什麼不要把兩個 ID 混用
常見錯誤:
idempotency_key = "{order_id}-{payment_id}" # ❌
這會爆露內部 ID,而且如果使用者退貨 + 重付,order_id 相同但 payment_id 不同,idempotency key 重複,系統會以為是同一筆——然後第二次扣款失敗。
四、跨系統一致性:為什麼一定塞 Message Queue
影片裡面試官點出了一個關鍵:不同系統之間的 TPS(throughput per second)是完全不同量級的。
- 你的 API 服務可以撐幾萬 TPS(單機上千,QPS 過萬)
- 銀行的清算 batch file 可能一天處理一次
- 銀行的 statement 回傳可能延遲數小時
如果你用同步 request 串起來,你的 API 會被銀行的 batch 拖死——使用者刷卡 200ms 內 authorize 成功,但你的內部處理流程被卡在「等銀行回 statement」上。
所以架構必須是:
[POS 終端]
↓ (同步,~200ms)
[Authorize API]
↓
[Transaction DB] ← 寫一筆 payment record
↓
[Message Queue] ← 非同步,塞進去就 return
↓
[Batch Worker] ← 慢慢消化,打包 batch file
↓
[Bank / PSP] ← 夜間清算
↓
[Statement Worker] ← 處理回傳
這個 Message Queue 不能省。如果你試圖用同步呼叫串起來,一定會在某個環節塞住。
訊息佇列選型考量
實務上會選:
- Kafka:高吞吐、持久化、可重放——如果你要處理 statement 回放、對帳,選 Kafka 沒錯
- RabbitMQ:傳統銀行整合常用,有 ack 機制、支援 transaction
- AWS SQS / GCP Pub/Sub:雲端服務,不用自己維運
POS 場景我個人偏好 Kafka——因為你可能要 replay 當天所有 transaction 來對帳,Kafka 的 retention model 適合。
五、跟 Stripe 級別的差別:不要為了潮而做 Event Sourcing
影片最後講了一個容易被忽略的點:大部分系統不是 Stripe,不要直接套 Stripe 的架構。
Stripe 的特殊地位
Stripe 同時是 Bank Provider + Payment Service Provider(PSP)——它直接連到銀行網絡,中間不需要再插一層 PSP。所以 Stripe 才可以:
- 同步完成 authorize + capture
- 即時告訴商家「款項已到」
- 提供完整的 webhook 事件流
但你不是 Stripe。你是 Stripe 的客戶(商家)。所以你的架構是:
[你的系統] → [Stripe/Adyen/Braintree] → [銀行網絡]
你拿到的 webhook 是 Stripe 已經幫你做完 capture 的事件,你沒辦法自己 capture。
Event Sourcing / CQRS 評價
有人會說「payment 應該用 Event Sourcing 啊,所有事件都記下來,可以 replay」——影片的面試官直接打槍:
「大部分系統不是 Stripe 級別,你做成 event sourcing 沒有太大收益。Event sourcing 適合已經建成 audit log 系統的公司——它本來就要處理任意時間點 replay。」
對一般 POS 系統來說:
- Saga 模式 + 補償交易就夠了:每個本地交易 + 失敗時的補償動作
- 不要把 payment 設計成 event sourcing 主架構:投資報酬率不對
- Audit log 該做但獨立做:寫一份完整的事件記錄,但查詢走傳統 SQL
這是一個務實的工程判斷——很多面試者會為了「看起來很專業」硬上 event sourcing,但面試官會追問「你為什麼這樣選?」,這時就要講得出 trade-off。
六、面試追問 checklist
整理一下影片中被追問的點,如果你要面 POS 系統設計,這些一定要準備:
- Authorize / Processing / Complete 三階段時序:有沒有講清楚發生在不同時段?不是「三步驟」是「三個時段」。
- Idempotency Key 的 scope:是 outer-level 還是刷卡一次 level?答:支付意圖層。
- 一個 order 多個 intent:split the bill、退款重付——有沒有想過這兩個 case?
- 內部 ID 不要曝露:你的 idempotency key 怎麼產生?hash 過嗎?
- Message Queue 為什麼必要:同步串起來會發生什麼?
- Credit Card vs Debit Card 要不要分:影片最後被追問這個——實務上會分,因為清算規則不同。
- 跟 Stripe 的差別:你是 Stripe 的客戶,不是 Stripe——架構不一樣。
結語
POS 系統設計的精髓不是 API 介面設計,而是時序和跨系統一致性。
記住三件事大概就能 cover 80% 的面試場景:
- 三階段時序:Authorize 即時、Processing 批次、Complete 非同步
- Idempotency Key:對應支付意圖、跨系統流通、絕不曝露內部 ID
- Message Queue:跨 TPS 系統之間必塞
剩下的是 trade-off——Saga 還是 Event Sourcing?Kafka 還是 RabbitMQ?Credit Card / Debit Card 分不分?這些沒有標準答案,但你要講得出為什麼這樣選。
如果你正要準備 system design interview,推薦把這支影片找出來看——s09g 講話速度不快,而且面試官的追問邏輯很清楚。十分鐘的影片,抵你自己讀半小時的設計文章。
七、架構師怎麼看:POS 系統在 2026 年的業界標準
影片講得很扎實——三階段時序、Idempotency Key、為什麼一定塞 MQ。但有兩個地方我自己在實務上會做不同選擇,也有幾個細節影片沒講但踩過坑。
1. Idempotency Key 的 24 小時 window
s09g 講 Idempotency Key 對應「支付意圖」,但沒講 Stripe 內部的實作細節:key 是有 expiry 的。Stripe 的 idempotency key 預設保留 24 小時,過期後同樣 key 可以重新發起交易。
為什麼要這樣設計?兩個原因:
- 儲存成本:Stripe 每天處理數億筆交易,每筆 key + response 至少 1KB,永久保留是天文數字
- 真實使用場景:使用者刷卡後 24 小時內 retry 是合理的;24 小時後 retry 已經是另一個 payment intent(他要重新確認訂單),用舊 key 反而會把錯的訂單「鎖」住
架構師實作建議:
CREATE TABLE idempotency_keys (
key VARCHAR(255) PRIMARY KEY,
payment_id UUID NOT NULL,
response JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL -- created_at + 24h
);
CREATE INDEX ON idempotency_keys(expires_at); -- 背景 job 定期清
不要直接用 idempotency_key 對應「永久」record,要帶 expiry。
2. Message Queue 選型:Kafka 不是唯一答案
影片最後比較 Kafka vs RabbitMQ,但 2026 年業界有第三條路,而且很多 POS 場景更適合:
NATS JetStream:
- 比 Kafka 輕(單 binary、無 ZooKeeper)
- 內建 at-least-once + exactly-once 語意
- Subject-based routing 比 Kafka topic 更靈活
- 適合 POS 這種「訊息量不大、但對延遲敏感」的場景
AWS EventBridge / SQS:
- 完全 managed,不用自己維運
- 跟 Lambda 整合最自然(POS 後端如果 serverless,這條路最便宜)
- 缺點:vendor lock-in
實務判斷:
| 場景 | 推薦 |
|---|---|
| 訊息量 < 10K/sec、serverless 架構 | SQS / EventBridge |
| 訊息量 10K-100K/sec、需要 replay | NATS JetStream |
| 訊息量 > 100K/sec、已有 Kafka 團隊 | Kafka |
| 傳統銀行整合、需要 AMQP | RabbitMQ |
3. 跟 Stripe 的差別:trade-off 反轉
影片打槍「不要為了潮做 Event Sourcing」,這個觀點在 2018 年對,但 2026 年其實已經反轉了。
原因是 Kafka + 雲端 K8s 把 Event Sourcing 的維運成本壓到很低:
- Kafka 3.x 之後 KRaft mode 取代 ZooKeeper,部署從 5 台變 3 台
- 雲端有 Confluent Cloud / MSK / Redpanda managed,1 個 cluster 一鍵起
- 開發框架成熟(Spring Cloud Stream / Debezium / Axon)
所以現在業界的判斷變成:
- 中小企業(單月交易 < 100 萬):Saga + ledger 就夠,不要上 ES
- 中型企業(單月交易 100 萬-1 億):可以考慮 ES,但要先驗證維運人力
- 大型企業 / 金融機構:ES 已經是標配(Visa、Mastercard 內部都是 ES-based)
我自己的判斷邏輯:不是「Stripe 所以 ES」,而是「你有沒有 Kafka 維運能力」。沒有,就 Saga;有,就 ES。這比「看誰是榜樣」務實多了。
4. 三階段時序的現代變體:Capture 即時化
影片講三階段拆開,但 2024 年後業界已經往「Capture 即時」方向演進:
- 過去:Authorize 即時、Processing 批次(晚上)、Complete 對帳(隔天)
- 現在:Authorize + Capture 在 1-3 秒內完成(Stripe 後台可以設 auto-capture timing)
這對商家的影響:
| 業務 | 過去設計 | 現在設計 |
|---|---|---|
| 電商刷卡 | 等 Complete 才出貨 | Authorize 成功 + Capture 成功就出貨 |
| 訂閱服務 | Authorize 不 Capture,月結時 Capture | 第一次 Authorize + Capture,後續只 Capture |
| 預授權(飯店、租車) | 只 Authorize | 走 Auth Hold + 後續 Capture,時間拉長到 30 天 |
架構師要注意:Capture 時機現在已經是產品決策,不是純技術問題。所以 POS 系統設計時,要讓 capture timing 可以配置,不能 hard-code 在系統裡。
5. 風控放在哪一層
影片在 Payment 那篇(不是 POS 這篇,但同一個脈絡)講到 4 種風控等級。我補一個架構師會問的問題:風控放 Payment Service 前還是後?
- 放前面(risk engine → payment service):壞交易根本不打 PSP,省通道費、保護 PSP quota
- 放後面(payment service → risk engine → manual review):PSP 先收到錢,再判斷要不要退款
業界實作是兩段式:
- Lightweight risk check(同步、毫秒級):放 Payment Service 前面,黑名單 + 簡單規則
- Deep risk analysis(非同步、秒級):放 Payment Service 後面,ML 模型 + 人工 review queue
原因:完全同步會把支付 latency 拉到 > 1 秒,使用者體驗崩潰;完全非同步會讓壞交易已經扣款成功,後續退款成本更高。
6. 一個面試官會問的反問
如果面試官聽完你講 Idempotency Key 反問:「如果使用者兩台裝置同時按結帳,按鈕都 disable 失敗,你怎麼辦?」
正確答案不是「我不會遇到這個問題」(太天真),也不是「我用分散式鎖」(太暴力),而是:
「Idempotency key 就是設計來處理這個的。兩台裝置雖然發出兩個 HTTP request,但它們都帶同一個 order_id 產生的 hash(hash 在使用者進入 checkout 頁時就生成)。我的服務收到第一個 request 開始處理,第二個 request 進來時查 key 發現已有 pending record,會進 blocking wait 等第一個完成(最多等 5 秒),完成後回傳同一個結果。這比分散式鎖簡單,而且不用假設前端 disable 一定成功。」
這個答案把 Idempotency Key 的「blocking wait」變化講出來了——大部分人只想到「查到結果就 return」,但實作上新交易要有 pending 狀態、查詢要有等待機制。
架構師 takeaway:POS 系統設計在 2026 年已經不是「架構圖怎麼畫」的問題,而是「要 managed 到什麼程度」的問題。完全自建(從 acquire bank 接到 ledger)只有金融機構才划算;其他團隊的甜蜜點是 PSP + 自建 idempotency layer + 自建對帳。Stripe 解決 70% 的髒活,剩下 30% 你要自己扛。