大规模支付系统设计

发布: (2026年2月21日 GMT+8 14:52)
9 分钟阅读
原文: Dev.to

Source: Dev.to

要进行翻译,请提供您希望翻译的文章正文内容(除代码块和 URL 之外的文本),我将按照要求保留原始格式、Markdown 语法和技术术语,仅翻译正文部分。

当 Maria 点击 “确认乘车” 时到底发生了什么?

Maria 在 15 分钟后有一个重要会议。
她没有现金。
她打开 Uber,叫车,完成下车。

付款方式? 隐形的、即时的、轻松的。

但在这一次点击背后,是现代软件中最复杂的分布式系统之一。

今天我们将拆解它——不仅仅是“如何刷卡”,而是 如何构建一个安全、可靠、可扩展的支付系统,能够每天处理数百万次乘车。

简单的幻象

从用户的角度:

Trip ends → $20 charged → Done

从后端的角度:

  • 安全地收集支付详情
  • 避免存储敏感的卡片数据
  • 防止欺诈
  • 处理银行故障
  • 将资金分配给多个方
  • 保持财务准确性
  • 对账差异
  • 在重试和超时中生存
  • 支持全球规模

这不是一个功能,而是基础设施。

1️⃣ 第一个问题:不能存储卡片数据

当用户输入:

  • 卡号
  • CVV
  • 有效期

直接存储意味着:

  • 严格的 PCI‑DSS 合规要求
  • 巨大的泄露风险
  • 法律风险

Solution: Tokenization

  1. 移动应用集成支付提供商的 SDK(Stripe、Adyen 等)。
  2. SDK 将卡片数据 直接 发送给提供商。
  3. 提供商返回一个 令牌
  4. 你只存储该令牌。

该令牌是可重复使用、具有限定范围的卡片扣费权限。如果被盗,在你的商户账户之外毫无用处。安全问题基本解决(大体上)。

2️⃣ 授权 vs. 捕获(细节所在)

当行程结束时,你并不仅仅是“收费”。通常你会:

  1. 授权 – 检查卡片是否有足够资金并锁定金额。
  2. 捕获 – 实际转移资金。

为什么要分开?

  • 行程费用可能会变化。
  • 你可能需要调整最终车费。
  • 你不想出现未付款的行程。

大型系统通常会提前授权(预估费用),随后再捕获(最终费用)。这看似小细节,却对架构影响巨大。

3️⃣ 钱款不直接从乘客流向司机

乘客 直接向司机付款。相反:

Rider → Uber Merchant Account → Split →
    → Driver
    → Uber Commission
    → Taxes
    → Fees

为什么?

  • 佣金控制
  • 税务处理
  • 争议处理
  • 防欺诈

直接的点对点支付会破坏会计。

4️⃣ 隐藏的英雄:内部账本系统

你不能把支付提供商当作唯一的真相来源。请自行构建 账本服务

一个简化的复式记账示例:

账户借方贷方
Rider$20
Driver$15
Platform$5

每一次变动都会被记录。复式记账确保资金不会凭空消失。若借方 ≠ 贷方 → 表示出现了问题。规模化时,这就是“运行良好”和“悄然损失 300 万美元”之间的差别。

5️⃣ 可靠性:外部系统会失败

您的支付系统依赖于:

  • 银行
  • 卡网络
  • 支付提供商
  • 网络调用

它们都有可能失败。一个常见的噩梦:

  1. 授权成功。
  2. 捕获请求超时。
  3. 您再次尝试。
  4. 客户被双重收费。

解决方案: 幂等键

  • 每次支付尝试都包含一个唯一键(例如 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️⃣ 退款并不简单

退款并不仅仅是“逆转交易”。它需要:

  1. 更新内部账本
  2. 向供应商发起退款请求
  3. 调整司机的余额
  4. 处理可能已经完成的付款

有时平台会承担暂时的损失。复杂性会随时间累积。

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. 从支付提供商拉取报告。
  2. 与内部账本进行比较。
  3. 确认不匹配项。

如果发现不匹配:

  • 标记以供审查
  • 触发调查

没有对账,细小的不一致会累积成数百万。

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 集成

  • 分布式系统
  • 财务会计
  • 法律合规
  • 容错
  • 欺诈建模
  • 银行集成
  • 事件驱动基础设施

这就是支付基础设施成为全球最难的后端领域之一的原因。

最终思考

当玛丽亚在布拉格下了那辆出租车时,她并没有想到:

  • 幂等键
  • 双重记账
  • 多供应商故障切换
  • 欺诈评分
  • 对账流水线

她只是走进了她的会议。

这就是目标。优秀的工程让复杂性变得不可见。

如果你在构建系统

不要只设计功能。要为以下方面设计:

  • 故障
  • 可扩展性
  • 可审计性
  • 正确性

因为金融系统不容错误。

0 浏览
Back to Blog

相关文章

阅读更多 »