支付系统架构设计完全指南从零开始,一次性讲清支付系统的架构、核心问题与设计方案
1. 支付系统全景
1.1 什么是支付系统
1.2 支付系统在哪一层
2. 核心架构设计
2.1 整体架构分层
2.2 支付层核心流程
2.3 数据模型设计
3. 核心难题与解决方案
3.1 幂等性
3.2 数据一致性
3.3 并发扣款
3.4 对账
3.5 回调处理
3.6 异常处理与补偿
3.7 超时关单
4. 设计方案对比
4.1 六种主流设计模式
4.2 多维度方案选型
5. 最佳实践总结
1. 支付系统全景
1.1 什么是支付系统
支付系统是连接用户、商户、第三方支付渠道和银行的金融基础设施。它处理的是「钱」 ——这是软件系统中容错率最低、一致性要求最高 的领域。简单来说,支付系统要保证一句话:
钱不能多扣、不能少扣、不能丢、不能重复扣。每一分钱的变动都要可追溯、可对账、可审计。
1.2 支付系统在整个系统中的位置
支付系统分层架构
展示业务层、支付层、渠道层和清算层的四层架构关系
用户 / 商户
业务层 — 订单系统、电商系统、会员系统、营销系统
支付层 ★ — 收银台、支付路由、风控、渠道路由、对账、清算
第三方支付: 微信 / 支付宝 / PayPal
直接渠道: 银行卡 / 网银 / Apple Pay
清算层 — 银联 / 网联 / 人行清算系统 / VISA / Mastercard
银行存管账户 / 虚拟账户体系
支付层是最关键的一层 ,它承上(对接业务)启下(对接渠道),需要处理所有关于「钱」的复杂逻辑。本文重点讲的就是这一层。
2. 核心架构设计
2.1 整体架构分层
一个成熟的支付系统内部也是分层的。下面是支付层内部的子层架构 :
支付层内部子层架构
接入层、核心层、渠道层和基础层的四层支付内部架构
接入层 — API 网关 / 鉴权 / 限流 / 协议转换
核心层 ★ — 支付引擎 / 收银台 / 渠道路由 / 风控引擎
(交易流水管理 / 状态机驱动 / 幂等保障 / 分布式事务协调)
渠道适配层 — 各支付渠道 SDK 封装
渠道路由 — 费率 / 成功率 / 降级策略
对账系统
清算结算
账户系统
支撑:MySQL (事务) / Redis (幂等) / MQ (异步) / 分布式锁 / 定时任务
各层职责速查表
层次 核心职责 关键技术
接入层 请求鉴权、签名验证、流量控制、协议适配 API Gateway、OAuth2、Rate Limiter
核心层 支付生命周期管理、状态机、渠道路由、风控决策 状态机模式、责任链、策略模式
渠道层 对接支付宝/微信等SDK,统一渠道路由与降级 适配器模式、熔断器、重试
基础层 对账、清算结算、账户余额管理 定时任务、批量处理、分布式锁
2.2 支付层核心流程 — 状态机驱动的支付生命周期
支付过程本质是一个状态机 。清晰地定义状态和状态转换是支付系统设计的基石。
支付状态机
支付单从创建到成功/失败/关闭的完整状态流转图
INIT (已创建)
用户发起支付
PAYING (支付中)
已调用渠道,等回调
SUCCESS (已支付)
支付成功,可发货
FAIL (支付失败)
可重试或换渠道
CLOSED (已关闭)
超时未支付 / 主动取消
REFUNDING → REFUNDED
实线 = 正常流转 虚线 = 边缘/异常路径
核心原则: 支付状态只能单向推进(除了退款),绝不允许从 SUCCESS 回到 PAYING。所有状态变更必须记录操作日志(operation log),保证数据可审计。
2.3 核心数据模型设计
支付系统的数据模型要支持上面整个状态机的流转。核心三张表:
1. 支付单 (payment_order)
CREATE TABLE payment_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
payment_no VARCHAR(64) NOT NULL UNIQUE ,
order_no VARCHAR(64) NOT NULL ,
user_id BIGINT NOT NULL ,
amount DECIMAL(18,2) NOT NULL ,
currency VARCHAR(8) DEFAULT 'CNY' ,
status VARCHAR(32) NOT NULL ,
channel VARCHAR(32) ,
channel_no VARCHAR(128) ,
version INT DEFAULT 0,
created_at DATETIME NOT NULL ,
updated_at DATETIME NOT NULL ,
INDEX idx_order_no (order_no),
INDEX idx_status_created (status, created_at)
);
2. 支付流水 (payment_transaction)
CREATE TABLE payment_transaction (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
payment_no VARCHAR(64) NOT NULL ,
trans_type VARCHAR(32) NOT NULL ,
request_data TEXT ,
response_data TEXT ,
status VARCHAR(16) ,
error_code VARCHAR(64) ,
error_msg VARCHAR(512) ,
created_at DATETIME NOT NULL ,
INDEX idx_payment_no (payment_no)
);
3. 幂等表 (idempotent_key)
CREATE TABLE idempotent_key (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
idempotent_key VARCHAR(128) NOT NULL UNIQUE ,
payment_no VARCHAR(64) ,
result TEXT ,
created_at DATETIME NOT NULL
);
3. 核心难题与解决方案
支付系统面临的问题,本质都是分布式系统中资金安全 相关的难题。逐一拆解:
3.1 幂等性 — 最基础也最重要
问题: 网络超时后用户重复点击、客户端重试、MQ 重投,都可能让同一笔支付被发起多次。如果没有幂等保护,用户可能被重复扣款。
解决方案——「唯一键 + 数据库事务」是最可靠的方式:
public PaymentResult pay (String key, PayRequest req) {
IdempotentRecord record = idempotentMapper.selectByKey(key);
if (record != null ) {
return JSON.parse(record.getResult());
}
try {
idempotentMapper.insert(key, "PROCESSING" );
} catch (DuplicateKeyException e) {
record = idempotentMapper.selectByKey(key);
return JSON.parse(record.getResult());
}
PaymentResult result = doActualPay(req);
idempotentMapper.updateResult(key, JSON.toJSONString(result));
return result;
}
幂等方案对比
方案 原理 可靠性 适用场景
数据库唯一索引 UNIQUE KEY 天然防重 ★★★★★ 核心支付链路(首选)
Redis SETNX SETNX 原子操作竞争锁 ★★★☆☆ 高并发但对一致性要求稍低的场景
分布式锁 Redisson/Zookeeper 锁 ★★★★☆ 复杂业务的串行化保护
业务状态判断 UPDATE SET WHERE status = INIT ★★★★☆ 配合乐观锁,简单有效
3.2 数据一致性 — 支付单和渠道结果必须对齐
经典问题: 用户付了钱(渠道扣款成功),但支付单还是 PAYING;或者渠道回调丢了,用户以为没付成功又付了一次。
解决一致性问题,核心策略有三层:
主路径保护:同步回调 + 异步查询兜底
渠道支付后会同步回调通知。同时启动延迟查询定时任务,5秒、30秒、5分钟、30分钟各查一次渠道订单状态,直到得到终态。
状态机约束:只有符合规则的状态转换才被允许
从 PAYING → SUCCESS 或 PAYING → FAIL,用数据库乐观锁(WHERE status = 'PAYING' AND version = oldVersion)保证单一推进路径。
UPDATE payment_order
SET status = 'SUCCESS' , channel_no = #{channelNo}, version = version + 1
WHERE payment_no = #{paymentNo}
AND status = 'PAYING'
AND version = #{oldVersion};
对账兜底:T+1 文件对账查漏补缺
每天和渠道进行逐笔对账,发现差异(我方有对方无、对方有我方无、金额不一致)后人工介入处理。详见 3.4 节。
3.3 并发扣款 — 不能让余额扣成负数
问题: 用户账户有 100 元,同时发起两笔 80 元的支付请求。如果没有并发控制,两笔可能都成功——余额变成 -60 元。
三种解决方案,从简单到复杂:
方案1:悲观锁
SELECT ... FOR UPDATE 锁住用户账户行,扣完才释放。
优点:绝对安全
缺点:并发低,容易死锁
适用:低频、高价值场景
方案2:乐观锁
用 version 字段,UPDATE SET balance = balance - 80, version = version+1 WHERE version = oldVer AND balance >= 80
优点:无锁,并发高
缺点:冲突需重试
适用:读多写少,余额检查
方案3:Redis 原子操作
Lua 脚本原子执行扣款:IF balance >= amount THEN DECRBY
优点:性能极高
缺点:需关注持久化
适用:高并发积分/虚拟币
最佳实践: 生产环境通常乐观锁 + 行级 CHECK 约束 双保险。SQL 中加上 AND balance >= #{amount} 条件,即使乐观锁版本号碰巧一致,余额不够也不会扣成负数。
3.4 对账 — 最后一道防线
对账是支付系统最重要的事后保障机制 。即便前面所有机制都出了问题,对账也能发现并纠正。
对账流程
T+1对账的三步流程:获取账单、逐笔比对、差异处理
我方账单 (payment_order / transaction)
渠道账单 (对账文件 / FTP下载)
逐笔比对:金额 + 交易号 + 状态
对平 → 无异常
差异 → 人工处理
长款/短款 → 调账
差异类型:我方有对方无 / 对方有我方无 / 金额不一致 / 状态不一致
对账差异处理决策表
差异类型 含义 处理方式
我方有、对方无 我们记录了支付,渠道没有 可能是我们重复记账,需核实后冲正
对方有、我方无 用户付了钱但系统没记录 补单——最严重,必须立刻处理
金额不一致 双方记录的金额不同 以渠道为准,差额补录调账单
状态不一致 我方记录成功,对方记录失败 以渠道终态为准,更新本地状态
3.5 回调处理 — 异步通知的可靠性
渠道回调的特点: 不可靠(可能丢)、乱序(可能先收到支付成功再收到支付中)、重复(同一笔多次回调)、延迟(可能T+1才到)。
处理回调的正确姿势:
public void handleCallback (String paymentNo, CallbackData data) {
if (!verifySignature(data)) {
throw new SecurityException("签名验证失败" );
}
PaymentOrder order = paymentMapper.selectByNo(paymentNo);
if (order == null ) {
return ;
}
if (order.getStatus() == "SUCCESS" || order.getStatus() == "FAIL" ) {
return ;
}
int rows = paymentMapper.updateStatus(
paymentNo,
data.getStatus(),
"PAYING" ,
order.getVersion()
);
if (rows == 0) {
log.warn ("状态更新冲突, paymentNo={}" , paymentNo);
}
}
3.6 异常处理与补偿 — 出了事怎么补救
异常分类与处理矩阵
异常场景 现象 处理策略
调用渠道超时
不知道渠道是否扣款
查询补偿: 定时查渠道订单状态,一直查到终态
渠道返回「处理中」
异步处理的渠道(如网银)
轮询查询: 按指数退避重试查询(1s→3s→10s→30s→5min)
回调丢失
渠道扣款成功但我们不知道
主动查询 + 对账: 定时扫 PAYING 超过 N 分钟的单,主动查渠道
支付成功后发货失败
钱扣了但没发货
退款: 自动退款(无法自动的进人工工单)
渠道故障
微信支付挂了
熔断 + 降级: 自动切到备用渠道(支付宝→微信→银联)
3.7 超时关单 — 定时关闭未支付的订单
用户下单后如果长时间不支付,需要自动关闭订单、释放库存。这里有三种实现方式:
定时扫表
定时任务扫描 status=INIT 且超过15分钟的单,批量关闭
简单可靠
延迟取决于扫描频率
延迟队列
下单时发一个延迟消息到 RocketMQ/Kafka,15分钟后消费
精准到时
依赖MQ可靠性
Redis 过期事件
SET key EX 900秒,监听 key 过期事件触发关单
轻量无依赖
过期事件不可靠(会丢)
生产推荐: 延迟队列(主) + 定时扫表(兜底)。延迟队列负责精确关单,定时扫表每 5 分钟跑一次做补偿。双重保障,永不遗漏。
4. 设计方案对比
4.1 六种主流设计模式
支付系统在不同规模、不同场景下,有不同的架构设计模式。这里整理六种主流方案:
模式1:单体架构 — 支付模块内嵌于业务系统
OrderService
├── createOrder()
├── pay()
├── handleCallback()
└── queryOrder()
模式2:独立支付服务 — 支付系统独立部署
payment-service (独立部署)
├── /api/pay/create
├── /api/pay/execute
├── /api/pay/query
├── /api/pay/refund
├── /callback/{channel}
└── 内部:对账定时任务 / 关单定时任务
模式3:收银台模式 — 统一支付页 + 多渠道路由
收银台 (Cashier)
├── 聚合支付页面 (H5 / SDK / API)
├── 渠道路由器
│ ├── 费率最优策略
│ ├── 成功率优先策略
│ └── 用户偏好策略
├── 渠道适配层
│ ├── WechatPayAdapter
│ ├── AlipayAdapter
│ └── UnionPayAdapter
└── 风控引擎 (实时决策)
模式4:支付网关模式 — 一套接口对接所有渠道
支付网关 (Payment Gateway)
├── 商户管理
│ ├── 商户入驻 & 资质审核
│ ├── 费率配置 (按商户 / 按渠道)
│ └── 结算周期配置 (T+1 / T+7 / D0)
├── API 接入层 (开放 API)
│ ├── 统一支付接口
│ ├── 统一退款接口
│ └── 统一查询接口
├── 支付核心 (同模式3)
├── 清结算系统
│ ├── 计费 (计算平台手续费)
│ ├── 分账 (多方分润)
│ └── 结算 (打款到商户账户)
└── 商户对账 (每个商户独立对账文件)
模式5:事件驱动架构 — 异步解耦 + 最终一致性
支付核心发出事件:
PaymentCreated → 风控系统消费 (风险评估)
→ 营销系统消费 (发放优惠)
→ 积分系统消费 (增加积分)
PaymentSuccess → 订单系统消费 (修改订单状态)
→ 通知系统消费 (推送消息)
→ 对账系统消费 (记账)
→ 数据分析消费 (实时报表)
PaymentRefunded → 订单系统消费 (退款状态)
→ 营销系统消费 (扣回优惠)
模式6:CQRS + 事件溯源 — 资金账户的终极方案
AccountEvent 表:
| id | account_id | type | amount | balance_after | timestamp |
| 1 | A001 | DEPOSIT | +100 | 100 | T1 |
| 2 | A001 | WITHDRAW | -30 | 70 | T2 |
| 3 | A001 | PAY | -50 | 20 | T3 |
AccountSnapshot (Redis):
account_id: "A001" → { balance: 20, version: 3 }
4.2 多维度方案选型指南
维度
单体
独立服务
收银台
支付网关
事件驱动
CQRS
开发复杂度
★☆☆☆☆
★★☆☆☆
★★★☆☆
★★★★☆
★★★★★
★★★★★
运维复杂度
★☆☆☆☆
★★☆☆☆
★★★☆☆
★★★★☆
★★★★★
★★★★☆
并发能力
低
中
高
高
高
高
扩展性
差
中
好
好
极好
极好
审计能力
弱
弱
中
中
中
极强
团队规模
1-3人
3-5人
5-10人
10-20人
15-30人
20+人
代表场景
内部工具
垂直电商
O2O平台
电商平台
大型平台
金融核心
选型核心逻辑: 大部分系统从模式2(独立服务)起步,随着渠道增多演进到模式3(收银台),平台化后演进到模式4(支付网关),到足够大时引入事件驱动。模式6(CQRS)只在金融核心账务系统(银行、证券)中使用。不要一上来就选最复杂的方案 ——支付系统的复杂度应该和业务规模同步增长。
5. 最佳实践总结
设计原则清单
# 原则 说明
1
资金安全第一
任何情况下不能丢钱、多扣、少扣。所有优化都以不牺牲资金安全为前提
2
幂等是基石
所有对外接口必须幂等。唯一索引 + INSERT 是最可靠的幂等方案
3
状态机驱动
支付单状态严格按状态机流转,状态变更用乐观锁保护,记录操作日志
4
对账兜底
每天 T+1 对账,所有差异必须处理。对账是最后一道防线
5
回调不可信
渠道回调可能丢、可能重复、可能乱序。必须配合主动查询和对账
6
扣款要悲观
涉及资金扣减的操作都用悲观锁(FOR UPDATE)或在 SQL 中加余额检查
7
异步要兜底
MQ / 延迟队列 / 定时任务,能做两层保障的不要只做一层
8
可观测性
全链路 TraceID、关键节点打点、异常告警。出了事要能快速定位
9
灰度 + 开关
新渠道、新功能要有灰度能力和一键关停开关。支付不能出大故障
10
简单优先
不到万不得已不要引入分布式事务(Seata等)。乐观锁 + 补偿 + 对账能解决 95% 的问题
技术选型推荐
数据存储
支付主数据、流水、对账
MySQL 事务 + 行锁 + 主从
TiDB 大规模时分布式扩展
缓存
幂等键、渠道配置、费率
Redis SETNX / Lua / 分布式锁
消息队列
异步通知、延迟关单、事件驱动
RocketMQ 事务消息 + 延迟消息
Kafka 高吞吐日志/事件流
监控告警
成功率、延迟、对账差异
Prometheus + Grafana
ELK 日志/链路追踪
一图总结支付系统的完整知识体系
支付系统全貌
支付系统知识体系全景图
交易核心
支付/退款/查询
渠道路由
适配/降级/熔断
风控引擎
规则/评分/拦截
清结算
计费/分账/打款
六大核心问题
幂等性
数据一致性
并发扣款
对账
回调处理
异常补偿
五大解决方案
DB唯一索引
乐观锁+状态机
对账+T+1补偿
MQ可靠投递
分布式锁
演进路径
单体
→
独立服务
→
收银台
→
支付网关
→
事件驱动
基础设施:MySQL + Redis + RocketMQ/Kafka + Prometheus + ELK
开发规范:幂等 → 状态机 → 乐观锁 → 对账 → 监控 → 灰度
支付系统的本质就一句话
用状态机 管生命周期,用幂等 防重复,用乐观锁 保一致性,
用对账 做兜底,用补偿 处理异常。记住这五句话,支付系统的核心就通了。