支付网关系统设计文档
支付网关系统设计文档
项目类型:金融系统设计实战
设计级别:高级架构师 / 系统设计面试
日期:2026-04-13
作者:MomoWeb3 架构实战
一、需求分析
1.1 面试场景还原
面试官:请设计一个在线支付网关系统,日均百万笔交易,支持多支付方式(信用卡/借记卡/支付宝/微信),需要对账和退款功能。你会怎么设计?
这是一道典型的系统设计面试题。好的候选人不会直接画图,而是先做需求澄清。
1.2 需求澄清问题表
| # | 澄清问题 | 假设回答 | 对设计的影响 |
|---|---|---|---|
| 1 | 日均交易量级?峰值是日均的几倍? | 日均100万笔,峰值10倍(大促场景) | 需要弹性扩容能力,数据库分片策略 |
| 2 | 单笔交易金额范围? | 0.01元~100万元 | 大额交易需要额外风控审核流程 |
| 3 | 支持哪些支付方式?未来是否扩展? | 信用卡/借记卡/支付宝/微信,未来扩展银联云闪付、Apple Pay | 通道适配层需要可插拔设计 |
| 4 | 商户数量和规模? | 1000+商户,Top 10商户占80%交易量 | 需要热点商户隔离,商户级限流 |
| 5 | 延迟要求? | 支付请求到返回结果 < 3秒 | 异步化非关键路径,缓存路由配置 |
| 6 | 可用性要求? | 99.99%(年停机 < 53分钟) | 多机房部署,通道故障自动降级 |
| 7 | 资金安全要求? | 不能多扣、不能少扣、不能重复扣 | 幂等机制 + 对账兜底 + 状态机严格校验 |
| 8 | 是否需要分账/结算? | 需要T+1结算给商户 | 结算服务独立设计,对账驱动结算 |
| 9 | 合规要求? | PCI-DSS(卡数据),央行备付金管理 | 卡号脱敏、加密存储、审计日志 |
| 10 | 跨境支付是否在scope内? | 第一期不含,但架构需要预留 | 多币种字段预留,汇率服务接口预留 |
1.3 功能需求清单
| 模块 | 功能 | 优先级 | 说明 |
|---|---|---|---|
| 收单 | 统一下单 | P0 | 商户提交支付请求,返回支付凭证 |
| 收单 | 支付查询 | P0 | 商户主动查询支付结果 |
| 收单 | 异步通知 | P0 | 支付结果异步回调商户 |
| 退款 | 全额退款 | P0 | 原路退回全部金额 |
| 退款 | 部分退款 | P1 | 退回部分金额,支持多次退款 |
| 对账 | 日终对账 | P0 | T+1与各通道核对交易明细 |
| 对账 | 差错处理 | P0 | 对账差异的自动/人工处理 |
| 路由 | 通道路由 | P0 | 根据规则选择最优支付通道 |
| 路由 | 通道管理 | P1 | 通道上线/下线/维护窗口管理 |
| 结算 | 商户结算 | P1 | T+1将资金结算给商户 |
| 监控 | 交易监控 | P0 | 成功率、延迟、异常告警 |
| 风控 | 基础风控 | P0 | 限额、黑名单、频次控制 |
1.4 非功能需求
| 维度 | 指标 | 说明 |
|---|---|---|
| 性能 | 延迟 < 3秒(P99) | 从商户请求到返回支付结果 |
| 性能 | 吞吐量 ≥ 1000 TPS | 日均100万笔 ÷ 86400 ≈ 12 TPS,峰值需支撑10倍 |
| 可用性 | 99.99% | 年停机时间 < 53分钟 |
| 可靠性 | 资金零差错 | 不多扣、不少扣、不重复扣 |
| 扩展性 | 100万→10亿笔/天 | 水平扩展,分库分表 |
| 安全性 | PCI-DSS Level 1 | 卡号加密存储、传输加密、访问控制 |
| 幂等性 | 重复请求安全 | 同一请求多次提交只产生一笔交易 |
| 可审计 | 全链路日志 | 满足央行监管和审计要求 |
1.5 约束条件和范围
- 范围内:收单、退款、对账、路由、通知、基础风控
- 范围外:跨境支付(预留接口)、银行直连清算、会员体系
- 技术约束:Java/Spring生态、MySQL + Redis、RocketMQ
- 合规约束:PCI-DSS、支付业务许可证、备付金管理
二、高层架构设计
2.1 C4 Context — 系统上下文
支付网关系统处于商户与金融基础设施之间的核心位置:
- 商户系统(Merchant System):通过统一API提交支付/退款请求,接收异步通知
- 终端用户(End User):通过商户的收银台间接与支付网关交互(跳转支付页面、输入密码等)
- PSP通道(Payment Service Providers):支付宝、微信支付、银联等第三方支付机构,处理实际的资金划转
- 银行系统(Bank):通过PSP间接交互,或通过银行直连通道处理银行卡交易
- 风控系统(Risk Control):独立的风控引擎,提供实时风险评估(可以是内部子系统或外部服务)
- 记账系统(Ledger):维护资金账本,记录每笔交易的借贷方向,确保资金平衡
- 通知系统(Notification):负责将支付结果通过HTTP回调推送给商户
2.2 C4 Container — 容器架构
支付网关内部划分为以下容器(Container),每个容器是一个独立部署单元:
| Container | 职责 | 技术选型 | 数据存储 |
|---|---|---|---|
| API Gateway | 统一入口、鉴权、限流、签名验签 | Spring Cloud Gateway / Nginx | Redis(限流计数) |
| Payment Core Service | 支付核心:下单、状态管理、幂等控制 | Spring Boot | MySQL(payment_order, payment_transaction) |
| Route Engine | 通道路由选择:根据规则和实时数据选择最优通道 | Spring Boot | Redis(通道健康度)+ MySQL(channel_config) |
| Risk Gateway | 风控网关:调用风控引擎做实时风险评估 | Spring Boot | Redis(频次计数) |
| Channel Adapter Layer | 通道适配:封装各PSP的差异,提供统一调用接口 | Spring Boot | MySQL(channel_log) |
| Ledger Client | 记账客户端:调用记账系统记录资金变动 | Spring Boot | 调用记账系统API |
| Reconcile Service | 对账服务:T+1核对交易,发现差异 | Spring Boot + XXL-JOB | MySQL(reconcile_record)+ 文件存储 |
| Settlement Service | 结算服务:根据对账结果计算商户应结金额 | Spring Boot + XXL-JOB | MySQL(settlement_record) |
| Notification Service | 通知服务:异步推送支付结果给商户 | Spring Boot + RocketMQ | MySQL(notify_record)+ Redis |
| Message Queue | 异步解耦:支付结果→通知/记账/对账 | RocketMQ | - |
2.3 核心流程 — Happy Path
一笔正常支付的完整流程如下:
- 商户下单:商户系统调用
POST /api/v1/payment/create,携带商户订单号、金额、支付方式等参数 - 签名验证:API Gateway 验证商户签名、检查限流,转发到 Payment Core
- 幂等检查:Payment Core 根据「商户ID + 商户订单号」做幂等校验,防止重复下单
- 创建订单:生成平台支付订单号,状态设为
CREATED,写入payment_order表 - 风控检查:调用 Risk Gateway 做实时风控(限额、黑名单、频次),风控通过则继续
- 路由选择:调用 Route Engine,根据支付方式、通道成功率、成本、限额选择最优通道
- 通道调用:Channel Adapter 将请求转换为目标PSP的格式,发送到支付宝/微信/银联
- 状态更新:收到PSP同步返回后,更新订单状态为
PROCESSING(或直接SUCCESS) - 异步回调:PSP异步通知支付结果,Channel Adapter 解析后更新订单状态为
SUCCESS - 记账:发送MQ消息,Ledger Client 异步记录借贷分录
- 通知商户:发送MQ消息,Notification Service 异步通知商户(指数退避重试)
- 返回结果:向商户返回支付凭证(如支付链接或二维码URL)
三、数据模型设计
3.1 核心表结构
payment_order(支付订单表)
CREATE TABLE payment_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
payment_order_no VARCHAR(32) NOT NULL COMMENT '平台支付订单号',
merchant_id VARCHAR(32) NOT NULL COMMENT '商户ID',
merchant_order_no VARCHAR(64) NOT NULL COMMENT '商户订单号',
amount BIGINT NOT NULL COMMENT '支付金额(分)',
currency VARCHAR(3) DEFAULT 'CNY' COMMENT '币种ISO-4217',
payment_method VARCHAR(20) NOT NULL COMMENT '支付方式:ALIPAY/WECHAT/BANK_CARD',
channel_code VARCHAR(32) COMMENT '实际使用的通道编码',
status VARCHAR(20) NOT NULL DEFAULT 'CREATED' COMMENT '订单状态',
notify_url VARCHAR(256) COMMENT '商户回调地址',
expire_time DATETIME COMMENT '订单过期时间',
paid_time DATETIME COMMENT '支付成功时间',
channel_order_no VARCHAR(64) COMMENT '通道方订单号',
extra_info JSON COMMENT '扩展信息',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_merchant_order (merchant_id, merchant_order_no),
UNIQUE KEY uk_payment_order_no (payment_order_no),
INDEX idx_status_created (status, created_at),
INDEX idx_merchant_id (merchant_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付订单表';
payment_transaction(支付交易流水表)
CREATE TABLE payment_transaction (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
transaction_no VARCHAR(32) NOT NULL COMMENT '交易流水号',
payment_order_no VARCHAR(32) NOT NULL COMMENT '关联支付订单号',
channel_code VARCHAR(32) NOT NULL COMMENT '通道编码',
transaction_type VARCHAR(20) NOT NULL COMMENT '交易类型:PAY/REFUND',
amount BIGINT NOT NULL COMMENT '交易金额(分)',
status VARCHAR(20) NOT NULL COMMENT '交易状态',
channel_request TEXT COMMENT '通道请求报文(脱敏)',
channel_response TEXT COMMENT '通道响应报文(脱敏)',
channel_order_no VARCHAR(64) COMMENT '通道方流水号',
error_code VARCHAR(32) COMMENT '错误码',
error_msg VARCHAR(256) COMMENT '错误信息',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_transaction_no (transaction_no),
INDEX idx_payment_order (payment_order_no),
INDEX idx_channel_order (channel_code, channel_order_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付交易流水表';
channel_config(通道配置表)
CREATE TABLE channel_config (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
channel_code VARCHAR(32) NOT NULL COMMENT '通道编码:ALIPAY_APP/WECHAT_JSAPI等',
channel_name VARCHAR(64) NOT NULL COMMENT '通道名称',
payment_method VARCHAR(20) NOT NULL COMMENT '支付方式',
fee_rate DECIMAL(6,4) NOT NULL COMMENT '费率(如0.0060表示0.6%)',
min_amount BIGINT DEFAULT 1 COMMENT '单笔最小金额(分)',
max_amount BIGINT DEFAULT 99999999 COMMENT '单笔最大金额(分)',
daily_limit BIGINT COMMENT '日限额(分)',
priority INT DEFAULT 100 COMMENT '静态优先级(越小越优先)',
weight INT DEFAULT 100 COMMENT '动态权重',
status VARCHAR(10) DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE/INACTIVE/MAINTENANCE',
config_json JSON COMMENT '通道私有配置(密钥/URL等,加密存储)',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_channel_code (channel_code),
INDEX idx_payment_method (payment_method, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通道配置表';
reconcile_record(对账记录表)
CREATE TABLE reconcile_record (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
reconcile_date DATE NOT NULL COMMENT '对账日期',
channel_code VARCHAR(32) NOT NULL COMMENT '通道编码',
payment_order_no VARCHAR(32) COMMENT '平台订单号',
channel_order_no VARCHAR(64) COMMENT '通道订单号',
our_amount BIGINT COMMENT '我方金额(分)',
channel_amount BIGINT COMMENT '通道方金额(分)',
our_status VARCHAR(20) COMMENT '我方状态',
channel_status VARCHAR(20) COMMENT '通道方状态',
reconcile_result VARCHAR(20) NOT NULL COMMENT '对账结果:MATCHED/MISMATCH/OUR_MISSING/CHANNEL_MISSING',
handle_status VARCHAR(20) DEFAULT 'PENDING' COMMENT '处理状态:PENDING/AUTO_FIXED/MANUAL_REVIEW/RESOLVED',
handle_remark VARCHAR(512) COMMENT '处理备注',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_date_channel_order (reconcile_date, channel_code, payment_order_no),
INDEX idx_result (reconcile_result, handle_status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='对账记录表';
refund_order(退款订单表)
CREATE TABLE refund_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
refund_order_no VARCHAR(32) NOT NULL COMMENT '退款订单号',
payment_order_no VARCHAR(32) NOT NULL COMMENT '原支付订单号',
merchant_id VARCHAR(32) NOT NULL COMMENT '商户ID',
merchant_refund_no VARCHAR(64) NOT NULL COMMENT '商户退款单号',
refund_amount BIGINT NOT NULL COMMENT '退款金额(分)',
original_amount BIGINT NOT NULL COMMENT '原支付金额(分)',
refund_reason VARCHAR(256) COMMENT '退款原因',
channel_code VARCHAR(32) NOT NULL COMMENT '通道编码',
channel_refund_no VARCHAR(64) COMMENT '通道退款流水号',
status VARCHAR(20) NOT NULL DEFAULT 'CREATED' COMMENT '退款状态',
refunded_time DATETIME COMMENT '退款成功时间',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_merchant_refund (merchant_id, merchant_refund_no),
UNIQUE KEY uk_refund_order_no (refund_order_no),
INDEX idx_payment_order (payment_order_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='退款订单表';
3.2 表关系
payment_order (1) ──→ (N) payment_transaction (一笔订单可能多次尝试不同通道)
payment_order (1) ──→ (N) refund_order (一笔订单可能多次部分退款)
payment_order (1) ──→ (N) reconcile_record (对账关联)
channel_config (1) ──→ (N) payment_transaction (通道配置关联交易)
3.3 分库分表策略
| 表 | 分片键 | 分片数量 | 策略 |
|---|---|---|---|
| payment_order | merchant_id | 64库 × 16表 | 保证同一商户的数据在同一分片 |
| payment_transaction | payment_order_no | 与payment_order相同分片 | 保证订单与流水在同一库 |
| refund_order | payment_order_no | 与payment_order相同分片 | 保证退款与原单同库查询 |
| reconcile_record | reconcile_date + channel_code | 按天分区 | 对账是按天批量处理 |
| channel_config | 不分片 | 全量缓存 | 数据量小,使用Redis缓存 |
分片键选择理由:以 merchant_id 为分片键,保证同一商户的所有数据落在同一分片,避免跨库查询。订单号中嵌入商户分片路由信息(如订单号前4位为merchant_id哈希值),支持通过订单号直接路由。
3.4 状态机定义
支付订单状态机
CREATED ──→ PROCESSING ──→ SUCCESS
│ │
│ ├──→ FAILED
│ │
│ └──→ TIMEOUT ──→ (对账补单)→ SUCCESS / CLOSED
│
└──→ CLOSED(超时未支付关闭)
SUCCESS ──→ REFUNDING ──→ REFUNDED
│
└──→ REFUND_FAILED
状态转换规则:
| 当前状态 | 事件 | 目标状态 | 前置条件 |
|---|---|---|---|
| CREATED | 提交通道 | PROCESSING | 风控通过、路由成功 |
| CREATED | 超时关闭 | CLOSED | 超过expire_time |
| PROCESSING | 通道成功 | SUCCESS | 通道返回成功 |
| PROCESSING | 通道失败 | FAILED | 通道返回明确失败 |
| PROCESSING | 通道超时 | TIMEOUT | 超过通道超时时间 |
| TIMEOUT | 对账补单 | SUCCESS / CLOSED | T+1对账确认结果 |
| SUCCESS | 发起退款 | REFUNDING | 退款金额 ≤ 可退金额 |
| REFUNDING | 退款成功 | REFUNDED(全额)/ SUCCESS(部分) | 通道返回退款成功 |
| REFUNDING | 退款失败 | REFUND_FAILED | 通道返回退款失败 |
四、核心组件详细设计
4.1 通道路由引擎
路由因子
| 因子 | 权重 | 数据来源 | 更新频率 |
|---|---|---|---|
| 通道成功率 | 40% | 最近1小时交易成功率 | 实时计算(滑动窗口) |
| 通道成本(费率) | 25% | channel_config表 | 配置变更时更新 |
| 通道限额 | 20% | 已用额度/剩余额度 | 实时计算 |
| 通道可用性 | 15% | 熔断器状态 | 实时(Sentinel/自建) |
路由算法 — 加权评分
Score(channel) = W_success × normalize(success_rate)
+ W_cost × normalize(1 - fee_rate)
+ W_limit × normalize(remaining_limit)
+ W_availability × availability_flag
选择 Score 最高的通道。
normalize(x):归一化到 [0, 1] 区间availability_flag:0(熔断中)或 1(正常)
降级策略 — 熔断器模式
采用三态熔断器(Closed → Open → Half-Open):
- Closed(正常):错误率 < 50% 或 最近5分钟成功 ≥ 10笔
- Open(熔断):最近1分钟错误率 > 50% 且 请求 ≥ 20笔,自动切换到备选通道
- Half-Open(探测):熔断60秒后放行10%流量探测,成功则恢复,失败继续熔断
降级顺序:主通道 → 备选通道 → 兜底通道 → 拒绝并告警
4.2 幂等机制
幂等键设计
- 幂等键:
merchant_id + merchant_order_no(商户维度唯一) - 存储:利用
payment_order表的唯一索引uk_merchant_order做天然去重 - 辅助:Redis Set 做前置快速判断(缓存已处理的幂等键,TTL = 24小时)
幂等处理流程
1. 接收请求 → 提取幂等键(merchant_id + merchant_order_no)
2. Redis SISMEMBER 快速判断:
- 存在 → 查询已有订单状态,返回已有结果(幂等返回)
- 不存在 → 继续
3. MySQL INSERT(依赖唯一索引):
- 成功 → 新订单,继续流程
- 唯一键冲突 → 并发重复请求,查询已有结果返回
4. 流程结束后 → Redis SADD 加入幂等键缓存
状态机防重入
除了幂等键,还通过状态机防止重复处理:
- 状态更新使用乐观锁:
UPDATE payment_order SET status = 'SUCCESS' WHERE payment_order_no = ? AND status = 'PROCESSING' - 只有
affected_rows = 1才继续后续处理(记账、通知) - 保证即使收到重复回调,也只处理一次
4.3 对账引擎
T+1 对账流程
Step 1: 文件获取(每天凌晨2:00)
→ 从各PSP下载前一天的交易对账文件(CSV/TXT/Excel)
→ 解析为统一格式:[通道订单号, 金额, 状态, 时间]
Step 2: 逐笔比对
→ 遍历通道文件的每笔交易
→ 用通道订单号关联我方 payment_transaction 记录
→ 比对金额、状态是否一致
Step 3: 差异标记
→ MATCHED:双方一致
→ MISMATCH:金额或状态不一致
→ OUR_MISSING:通道有而我方无(漏单)
→ CHANNEL_MISSING:我方有而通道无(掉单)
Step 4: 差错处理(见 ADR-003)
→ 小额自动处理 + 大额人工审核 + 异常升级
Step 5: 报表生成
→ 输出对账报表:总笔数、匹配率、差异明细
→ 发送告警(匹配率 < 99.9% 时)
自动平账规则
| 差异类型 | 金额阈值 | 自动处理方式 |
|---|---|---|
| OUR_MISSING(我方漏单) | ≤ 100元 | 自动补单,状态设为SUCCESS |
| CHANNEL_MISSING(我方多单) | ≤ 100元 | 自动冲正,状态设为CLOSED |
| MISMATCH(金额差异) | 差额 ≤ 1元 | 以通道方金额为准,调整我方记录 |
| 其他 | 任意 | 标记为 MANUAL_REVIEW,通知运营 |
4.4 通道适配层
适配器模式设计
ChannelAdapter(统一接口)
├── AlipayAdapter — 支付宝通道适配
├── WechatAdapter — 微信支付通道适配
├── UnionPayAdapter — 银联通道适配
└── BankCardAdapter — 银行卡直连通道适配
统一接口抽象
public interface ChannelAdapter {
/** 发起支付 */
ChannelPayResponse pay(ChannelPayRequest request);
/** 支付查询 */
ChannelQueryResponse query(ChannelQueryRequest request);
/** 发起退款 */
ChannelRefundResponse refund(ChannelRefundRequest request);
/** 解析异步通知 */
ChannelNotifyResult parseNotify(HttpServletRequest request);
/** 下载对账文件 */
ReconcileFile downloadBill(String date);
}
每个适配器负责:
- 请求参数转换(统一格式 → PSP特有格式)
- 签名生成(各PSP签名算法不同)
- 响应解析(PSP返回 → 统一格式)
- 异常处理(PSP错误码 → 统一错误码)
通道管理
- 上线:新增 channel_config 记录,先设为 INACTIVE,测试通过后 ACTIVE
- 下线:设为 INACTIVE,Route Engine 不再选择该通道
- 维护窗口:设为 MAINTENANCE + 设置维护时间窗,窗口期内自动跳过
五、深度问题
5.1 性能优化
热点商户处理:
- Top 10 商户占 80% 交易量,可能导致分库分表热点
- 方案:热点商户独立分片(VIP分片),或采用「商户ID + 时间」混合分片键分散热点
数据库优化:
- 读写分离:支付写主库,查询走从库(注意同步延迟)
- 连接池:HikariCP,核心参数
maxPoolSize = CPU核数 × 2 + 磁盘数 - 索引:覆盖索引减少回表,避免
SELECT *
缓存策略:
- 通道配置:全量缓存到 Redis + 本地缓存(Caffeine),变更时 MQ 广播刷新
- 幂等键:Redis Set 前置判断,减少数据库查询
- 路由结果:短期缓存(10秒),同一商户同一支付方式不反复计算
异步化:
- 记账:MQ 异步(不影响支付响应时间)
- 通知:MQ 异步 + 指数退避重试(1s/2s/4s/8s/.../最大24小时)
- 对账/结算:定时任务离线处理
5.2 高可用
多机房部署:
- 同城双活 + 异地灾备(两地三中心)
- 数据库:同城 Semi-Sync Replication,异地异步复制
- 流量调度:DNS + GSLB 实现机房级切换
通道故障降级:
- 熔断器自动降级到备选通道(用户无感知)
- 所有通道不可用时:返回「支付繁忙,请稍后重试」,而非报错
- 降级期间持续探测,恢复后自动切回
限流熔断:
- 全局限流:API Gateway 层限制总 QPS
- 商户级限流:防止单个商户打满系统
- 通道级限流:不超过PSP允许的 QPS
- 工具:Sentinel / 自建滑动窗口
5.3 数据一致性 — 三重保障
这是支付系统最核心的设计要点:
第一重:本地事务
→ 支付订单状态更新 + 交易流水写入在同一个数据库事务中
→ 保证单库内的数据一致性
第二重:异步通知 + 主动查询
→ PSP 异步通知 + 定时轮询(每5分钟查询 PROCESSING 状态超过10分钟的订单)
→ 保证最终一致性
第三重:T+1 对账
→ 日终对账发现所有差异,是最后的安全网
→ 理论上只要对账100%覆盖,就不会有资金差错
为什么需要三重?
- 第一重解决正常场景
- 第二重解决通知丢失/延迟的场景
- 第三重解决所有异常场景的兜底(包括系统bug导致的状态不一致)
5.4 安全合规
PCI-DSS 核心要求:
- 卡号传输:TLS 1.2+
- 卡号存储:AES-256 加密,密钥分段管理(HSM)
- 卡号展示:只显示后4位(
**** **** **** 1234) - 日志:禁止记录完整卡号和CVV
防重放:
- 每个请求携带
nonce(一次性随机串)+timestamp - 服务端校验:timestamp 与服务器时间差 < 5分钟
- nonce 存入 Redis(TTL = 5分钟),重复 nonce 直接拒绝
签名验签:
- 商户请求:RSA-SHA256 签名(商户用私钥签,平台用公钥验)
- 平台通知:同样签名(平台用私钥签,商户用公钥验)
- 防止请求篡改
5.5 监控告警
| 指标 | 采集方式 | 告警阈值 | 优先级 |
|---|---|---|---|
| 支付成功率 | 实时计算(1分钟窗口) | < 95% | P0 |
| 支付延迟 P99 | Prometheus Histogram | > 5秒 | P1 |
| 通道健康度 | 熔断器状态 | 任一通道熔断 | P0 |
| 资金对账率 | 日终对账报表 | < 99.99% | P0 |
| QPS | API Gateway 统计 | > 阈值 80% | P1 |
| 通知成功率 | Notification Service | < 99% | P1 |
| 数据库连接池 | HikariCP Metrics | 使用率 > 80% | P2 |
告警升级机制:
- P0:立即电话 + 短信通知值班人员,15分钟未响应升级到主管
- P1:短信 + 企业微信/钉钉通知,30分钟未响应升级
- P2:企业微信/钉钉通知
5.6 演进路线
Phase 1(当前):单体应用
→ 单机房,MySQL主从,支持日均100万笔
→ 适用于创业期,快速迭代
Phase 2:微服务拆分
→ Payment Core / Route / Channel / Reconcile 独立部署
→ 引入 RocketMQ 解耦,Redis 缓存
→ 支持日均1000万笔
Phase 3:多机房
→ 同城双活,分库分表(ShardingSphere)
→ 通道适配层独立集群
→ 支持日均1亿笔
Phase 4:全球化
→ 多地域部署(中国/东南亚/欧洲)
→ 多币种支持,接入当地PSP
→ 合规适配(GDPR/PSD2/MiCA)
→ 支持日均10亿笔
六、架构决策记录(摘要)
| ADR | 标题 | 决策 | 详见 |
|---|---|---|---|
| ADR-001 | 幂等方案选择 | 商户订单号去重 + Redis辅助 | ADR-001 |
| ADR-002 | 通道路由策略 | 动态权重路由 | ADR-002 |
| ADR-003 | 对账差错处理 | 分级处理(小额自动+大额人工) | ADR-003 |
七、面试口述版
2分钟版本(电梯演讲)
支付网关是商户与支付渠道之间的中间层。我的设计核心思路是"三层保障":
第一层是通道路由引擎,基于成功率、成本、限额的加权评分动态选择最优通道,配合熔断器实现故障自动降级。第二层是幂等和状态机,通过商户订单号唯一索引做天然幂等,乐观锁状态机防止重复处理,确保"不多扣、不重扣"。第三层是对账兜底,T+1日终对账作为最后安全网,分级处理差异——小额自动冲正、大额人工审核。
架构上采用分层设计:API Gateway负责鉴权限流,Payment Core处理核心流程,Channel Adapter层用适配器模式封装各PSP差异,通过MQ异步解耦记账和通知。数据层以merchant_id分库分表,支持从百万到十亿的扩展。
5分钟版本
在2分钟版本基础上,补充以下内容:
架构容器:系统分为9个独立部署的容器。API Gateway做统一入口和限流,Payment Core是核心,内含订单管理、幂等检查、状态机。Route Engine独立部署,支持实时调整路由权重。Channel Adapter是适配器模式,每接入一个新PSP只需实现统一接口。Reconcile Service是定时任务驱动的对账引擎。中间用RocketMQ解耦。
核心流程:商户下单 → 签名验签 → 幂等检查 → 创建订单 → 风控 → 路由 → 通道调用 → 状态更新 → 异步记账+通知。整个Happy Path延迟控制在3秒内。
关键决策:幂等选择商户订单号而非全局UUID,因为语义清晰、不增加集成复杂度。路由选择动态权重而非ML预测,因为可解释性和运维友好。对账选择分级处理而非全自动,因为大额差异需要人工确认保障资金安全。
15分钟版本
在5分钟版本基础上,展开以下深度话题:
- 数据模型(2分钟):五张核心表的设计,分库分表策略,状态机定义
- 一致性保障(2分钟):本地事务+异步通知+定时查询+对账兜底的三重保障
- 性能优化(2分钟):热点商户隔离、缓存策略、异步化
- 高可用(2分钟):多机房部署、通道降级、限流熔断
- 安全合规(1分钟):PCI-DSS、签名验签、防重放
- 演进路线(1分钟):单体→微服务→多机房→全球化
八、自评与反思
做得好的地方
- 需求澄清充分:10个澄清问题覆盖了功能、非功能、约束三个维度,展示了架构师的全局思维
- 层次分明:从Context到Container到Component逐层深入,C4方法论运用得当
- 核心问题有深度:幂等机制、对账引擎、路由策略都有具体的方案设计和trade-off分析
- 三重保障体系:事务+通知+对账的分层保障是支付系统的精髓
可以改进的地方
- 国际化/多币种:虽然预留了字段,但具体的汇率管理、多币种记账没有展开
- 分布式事务:跨服务的事务一致性(如支付成功但记账失败)可以更深入讨论Saga模式
- 安全:HSM硬件安全模块、密钥轮转策略可以更详细
- 测试策略:混沌工程、全链路压测、资金安全测试没有涉及
面试中容易被追问的点
- "你说用乐观锁,如果并发更新失败怎么重试?" → 回答重试策略和最终一致性
- "对账发现多扣了怎么办?" → 回答自动退款或人工退款的流程
- "如果Redis挂了,幂等还能保证吗?" → 回答MySQL唯一索引是兜底
- "怎么测试支付系统的正确性?" → 回答Mock通道 + 对账验证 + 影子流量
附件