Hero Image
- Mark

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

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


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

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

  1. 全部在線變體(online-only):使用者從下單到付款到出貨,所有事都在一條 HTTP 鏈路上跑完。這是電商網站最常見的情境。
  2. 刷卡變體(card-present):今天的主角——實體店面、用戶拿信用卡刷 POS 終端。這個變體必須拆成線上跟線下兩段
  3. 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 系統設計,這些一定要準備:

  1. Authorize / Processing / Complete 三階段時序:有沒有講清楚發生在不同時段?不是「三步驟」是「三個時段」。
  2. Idempotency Key 的 scope:是 outer-level 還是刷卡一次 level?答:支付意圖層。
  3. 一個 order 多個 intent:split the bill、退款重付——有沒有想過這兩個 case?
  4. 內部 ID 不要曝露:你的 idempotency key 怎麼產生?hash 過嗎?
  5. Message Queue 為什麼必要:同步串起來會發生什麼?
  6. Credit Card vs Debit Card 要不要分:影片最後被追問這個——實務上會分,因為清算規則不同。
  7. 跟 Stripe 的差別:你是 Stripe 的客戶,不是 Stripe——架構不一樣。

結語

POS 系統設計的精髓不是 API 介面設計,而是時序跨系統一致性

記住三件事大概就能 cover 80% 的面試場景:

  1. 三階段時序:Authorize 即時、Processing 批次、Complete 非同步
  2. Idempotency Key:對應支付意圖、跨系統流通、絕不曝露內部 ID
  3. Message Queue:跨 TPS 系統之間必塞

剩下的是 trade-off——Saga 還是 Event Sourcing?Kafka 還是 RabbitMQ?Credit Card / Debit Card 分不分?這些沒有標準答案,但你要講得出為什麼這樣選。

如果你正要準備 system design interview,推薦把這支影片找出來看——s09g 講話速度不快,而且面試官的追問邏輯很清楚。十分鐘的影片,抵你自己讀半小時的設計文章。

Other Related Posts:

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

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

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

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

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

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

17th Aug 2026 - Mark