大规模支付系统设计
Source: Dev.to
要进行翻译,请提供您希望翻译的文章正文内容(除代码块和 URL 之外的文本),我将按照要求保留原始格式、Markdown 语法和技术术语,仅翻译正文部分。
当 Maria 点击 “确认乘车” 时到底发生了什么?
Maria 在 15 分钟后有一个重要会议。
她没有现金。
她打开 Uber,叫车,完成下车。
付款方式? 隐形的、即时的、轻松的。
但在这一次点击背后,是现代软件中最复杂的分布式系统之一。
今天我们将拆解它——不仅仅是“如何刷卡”,而是 如何构建一个安全、可靠、可扩展的支付系统,能够每天处理数百万次乘车。
简单的幻象
从用户的角度:
Trip ends → $20 charged → Done
从后端的角度:
- 安全地收集支付详情
- 避免存储敏感的卡片数据
- 防止欺诈
- 处理银行故障
- 将资金分配给多个方
- 保持财务准确性
- 对账差异
- 在重试和超时中生存
- 支持全球规模
这不是一个功能,而是基础设施。
1️⃣ 第一个问题:不能存储卡片数据
当用户输入:
- 卡号
- CVV
- 有效期
直接存储意味着:
- 严格的 PCI‑DSS 合规要求
- 巨大的泄露风险
- 法律风险
Solution: Tokenization
- 移动应用集成支付提供商的 SDK(Stripe、Adyen 等)。
- SDK 将卡片数据 直接 发送给提供商。
- 提供商返回一个 令牌。
- 你只存储该令牌。
该令牌是可重复使用、具有限定范围的卡片扣费权限。如果被盗,在你的商户账户之外毫无用处。安全问题基本解决(大体上)。
2️⃣ 授权 vs. 捕获(细节所在)
当行程结束时,你并不仅仅是“收费”。通常你会:
- 授权 – 检查卡片是否有足够资金并锁定金额。
- 捕获 – 实际转移资金。
为什么要分开?
- 行程费用可能会变化。
- 你可能需要调整最终车费。
- 你不想出现未付款的行程。
大型系统通常会提前授权(预估费用),随后再捕获(最终费用)。这看似小细节,却对架构影响巨大。
3️⃣ 钱款不直接从乘客流向司机
乘客 不 直接向司机付款。相反:
Rider → Uber Merchant Account → Split →
→ Driver
→ Uber Commission
→ Taxes
→ Fees
为什么?
- 佣金控制
- 税务处理
- 争议处理
- 防欺诈
直接的点对点支付会破坏会计。
4️⃣ 隐藏的英雄:内部账本系统
你不能把支付提供商当作唯一的真相来源。请自行构建 账本服务。
一个简化的复式记账示例:
| 账户 | 借方 | 贷方 |
|---|---|---|
| Rider | $20 | |
| Driver | $15 | |
| Platform | $5 |
每一次变动都会被记录。复式记账确保资金不会凭空消失。若借方 ≠ 贷方 → 表示出现了问题。规模化时,这就是“运行良好”和“悄然损失 300 万美元”之间的差别。
5️⃣ 可靠性:外部系统会失败
您的支付系统依赖于:
- 银行
- 卡网络
- 支付提供商
- 网络调用
它们都有可能失败。一个常见的噩梦:
- 授权成功。
- 捕获请求超时。
- 您再次尝试。
- 客户被双重收费。
解决方案: 幂等键
- 每次支付尝试都包含一个唯一键(例如
ride_id)。 - 如果重试,提供商会识别该键并避免重复处理。
如果没有幂等性,您将对用户进行双重收费并失去信任。
6️⃣ 智能重试(非盲目重试)
| 错误 | 是否重试 |
|---|---|
| 网络超时 | 是 |
| 速率限制 | 是 |
| 资金不足 | 否 |
| 欺诈阻止 | 否 |
盲目重试会导致混乱。智能重试则提升弹性。
7️⃣ Fraud Layer (Before Money Moves)
Before charging, run:
- Velocity checks
- Device fingerprinting
- Location mismatch detection
- Behavioral anomaly detection
If something looks suspicious, trigger:
- 3‑D Secure
- OTP verification
- Manual review
Payment systems are also fraud systems. Ignoring this will let chargebacks destroy margins.
8️⃣ 退款并不简单
退款并不仅仅是“逆转交易”。它需要:
- 更新内部账本
- 向供应商发起退款请求
- 调整司机的余额
- 处理可能已经完成的付款
有时平台会承担暂时的损失。复杂性会随时间累积。
9️⃣ Driver Payouts: A Different System
Charging cards is one system. Paying drivers is another.
Typical flow:
- Aggregate earnings daily
- Settle weekly (or offer instant payout for a fee)
Uses bank rails like ACH, SEPA, etc., which are completely different from card networks. Two financial systems under one product.
🔟 对账(成人工作)
每晚:
- 从支付提供商拉取报告。
- 与内部账本进行比较。
- 确认不匹配项。
如果发现不匹配:
- 标记以供审查
- 触发调查
没有对账,细小的不一致会累积成数百万。
1️⃣1️⃣ 扩展到数百万次乘车
在大规模下:
- 每天 1 M+ 次乘车
- 峰值时每秒 1 000+ 笔交易
你需要:
- 无状态支付服务
- 事件驱动架构
- 消息队列(Kafka、Pub/Sub 等)
- 横向扩展
不要使用同步的 “Ride → Immediate Charge”,而是使用解耦的流程:
RideCompleted Event → Payment Queue → Worker → Provider
解耦可以防止级联故障。
1️⃣2️⃣ 多供应商策略
永远不要只依赖单一支付供应商。实现:
- 主供应商
- 次要备用
使用抽象层:
def charge(amount, token):
# routing logic decides which provider to use
...
故障会发生;备用方案可以让平台保持运行。
“if.”
They are “when.”
看似简单的其实是分布式金融
一个乘车支付系统 不是:
- 仅仅是 API 调用
- 仅仅是令牌存储
- 仅仅是 Stripe 集成
它 是:
- 分布式系统
- 财务会计
- 法律合规
- 容错
- 欺诈建模
- 银行集成
- 事件驱动基础设施
这就是支付基础设施成为全球最难的后端领域之一的原因。
最终思考
当玛丽亚在布拉格下了那辆出租车时,她并没有想到:
- 幂等键
- 双重记账
- 多供应商故障切换
- 欺诈评分
- 对账流水线
她只是走进了她的会议。
这就是目标。优秀的工程让复杂性变得不可见。
如果你在构建系统
不要只设计功能。要为以下方面设计:
- 故障
- 可扩展性
- 可审计性
- 正确性
因为金融系统不容错误。