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_idis globally unique. When the payment executor sends a payment request to a third-party PSP, thepayment_order_idis 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_idis 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 | 支付失败,执行器返回失败,例如余额不足、卡信息无效等问题 |
2.4 Double-entry Ledger System 双重记账系统
在账本系统中有一个非常关键的设计原则——双重记账原则 (double-entry principle,也叫做复式记账 )。这个原则是任何支付系统实现精确记账的基础。
双重记账系统的核心思想是:每一笔支付交易都需要记录成两条账本记录,一个账户是借记 (Debit ),另一个账户是贷记 (Credit ),金额相同。
例如,当买家向卖家支付 1 美元时
- 在账本中,买家账户被借记 $1
- 同时,卖家账户被贷记 $1
2.5 Hosted payment page 托管支付页
托管支付页由 PSP 托管,客户的信用卡信息直接输入给 PSP,而不是我们自己的支付服务系统,这样可以极大地简化系统合规要求并降低风险
大多数公司不愿意在自己的系统中直接存储信用卡信息,因为一旦存储,就必须遵守一系列严格的合规标准,例如美国的 PCI DSS (支付卡行业数据安全标准 ),这些要求既复杂又成本高昂。
为了规避处理信用卡信息带来的合规风险,企业通常会采用由 支付服务提供商 (PSP ) 提供的托管支付页面 (Hosted Payment Page )。这种支付页面通常有以下两种形式:
- 在网页端,是通过嵌入的 widget 或 iframe 实现
- 在移动端,可能是支付 SDK 提供的一个预置支付页面
例如,PayPal 的集成就是一个典型例子,用户在结账流程中会跳转到 PayPal 的托管支付页完成付款。

2.6 Pay-out flow 出账流程
出账流程的系统组件与入账流程 (Pay-in flow )非常相似,但有一个关键区别:
入账流程是通过 PSP (支付服务提供商 )将买家的信用卡资金转入电商平台的银行账户。而出账流程则是通过第三方出账服务商,将钱从电商平台的银行账户转出,打给卖家的银行账户。
在实际应 用中,电商平台通常会使用像 Tipalti 这样的第三方应付服务商 (Accounts Payable Providers )来处理出账操作。
因为出账涉及资金从企业流向个人或卖家账户,它同样面临严格的账务处理和合规监管要求,例如税务申报、付款报表、反洗钱 (AML )检查等。这使得出账流程在技术实现和合规层面都不容忽视。
Step 3 - Design Deep Dive
在本节中,我们的重点是如何让支付系统变得更快、更健壮、更安全。在分布式系统中,错误和故障不仅是可能的,而且是常见的现象。例如:
- 如果用户连续点击了多次“支付”按钮,会不会被重复扣款?what happens if a customer pressed the “pay” button multiple times?
- 网络不稳定时引发的支付失败,我们该如何处理? How do we handle payment failures caused by poor network connections?
- 系统内部各个服务之间如何协作,确保支付可靠完成?
本节将深入探讨几个关键主题
- PSP 接入 (PSP integration )
- 对账处理 (Reconciliation )
- 支付延迟处理 (Handling payment processing delays )
- 内部服务通信 (Communication among internal services )
- 支付失败处理 (Handling failed payments )
- 精准一次性处理 (Exact-once delivery )
- 一致性保障 (Consistency )
- 安全性设计 (Security )
3.1 PSP integration
从理论上讲,如果支付系统能够直接接 入银行或卡组织 (例如 Visa、MasterCard ),就可以绕过 PSP 直接完成支付。但这种方式非常少见,通常只适用于业务规模极大、有能力自行承担合规和集成成本的大型公司。
对大多数公司来说,支付系统会通过以下两种方式接入 PSP
- 方式一: API 集成 (适合可自行处理敏感数据的公司)
- 公司自身开发支付页面,收集和存储敏感支付信息
- PSP 提供接口,负责将信息传输到银行或卡组织
- 公司需要符合 PCI-DSS 等合规标准,风险较高
- 方式二: 托管支付页 (适合不想处理敏感数据的公司)
- 公司不处理敏感数据,由 PSP 提供一个托管支付页面 (Hosted Payment Page )
- 客户输入卡信息后直接提交到 PSP
- 信息安全地存储在 PSP 侧
这是大多数公司采用的方式,因其合规压力小、集成简单。

为了简化展示,图 4 中省略了 Payment Executor (支付执行器 )、Ledger (账本 ) 和 Wallet (钱包 ) 的细节。整个支付流程由 支付服务 (Payment Service )统一协调和编排。
- 步骤 1.用户点击结账
- 用户在浏览器中点击“结账”按钮;
- 客户端将支付订单信息发送给支付服务 (Payment Service )。
- 步骤 2.注册支付请求
- 支付服务收到支付订单信息后,向 PSP 发送 支付注册请求 (Payment Registration Request );
- 请求中包含支付信息,如金额、币种、请求过期时间、支付完成后的跳转链接 (redirect URL )等。
- 为了确保每个支付订单只注册一次,该请求中还包含一个 UUID 字段 (也叫 nonce ),通常直接使用 payment_order 的 ID。
- 步骤 3.获取 token
- PSP 返回一个 token (PSP 侧的唯一标识符 ),是一个 UUID;
- 后续我们可以使用这个 token 查询支付注册和支付执行的状态。
- 步骤 4.存储 token
- 支付服务将 PSP 返回的 token 存入数据库;
- 只有在 token 被持久化后,才允许展示托管支付页面,确保流程一致性。
- 步骤 5.展示托管支付页
- 客户端显示 由 PSP 托管的支付页面 (Hosted Payment Page );
- 移动端一般通过 PSP 提供的 SDK 集成;
- Stripe 提供了一个 JavaScript 库,用于显示支付 UI、收集敏感的支付信息,并直接调用 PSP 完成支付。敏感的支付信息由 Stripe 收集,永远不会到达我们的支付系统。托管的支付页面通常需要两条信息:
token:支付注册时 PSP 返回的唯一标识符。在第 4 步获取的 token 用于让 PSP 的 JS SDK 拉取支付请求的详细信息。包括支付金额等。redirect URL:支付完成后 PSP 会重定向到这个 URL,通常是电商网站的订单详情页。

- 步骤 6.用户提交支付信息
- 用户在 PSP 托管的支付页面中填写支付信息,包括:信用卡号, 持卡人姓名, 有效期等
- 填写完毕后,用户点击“支付”按钮,PSP 开始处理支付。
- 步骤 7.PSP 返回支付结果 (同步返回 )
- 支付完成后,PSP 会将支付结果 (成功或失败 )同步返回给前端页面,用于下一步跳转。
- 步骤 8.重定向到 redirect URL
- 浏览器 会根据支付请求中预先配置的 redirect_url,跳转到电商平台的前端页面,例如:
https://your-company.com/?tokenID=JIOUIQ123NSF&payResult=X324FSa - 支付状态 (如是否成功 )通常会被作为 URL 参数附加在跳转地址后面,供前端页面读取并展示给用户。
- 浏览器 会根据支付请求中预先配置的 redirect_url,跳转到电商平台的前端页面,例如:
- 步骤 9.Webhook 异步通知
- 同时,PSP 会通过 Webhook (异步回调 )通知支付系统本次交易的结果
- Webhook 是支付系统预先注册在 PSP 那里的一个 URL;
- PSP 在支付完成后,向该 URL 发起 HTTP 请求,将支付状态推送过来;
- 支付服务接收到回调后,解析其中的 token、支付状态等字段;
- 并在数据库中更新对应订单的
payment_order_status字段,例如设为SUCCESS或FAILED. 这样,即便用户浏览器没有跳转回来,系统也能获知最终的支付状态。
So far, we explained the happy path of the hosted payment page. In reality, the network connection could be unreliable and all 9 steps above could fail. Is there any systematic way to handle failure cases? The answer is reconciliation 对账.
3.2 Reconciliation 对账
在支付系统中,服务之间通常通过异步通信 (如 webhook 回调 )协作。但异步通信具有不确定性:消息可能丢失、重复,或延迟处理,尤其在与 PSP 或银行交互时更为常见。
为确保账务一致性,系统需要引入对账机制 Reconciliation 。对账是指定期比对内部账本记录与外部结算数据(如 PSP 提供的交易报表),以发现并修复差异。
3.2.1 对账流程简述
- 每日结算文件: PSP 或银行每天发送 settlement file,包含账户余额和当日交易记录;
- 解析结算数据: 对账服务读取并提取交易字段;
- 账本比对: 与系统内的账本记录核对金额、交易状态、交易 ID;
- 标记异常并修复: 如 webhook 丢失或状态不一致;
- 记录日志并触发补偿机制: 可自动修复或人工介入。

3.2.2 对账异常分类与处理方式
Reconciliation is also used to verify that the payment system is internally consistent. For example, the states in the ledger and wallet might diverge and we could use the reconciliation system to detect any discrepancy. 对账也用于验证支付系统内部的一致性。例如,账本和钱包的状态可能存在差异,我们可以使用对账系统来检测任何差异。
- 可分类且可自动修复
- 差异原因明确,修复逻辑清晰,自动化成本低,工程团队可以实现自动识别与修复。
- 示例: 某笔支付在 PSP 侧状态为成功,但系统因 webhook 延迟仍标记为
EXECUTING. 对账系统识别后可自动更新为SUCCESS. 并触发后续流程,如调用钱包服务。
- 可分类但需人工修复
- 差异原因明确,修复方式已知,但处理逻辑较复杂或依赖人工判断,自动化成本高,故由财务团队手动处理。
- 示例: 一笔交易金额与 PSP 文件不一致,原因是客服在后台发起了部分退款,但备注缺失。系统可以识别出异常,但是否应当调整金额仍需人工判断,因此该条记录会放入 job queue,供财务处理。
- 不可分类
- 差异原因不明确,系统无法判断如何修复,需要人工进一步调查。此类异常会被放入特殊队列等待人工介入。
- 示例: 对账文件中出现一笔系统中完全不存在的交易,查不到对应的 payment_order_id、webhook 或日志,可能是重复支付、接口异常或外部错账,需要人工排查数据源或联系 PSP 查明原因。
3.3 Handling payment processing delays 处理支付延迟
- 支付请求可能长时间未完成
- 一个支付请求从发起到完成,通常只需几秒钟。但在某些特殊情况下,支付可能“卡住”,长时间保持未完成状态,甚至延迟数小时或数天。这种情况在实际支付业务中并不少见. 示例:
- PSP (支付服务提供商 )检测到该笔支付存在高风险 (如金额异常、设备异常、地理位置可疑等 ),需要人工干预进行审批。
- 使用的信用卡启用了 3D Secure 认证 (如 Visa Secure、Mastercard Identity Check ),用户需跳转到发卡行页面输入短信验证码或其他身份信息,完成后才能继续支付流程。
- 一个支付请求从发起到完成,通常只需几秒钟。但在某些特殊情况下,支付可能“卡住”,长时间保持未完成状态,甚至延迟数小时或数天。这种情况在实际支付业务中并不少见. 示例:
- 支付服务需支持长时延处理流程
- 系统不能假设所有支付请求都是同步完成的,因此必须支持异步支付状态流转,并提供用户可视化的反馈机制. 示例:
- 用户在支付页面点击“支付”后,系统应能够识别出
PENDING状态,并显示“正在处理中”,而不是误判为失败或异常。 - 系统需记录
PENDING状态的订单,并允许用户后续重新进入查询状态。
- 用户在支付页面点击“支付”后,系统应能够识别出
- 系统不能假设所有支付请求都是同步完成的,因此必须支持异步支付状态流转,并提供用户可视化的反馈机制. 示例:
- PSP 托管支付页的处理方式
- 当前大多数公司都采用 PSP 提供的 托管支付页 (hosted payment page ) 来处理支付信息,避免接触敏感卡号、简化 PCI 合规流程。
- 当支付较慢时,PSP 会以以下方式支持整个流程:
- PSP 向前端返回
PENDING状态 (表示支付已发起,但未最终确认 )。 - 前端根据状态提示用户“支付处理中”,并提供一个状态轮询或结果页面。
- PSP 会在后台持续跟踪支付请求,一旦支付完成 (成功或失败 ),会向支付系统注册的 webhook 地址发送支付结果通知。
- 支付系统收到 webhook 后,更新内部
payment_order_status,并触发后续逻辑,如发货、通知用户等。
- PSP 向前端返回
- 部分 PSP 不支持 webhook,需轮询处理
- 并不是所有 PSP 都支持 webhook 异步通知。有些 PSP 要求商户系统 自行定期轮询,来检查
PENDING状态的交易是否已更新。 - 示例:
- 支付服务在检测到某笔交易为
PENDING后,设置一个轮询任务 (例如每 5 分钟一次 ),调用 PSP 的订单状态查询接口。
- 支付服务在检测到某笔交易为
- 并不是所有 PSP 都支持 webhook 异步通知。有些 PSP 要求商户系统 自行定期轮询,来检查