题目
你要为一个数据 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 和业务操作建立唯一约束,记录每次验证与执行结果,并用故障注入覆盖客户端超时、服务重启、验证器重复回调和对账延迟。