題目
你要為資料 API 增加按請求付費能力。未付款請求應回傳 HTTP 402,客戶端完成付款後重試原請求。請說明 402 在 RFC 9110 中的地位,並設計一個類似 x402 的端到端協定,涵蓋付款要求、證明驗證、資源綁定、重播防護、冪等性、退款與帳務對帳。
面試官考察點
- 能否區分 402 的標準語意與具體付款方案:RFC 9110 保留此狀態碼,但沒有規定付款網路、貨幣或回應格式。
- 能否把付款證明綁定到資源、金額、收款方、網路與有效期,避免一筆付款被挪到另一個請求。
- 能否處理客戶端重試、逾時、重複扣款、區塊鏈確認延遲與服務端帳務一致性。
- 能否說清楚付款服務、資源服務、結算方與稽核日誌之間的信任邊界。
參考答案
402 是 HTTP 狀態碼註冊表中的 “Payment Required”。RFC 9110 保留了它,但沒有定義通用付款協定。協定需要把 402 當成可解析的挑戰回應,而不是把「收到 402」當成已經付款。
資源服務可在 402 回應中返回唯一的 payment requirement,包含資源識別、請求方法與路徑、金額、資產、網路、收款地址、過期時間與隨機數。客戶端只針對這些欄位簽名或付款。服務端或受信任的 facilitator 驗證證明,確認金額、收款方、網路與資源都相符,再把一次性的 receipt 交給資源服務。
資源服務應先記錄 request id 與 payment id 的冪等關係,再執行昂貴操作。同一 request id 重試時,服務端返回相同結果或明確的處理中狀態。付款成功不等於資源操作成功,因此要把付款、授權、業務執行與退款拆成可追蹤狀態,並以對帳任務找出鏈上確認、服務端記錄與實際交付之間的差異。
實作範例
以下偽程式碼展示挑戰與重試的核心邊界;真實系統還需要接入付款驗證器、冪等儲存與稽核日誌。
handle(request):
id = request.idempotencyKey
if receiptStore.has(id):
return receiptStore.result(id)
requirement = makeRequirement(
resource = canonicalResource(request),
amount = quote(request),
network = "base",
expiresAt = now + 60s,
nonce = randomBytes(16)
)
proof = request.headers["Payment-Proof"]
if proof is missing:
return 402, { "payment-required": requirement }
payment = verifyProof(proof, requirement)
if payment.invalid or payment.expired or payment.replayed:
return 402, { "payment-required": requirement, "reason": "invalid-proof" }
result = executeOnce(id, request, payment)
receiptStore.put(id, payment.id, result)
return 200, result關鍵是 canonicalResource 與 verifyProof 的輸入必須由同一套正規化規則產生;否則同一資源可能有多個字串表示,造成簽名驗證與授權判斷不一致。executeOnce 需要使用唯一約束、交易或持久化狀態,確保重試不會重複交付副作用。
常見誤區
- 認為 402 自帶付款流程。它只表達「需要付款」,付款欄位與驗證規則必須由協定定義。
- 只驗證金額,不驗證資源、網路、收款方、資產與過期時間,導致跨資源替換或跨網路重播。
- 付款確認後直接執行副作用,卻沒有 request id 冪等記錄,逾時重試會重複扣款或重複建立資源。
- 把鏈上交易已送出當成最終結算;確認延遲、分叉、facilitator 故障與退款都要進入狀態機。
- 把付款證明放進日誌或 URL,造成憑證洩露與可重播風險。
實戰取捨
小額、低風險的讀取 API 可以採用短期報價、一次性 nonce 與非同步最終對帳;高價值寫入操作應先取得可驗證的結算狀態,再在冪等交易中交付。若客戶端沒有錢包或鏈上能力,可以讓 facilitator 代付,但要明確界定 facilitator 的信任範圍、費率、限額與故障回退。
協定還要定義價格變化、過期挑戰、部分付款、付款成功但資源失敗、退款與服務降級。快取層不能把含付款證明的回應分享給其他主體,快取鍵必須包含授權結果,或只快取公開的 402 challenge。
參考資料
- RFC 9110 HTTP Semantics:402 的註冊語意與 HTTP 狀態碼約束。
- x402 Introduction:以 402 challenge 驅動無帳戶按請求付費的協定概念。
- Coinbase HTTP 402 Core Concepts:付款要求、驗證與資源存取的實作邊界。
追問
如何防止同一付款證明被用於兩個不同資源?
把正規化後的方法、路徑、查詢參數摘要或資源 ID 寫入 payment requirement,讓證明覆蓋這些欄位;服務端用同一正規化演算法重算摘要,並為 nonce 或 payment id 建立一次性消費記錄。
402 challenge 應該放在回應標頭還是回應本文?
先定義版本化的機器可讀格式。標頭適合輕量提示,回應本文適合攜帶多欄位付款要求;無論位置如何,都要限制大小、宣告內容類型,並避免把敏感憑證放入可快取標頭。
如何處理付款成功但業務執行失敗?
把 payment、authorization、execution 與 refund 分成獨立狀態,使用 request id 關聯。若業務不可重試,進入退款或人工對帳佇列;若可重試,回傳處理中狀態並確保後續查詢得到相同結果。
x402 是否要求區塊鏈?
現有 x402 資料以鏈上或 facilitator 付款為例,但 HTTP 402 本身不規定結算網路。面試時應把「狀態碼語意」與「付款軌道」分開,說明替換為其他付款網路時必須重新定義證明、最終性與退款語意。
怎樣驗證系統沒有重複扣款?
對 payment id、request id 與業務操作建立唯一約束,記錄每次驗證與執行結果,並用故障注入覆蓋客戶端逾時、服務重啟、驗證器重複回呼與對帳延遲。