支付系统架构设计完全指南从零开始,一次性讲清支付系统的架构、核心问题与设计方案

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, -- INIT/PAYING/SUCCESS/FAIL/CLOSED channel VARCHAR(32), -- wechat/alipay/unionpay 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, -- PAY/REFUND/QUERY/CALLBACK request_data TEXT, -- 请求报文(JSON) response_data TEXT, -- 响应报文 status VARCHAR(16), -- SUCCESS/FAIL/PROCESSING 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, -- 首次请求的结果(JSON),重复请求直接返回 created_at DATETIME NOT NULL );

3. 核心难题与解决方案

支付系统面临的问题,本质都是分布式系统中资金安全相关的难题。逐一拆解:

3.1 幂等性 — 最基础也最重要

问题:网络超时后用户重复点击、客户端重试、MQ 重投,都可能让同一笔支付被发起多次。如果没有幂等保护,用户可能被重复扣款。

解决方案——「唯一键 + 数据库事务」是最可靠的方式:

// 核心思路:利用数据库唯一索引天然防重 // 1. 调用方在请求头传入 X-Idempotent-Key // 2. 服务端先查幂等表,已存在则直接返回已有结果 // 3. 不存在则做 INSERT,利用唯一索引防并发 public PaymentResult pay(String key, PayRequest req) { // Step 1: 先查是否已处理 IdempotentRecord record = idempotentMapper.selectByKey(key); if (record != null) { return JSON.parse(record.getResult()); // 直接返回已有结果 } // Step 2: 用 INSERT 竞争——只有一个线程能成功 try { idempotentMapper.insert(key, "PROCESSING"); } catch (DuplicateKeyException e) { // 并发冲突,说明已被另一个线程处理,查询结果返回 record = idempotentMapper.selectByKey(key); return JSON.parse(record.getResult()); } // Step 3: 执行支付逻辑 PaymentResult result = doActualPay(req); // Step 4: 更新幂等记录为最终结果 idempotentMapper.updateResult(key, JSON.toJSONString(result)); return result; }

幂等方案对比

方案原理可靠性适用场景
数据库唯一索引UNIQUE KEY 天然防重★★★★★核心支付链路(首选)
Redis SETNXSETNX 原子操作竞争锁★★★☆☆高并发但对一致性要求稍低的场景
分布式锁Redisson/Zookeeper 锁★★★★☆复杂业务的串行化保护
业务状态判断UPDATE SET WHERE status = INIT★★★★☆配合乐观锁,简单有效

3.2 数据一致性 — 支付单和渠道结果必须对齐

经典问题:用户付了钱(渠道扣款成功),但支付单还是 PAYING;或者渠道回调丢了,用户以为没付成功又付了一次。

解决一致性问题,核心策略有三层:

  • 主路径保护:同步回调 + 异步查询兜底

    渠道支付后会同步回调通知。同时启动延迟查询定时任务,5秒、30秒、5分钟、30分钟各查一次渠道订单状态,直到得到终态。

  • 状态机约束:只有符合规则的状态转换才被允许

    从 PAYING → SUCCESS 或 PAYING → FAIL,用数据库乐观锁(WHERE status = 'PAYING' AND version = oldVersion)保证单一推进路径。

    -- 乐观锁更新:只有当前状态是 PAYING 才能改为 SUCCESS UPDATE payment_order SET status = 'SUCCESS', channel_no = #{channelNo}, version = version + 1 WHERE payment_no = #{paymentNo} AND status = 'PAYING' AND version = #{oldVersion}; -- 如果 affected_rows = 0,说明状态已被他人修改,放弃本次更新
  • 对账兜底:T+1 文件对账查漏补缺

    每天和渠道进行逐笔对账,发现差异(我方有对方无、对方有我方无、金额不一致)后人工介入处理。详见 3.4 节。

  • 3.3 并发扣款 — 不能让余额扣成负数

    问题:用户账户有 100 元,同时发起两笔 80 元的支付请求。如果没有并发控制,两笔可能都成功——余额变成 -60 元。

    三种解决方案,从简单到复杂:

    方案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) { // 1. 验签 — 防止伪造回调 if (!verifySignature(data)) { throw new SecurityException("签名验证失败"); } // 2. 查支付单,判断是否需要处理 PaymentOrder order = paymentMapper.selectByNo(paymentNo); if (order == null) { return; // 支付单不存在,忽略或告警 } // 3. 状态幂等判断 — 已经是终态就不再处理 if (order.getStatus() == "SUCCESS" || order.getStatus() == "FAIL") { return; // 已到终态,直接返回成功(不抛异常,否则渠道会重试) } // 4. 状态机推进 — 用乐观锁更新 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分钟的单,批量关闭

    简单可靠

    延迟取决于扫描频率

    Redis 过期事件

    SET key EX 900秒,监听 key 过期事件触发关单

    轻量无依赖

    过期事件不可靠(会丢)

    生产推荐:延迟队列(主) + 定时扫表(兜底)。延迟队列负责精确关单,定时扫表每 5 分钟跑一次做补偿。双重保障,永不遗漏。

    4. 设计方案对比

    4.1 六种主流设计模式

    支付系统在不同规模、不同场景下,有不同的架构设计模式。这里整理六种主流方案:

    模式1:单体架构 — 支付模块内嵌于业务系统

    // 最简单的形态:支付逻辑直接在订单服务里 // 优势:零额外部署成本,开发快 // 劣势:耦合严重,支付逻辑和业务逻辑搅在一起 // 适用:创业初期、用户量 < 10万、日订单 < 1000 OrderService ├── createOrder() ├── pay() // 支付逻辑直接写这里 ├── handleCallback() └── queryOrder()

    模式2:独立支付服务 — 支付系统独立部署

    // 支付系统独立为一个微服务,通过 RPC/HTTP 暴露 API // 优势:解耦,可独立扩容、独立演进 // 劣势:需要处理分布式事务、网络开销 // 适用:中等规模,日订单 1k~100k payment-service (独立部署) ├── /api/pay/create // 创建支付单 ├── /api/pay/execute // 执行支付 ├── /api/pay/query // 查询状态 ├── /api/pay/refund // 退款 ├── /callback/{channel} // 渠道回调 └── 内部:对账定时任务 / 关单定时任务

    模式3:收银台模式 — 统一支付页 + 多渠道路由

    // 用户看到统一收银台,后端自动选最优渠道 // 优势:用户体验统一,渠道可随时切换 // 核心组件: // 渠道路由器:根据费率、成功率、用户习惯选择渠道 // 渠道适配器:统一接口适配不同渠道 SDK // 风控引擎:支付前风险评估 // 适用:多支付渠道、大用户量的平台 收银台 (Cashier) ├── 聚合支付页面 (H5 / SDK / API) ├── 渠道路由器 │ ├── 费率最优策略 │ ├── 成功率优先策略 │ └── 用户偏好策略 ├── 渠道适配层 │ ├── WechatPayAdapter │ ├── AlipayAdapter │ └── UnionPayAdapter └── 风控引擎 (实时决策)

    模式4:支付网关模式 — 一套接口对接所有渠道

    // 不仅自己用,还开放给其他业务线 / 外部商户 // 优势:复用支付能力,统一管理费率、风控、对账 // 核心额外能力: // 商户管理:多租户隔离、费率配置 // API 鉴权:OAuth2 / AK/SK 签名 // 分账:平台抽佣 + 多方分账 // 适用:平台型业务(电商平台、SaaS平台) 支付网关 (Payment Gateway) ├── 商户管理 │ ├── 商户入驻 & 资质审核 │ ├── 费率配置 (按商户 / 按渠道) │ └── 结算周期配置 (T+1 / T+7 / D0) ├── API 接入层 (开放 API) │ ├── 统一支付接口 │ ├── 统一退款接口 │ └── 统一查询接口 ├── 支付核心 (同模式3) ├── 清结算系统 │ ├── 计费 (计算平台手续费) │ ├── 分账 (多方分润) │ └── 结算 (打款到商户账户) └── 商户对账 (每个商户独立对账文件)

    模式5:事件驱动架构 — 异步解耦 + 最终一致性

    // 通过 MQ 事件驱动,各系统独立消费、独立处理 // 优势:极致解耦,扩展性强,容错性好 // 劣势:调试复杂,需要完善的监控和链路追踪 // 适用:大型系统,多团队协作 支付核心发出事件: PaymentCreated → 风控系统消费 (风险评估) → 营销系统消费 (发放优惠) → 积分系统消费 (增加积分) PaymentSuccess → 订单系统消费 (修改订单状态) → 通知系统消费 (推送消息) → 对账系统消费 (记账) → 数据分析消费 (实时报表) PaymentRefunded → 订单系统消费 (退款状态) → 营销系统消费 (扣回优惠) // 保证事件不丢失: // 1. 支付核心先落库,再发 MQ // 2. MQ 开启事务消息 / 可靠投递 // 3. 消费端做幂等

    模式6:CQRS + 事件溯源 — 资金账户的终极方案

    // 把账户余额看作「事件序列」的聚合结果 // 不是存一个 final_balance,而是记录所有交易事件 // 优势:完整审计轨迹,可重放,天然防篡改 // 劣势:查询余额需聚合计算,复杂度高 // 适用:对审计要求极高、需要完整资金轨迹的系统 // 写模型 (Command):只追加事件,不修改余额 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 | // 读模型 (Query):定期/实时投影当前余额到缓存 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% 的问题

    技术选型推荐

    缓存

    幂等键、渠道配置、费率

    Redis SETNX / Lua / 分布式锁

    消息队列

    异步通知、延迟关单、事件驱动

    RocketMQ 事务消息 + 延迟消息

    Kafka 高吞吐日志/事件流

    监控告警

    成功率、延迟、对账差异

    Prometheus + Grafana

    ELK 日志/链路追踪

    一图总结支付系统的完整知识体系

    支付系统全貌 支付系统知识体系全景图 交易核心 支付/退款/查询 渠道路由 适配/降级/熔断 风控引擎 规则/评分/拦截 清结算 计费/分账/打款 六大核心问题 幂等性 数据一致性 并发扣款 对账 回调处理 异常补偿 五大解决方案 DB唯一索引 乐观锁+状态机 对账+T+1补偿 MQ可靠投递 分布式锁 演进路径 单体 独立服务 收银台 支付网关 事件驱动 基础设施:MySQL + Redis + RocketMQ/Kafka + Prometheus + ELK 开发规范:幂等 → 状态机 → 乐观锁 → 对账 → 监控 → 灰度

    支付系统的本质就一句话

    状态机管生命周期,用幂等防重复,用乐观锁保一致性, 用对账做兜底,用补偿处理异常。记住这五句话,支付系统的核心就通了。