18. Design Payment System
In this chapter, we design a payment system. E-commerce has exploded in popularity across the world in recent years. What makes every transaction possible is a payment system running behind the scenes. A reliable, scalable, and flexible payment system is essential.
在本章中,我们将设计一个支付系统。近年来,电子商务在全球范围内蓬勃发展。每笔交易的实现都离不开一个在幕后运行的支付系统。一个可靠、可扩展且灵活的支付系统至关重要。
Reference: https://bytebytego.com/courses/system-design-interview/payment-system
Step 1 - Understand the Problem and Establish Design Scope
1.1 Functional requirements
- Handle credit card payments via third-party processors (e.g., Stripe), without storing sensitive card data
- 通过第三方支付处理器 (如 Stripe )处理信用卡支付,不存储敏感卡片信息
- Support both customer payments (pay-in) and seller payouts (pay-out)
- 支持顾客付款和卖家结算两个方向的资金流转
- Designed for global use, but assume a single currency for now
- 面向全球用户,但当前仅考虑单一货币
- Process around 1 million transactions per day
- 支持每日约 100 万笔交易的处理能力
- Integrate with internal and external systems, and support reconciliation for consistency
- 与内部系统 (如会计、分析 )和外部服务集成,并支持对账以保证一致性
1.2 Non-functional requirements
- Reliability and fault tolerance. Failed payments need to be carefully handled.
- 可靠性和容错性。付款失败需要谨慎处理。
- A reconciliation process between internal services (payment systems, accounting systems) and external services (payment service providers) is required. The process asynchronously verifies that the payment information across these systems is consistent.
- 需要在内部服务 (支付系统、会计系统 )和外部服务 (支付服务提供商 )之间建立对账流程。该流程异步验证这些系统之间的支付信息是否一致。
1.3 Back-of-the-envelope estimation 粗略估计
The system needs to process 1 million transactions per day, which is 1,000,000 transactions / 10^5 seconds = 10 transactions per second (TPS). 10 TPS is not a big number for a typical database, which means the focus of this system design interview is on how to correctly handle payment transactions, rather than aiming for high throughput.
该系统每天需要处理 100 万笔交易,即 1,000,000 笔交易 / 10^5 秒 = 每秒 10 笔交易 (TPS)。对于典型的数据库来说,10 TPS 并不是一个很大的数字,这意味着本次系统设计面试的重点是如何正确处理支付交易,而不是追求高吞吐量。
Step 2 - Propose High-Level Design and Get Buy-In
Take the e-commerce site, Amazon, as an example. After a buyer places an order, the money flows into Amazon’s bank account, which is the pay-in flow. Although the money is in Amazon's bank account, Amazon does not own all of the money. The seller owns a substantial part of it and Amazon only works as the money custodian for a fee. Later, when the products are delivered and money is released, the balance after fees then flows from Amazon’s bank account to the seller's bank account. This is the pay-out flow. The simplified pay-in and pay-out flows are shown in Figure 1.
以电商网站亚马逊为例。买家下单后,款项会进入亚马逊的银行账户,这就是入账流程。虽然款项在亚马逊的银行账户中,但亚马逊并非所有款项的所有者。卖家拥有其中很大一部分,亚马逊仅作为资金托管人,并收取一定费用。之后,当商品发货并款项到账后,扣除费用后的余额会从亚马逊的银行账户转入卖家的银行账户,这就是出账流程。简化的入账和出账流程如图 1 所示。
2.1 Pay-in flow 入账流程
- Payment service 支付服务
- The payment service accepts payment events from users and coordinates the payment process. The first thing it usually does is a risk check, assessing for compliance with regulations such as AML/CFT [2], and for evidence of criminal activity such as money laundering or financing of terrorism. The payment service only processes payments that pass this risk check. Usually, the risk check service uses a third-party provider because it is very complicated and highly specialized.
- 支付服务接受用户的付款事件并协调支付流程。它通常首先进行风险检查,评估是否符合反洗钱/反恐怖融资[2]等法规,并查找洗钱或恐怖主义融 资等犯罪活动的证据。支付服务只处理通过此风险检查的付款。由于风险检查服务非常复杂且高度专业化,通常会使用第三方服务提供商。
- Payment executor 付款执行人
- The payment executor executes a single payment order via a Payment Service Provider (PSP). A payment event may contain several payment orders.
- 支付执行器通过支付服务提供商 (PSP )执行单笔支付订单。一个支付事件可能包含多笔支付订单。
- Payment Service Provider (PSP)
- A PSP moves money from account A to account B. In this simplified example, the PSP moves the money out of the buyer’s credit card account.
- PSP 将资金从账户 A 转移到账户 B。在这个简化的例子中,PSP 将资金从买家的信用卡账户中转出。
- Card schemes
- Card schemes are the organizations that process credit card operations. Well known card schemes are Visa, MasterCard, Discovery, etc. The card scheme ecosystem is very complex [3].
- 信用卡组织是处理信用卡业务的组织。知名的信用卡组织有 Visa、MasterCard、Discovery 等。信用卡组织生态系统非常复杂 [3]。
- Ledger 账本
- The ledger keeps a financial record of the payment transaction. For example, when a user pays the seller $1, we record it as debit $1 from a user and credit $1 to the seller. The ledger system is very important in post-payment analysis, such as calculating the total revenue of the e-commerce website or forecasting future revenue.
- 账本保存支付交易的财务记录。例如,当用户向卖家支付 1 美元时,我们会将其记录为用户借方 1 美元,卖家贷方 1 美元。账本系统在支付后分析中非常重要,例如计算电商网站的总收入或预测未来收入。
- Wallet 钱包
- The wallet keeps the account balance of the merchant. It may also record how much a given user has paid in total.
- 钱包保存着商家的账户余额。它也可能记录特定用户总共支付了多少钱。
As shown in Figure 2, a typical pay-in flow works like this:
- 1.When a user clicks the “place order” button, a payment event is generated and sent to the payment service.
- 当用户点击“下订单”按钮时,就会生成支付事件并发送到支付服务。
- 2.The payment service stores the payment event in the DB.
- 支付服务将支付事件存储在数据库中。
- 3.Sometimes, a single payment event may contain several payment orders. For example, you may select products from multiple sellers in a single checkout process. If the e-commerce website splits the checkout into multiple payment orders, the payment service calls the payment executor for each payment order.
- 有时,单个支付事件可能包含多个支付订单。例如,您可能在一次结账流程中选择了来自多个卖家的商品。如果电商网站将结账拆分为多个支付订单,则支付服务会为每个支付订单调用支付执行器。
- 4.The payment executor stores the payment order in the DB.
- 支付执行器将支付订单存储在 DB 中。
- 5.The payment executor calls an external PSP to process the credit card payment.
- 付款执行器调用外部 PSP 来处理信用卡付款。
- 6.After the payment executor has successfully processed the payment, the payment service updates the wallet to record how much money a given seller has.
- 付款执行器成功处理付款后,支付服务会更 新钱包以记录特定卖家有多少钱。
- 7.The wallet server stores the updated balance information in the DB.
- 钱包服务将更新后的余额信息存储在数据库中。
- 8.After the wallet service has successfully updated the seller’s balance information, the payment service calls the ledger to update it.
- 钱包服务成功更新卖家余额信息后,支付服务会调用分类账进行更新。
- 9.The ledger service appends the new ledger information to DB.
- 记账服务将新的分类帐信息附加到数据库。
2.2 APIs for payment service
We use the RESTful API design convention for the payment service.
2.2.1 POST /v1/payments
This endpoint executes a payment event. As mentioned above, a single payment event may contain multiple payment orders. 此端点执行支付事件。如上所述,单个支付事件可能包含多个支付订单。
{
"buyer_info": {
"name": "Alice Zhang", // 买家姓名 (由前端填写 )
"email": "alice@example.com", // 买家邮箱 (由前端填写 )
"phone": "+1-415-555-1234", // 买家手机号 (由前端填写 )
"address": {
// 买家收货地址 (由前端填写或用户配置 )
"line1": "123 Market Street", // 地址第一行
"city": "San Francisco", // 城市
"state": "CA", // 州/省
"postal_code": "94103", // 邮政编码
"country": "US" // 国家 (ISO 国家码 )
}
},
"checkout_id": "chk_20250705_000123", // 结账单号 (由 Checkout 服务生成,通常在创建购物车结账页时生成 )
"credit_card_info": {
"token": "tok_abc123xyz456", // 支付 token (由前端通过 PSP SDK 获取,比如 Stripe Elements )
"provider": "stripe", // 支付服务商 (由前端或支付配置中心决定 )
"last4": "1111", // 卡号后四位 (由 PSP 返回,前端或支付服务回填 )
"brand": "Visa", // 卡品牌 (由 PSP 返回 )
"expiry_month": "12", // 有效期月份 (由前端填写 )
"expiry_year": "2027" // 有效期年份 (由前端填写 )
},
"payment_orders": [
{
"seller_account": "seller_001", // 卖家账户 (由订单服务 Order Service 决定 )
"amount": "49.99", // 金额 (由订单服务计算 )
"currency": "USD", // 币种 (由商城或国际化服务设置 )ISO 4217
"payment_order_id": "po_20250705_0001" // 支付订单号 (由 Payment Service 生成 )
},
{
"seller_account": "seller_002", // 第二位卖家 (由订单服务识别并拆单 )
"amount": "30.00",
"currency": "USD",
"payment_order_id": "po_20250705_0002" // globally unique ID (idempotency key) 全局唯一幂等键
}
]
}
- the
payment_order_id
is globally unique. When the payment executor sends a payment request to a third-party PSP, thepayment_order_id
is used by the PSP as the deduplication ID, also called the idempotency key.- 全局唯一键。当支付执行方向第三方支付服务提供商 (PSP) 发送支付请求时,payment_order_id 会被 PSP 作为去重 ID,也称为幂等键
- the data type of the “amount” field is “string,” rather than “double”. Double is not a good choice because:
- Different protocols, software, and hardware may support different numeric precisions in serialization and deserialization. This difference might cause unintended rounding errors.
- 不同的协议、软件和硬件在序列化和反序列化过程中可能支持不同的数值精度。这种差异可能会导致意外的舍入误差。
- The number could be extremely big (for example, Japan’s GDP is around 5x1014 yen for the calendar year 2020), or extremely small (for example, a satoshi of Bitcoin is 10-8).
- It is recommended to keep numbers in string format during transmission and storage. They are only parsed to numbers when used for display or calculation.
- 建议在传输和存储过程中将数字保留为字符串格式。只有在显示或计算时才会将其解析为数字。
2.2.2 GET /v1/payments/{:id}
- This endpoint returns the execution status of a single payment order based on
payment_order_id
.- 该接口根据
payment_order_id
返回单个支付订单的执行状态。
- 该接口根据
- The payment API mentioned above is similar to the API of some well-known PSPs. If you are interested in a more comprehensive view of payment APIs, check out Stripe’s API documentation https://stripe.com/docs/api.
2.3 The data model for payment service 支付服务的数据模型
We need two tables for the payment service: payment event and payment order. When we select a storage solution for a payment system, performance is usually not the most important factor. Instead, we focus on the following:
- Proven stability. Whether the storage system has been used by other big financial firms for many years (for example more than 5 years) with positive feedback. 稳定性已得到验证。该存储系统是否已被其他大型金融机构使用多年 (例如超过 5 年 ),并获得良好的反馈。
- The richness of supporting tools, such as monitoring and investigation tools. 支持工具的丰富性,例如监控和调查工具。
Usually, we prefer a traditional relational DB with ACID transaction support over NoSQL/NewSQL.
2.3.1 Payment Event 表结构
CREATE TABLE payment_event (
checkout_id VARCHAR(64) PRIMARY KEY COMMENT '结账 ID,唯一标识一次支付事件',
buyer_info TEXT COMMENT '买家信息 (可以是 JSON 字符串 )',
seller_info TEXT COMMENT '卖家信息 (可以是 JSON 字符串或多个卖家标识 )',
credit_card_info TEXT COMMENT '信用卡信息 (根据支付服务商格式存储 )',
is_payment_done BOOLEAN DEFAULT FALSE COMMENT '支付是否完成标志位'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付事件主表';
2.3.2 Payment Order 表结构
CREATE TABLE payment_order (
payment_order_id VARCHAR(64) PRIMARY KEY COMMENT '支付子订单 ID',
buyer_account VARCHAR(64) COMMENT '付款用户账户',
amount DECIMAL(18, 2) COMMENT '支付金额',
currency VARCHAR(10) COMMENT '货币类型 (如 USD、CNY )',
checkout_id VARCHAR(64) COMMENT '关联的结账事件 ID',
payment_order_status VARCHAR(32) COMMENT '支付订单状态 (如 NOT_STARTED, EXECUTING, SUCCESS, FAILED',
ledger_updated BOOLEAN DEFAULT FALSE COMMENT '账本是否已更新',
wallet_updated BOOLEAN DEFAULT FALSE COMMENT '钱包是否已更新',
CONSTRAINT fk_checkout_id FOREIGN KEY (checkout_id)
REFERENCES payment_event(checkout_id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付子订单表';
- The
checkout_id
is the foreign key. A single checkout creates a payment event that may contain several payment orders.checkout_id
是外键。单次结账会创建一个付款事件,该事件可能包含多个付款订单。
- When we call a third-party PSP to deduct money from the buyer's credit card, the money is not directly transferred to the seller. Instead, the money is transferred to the e-commerce website’s bank account. This process is called pay-in. When the pay-out condition is satisfied, such as when the products are delivered, the seller initiates a pay-out. Only then is the money transferred from the e-commerce website’s bank account to the seller's bank account. Therefore, during the pay-in flow, we only need the buyer’s card information, not the seller’s bank account information.
- 当我们调用第三方支付服务提供商 (PSP) 从买家信用卡扣款时,款项不会直接转给卖家,而是转入电商网站的银行账户。这个过程称为“到账”。当满足出账条件 (例如商品送达 )时,卖家才会发起出账。只有此时,款项才会从电商网站的银行账户转入卖家的银行账户。因此,在到账流程中,我们只需要买家的卡信息,而无需卖家的银行账户信息。
2.3.3 payment_order_status
状态机
- 1.初始化
- 默认状态为
NOT_STARTED
- 这时支付订单还未发送出去
- 默认状态为
- 2.发送支付请求
- 当支付服务将订单发送到 Payment Executor 后,状态更新为
EXECUTING
- 当支付服务将订单发送到 Payment Executor 后,状态更新为
- 3.支付执行完成
- 如果执行器返回成功,状态更新为
SUCCESS
- 如果执行器返回失败,状态更新为
FAILED
- 如果执行器返回成功,状态更新为
- 4.支付成功后的处理
- 当状态变为 SUCCESS,支付服务会调用 钱包服务 (Wallet Service) 更新卖家的余额
- 钱包更新成功后,字段
wallet_updated
会被置为 TRUE - 为简化设计,假设钱包更新操作 永远成功
- 5.账本更新
- 在钱包更新成功后,支付服务会进一步调用账本服务 (Ledger Service ),将交易信息写入账本数据库,并将 ledger_updated 字段设置为 TRUE。
- 6.整体支付完成检查
- 当某个结账单号 (
checkout_id
)下的所有支付订单都成功执行并完成后续处理后,支付服务会将 payment_event 表中的is_payment_done
字段设置为TRUE
,表示该笔支付已完全完成。
- 当某个结账单号 (
- 7.异常监控机制
- 系统中通常会部署一个定时任务 (Scheduled Job ),定期扫描仍处于进行中 (如 NOT_STARTED 或 EXECUTING )的支付订单。如果发现某笔订单在设定时间内 (如 5 分钟 )未完成处理,则会触发告警,提示工程师及时排查,确保支付流程的稳定性和可靠性。
状态 | 含义 |
---|---|
NOT_STARTED | 初始状态,表示支付服务还未将该订单发送给执行器 (Payment Executor ) |
EXECUTING | 支付执行中,表示支付服务已将该订单发送给执行器,正在进行支付处理 |
SUCCESS | 支付成功,支付执行器返回成功,表示该笔款项已经处理完成 |
FAILED | 支付失败,执行器返回失败,例如余额不足、卡信息无效等问题 |