支付网关系统 - 面试追问 Q&A
支付网关系统 - 面试追问 Q&A
10道高频面试追问,每道按"30秒简答 + 2分钟详答 + 追问准备"结构整理。
Q1: PSP返回超时怎么处理?
简短回答(30秒)
PSP超时不能假设成功也不能假设失败,因为"不知道有没有扣款"。正确做法是:将订单标记为TIMEOUT,立即启动定时轮询(每5分钟查询PSP),同时T+1对账作为最终兜底。绝对不能在超时后重新发起扣款,否则可能导致重复扣款。
详细回答(2分钟)
PSP超时是支付系统最棘手的场景之一,核心困难在于"不确定性"——我们不知道PSP是否已经完成了扣款。
处理流程:
-
立即标记TIMEOUT:
UPDATE payment_order SET status = 'TIMEOUT' WHERE status = 'PROCESSING'。注意用乐观锁,防止与异步回调并发冲突 -
定时查询机制:
- 超时后立即查询一次PSP的订单状态
- 如果PSP也返回"处理中"或查询也超时,启动定时任务每5分钟查询一次
- 最多查询24次(覆盖到T+1对账时间)
-
查询结果处理:
- PSP返回"成功" → 更新为SUCCESS,触发记账和通知
- PSP返回"失败/不存在" → 更新为CLOSED
- PSP返回"处理中" → 继续等待下次查询
-
T+1对账兜底:无论定时查询是否得到结果,第二天的对账一定会确认最终状态
-
超时统计和告警:如果某通道超时率突然上升,触发熔断器,将流量切换到备选通道
关键原则:超时 ≠ 失败。绝不能在超时后重新发起扣款请求(除非PSP明确返回"订单不存在")。
追问准备
- 如果PSP的查询接口也超时怎么办? → 继续定时重试,同时人工联系PSP确认。最坏情况等T+1对账
- 用户在等待页面怎么体验? → 告知用户"支付处理中,请稍后查看结果",提供订单查询入口
- 超时时间怎么设置? → 通常30秒。太短会误判正常请求,太长影响用户体验
Q2: 如何保证"不多扣"?
简短回答(30秒)
"不多扣"的核心是幂等性。通过三层防护:第一层是商户订单号唯一索引,同一笔订单只能创建一次;第二层是状态机乐观锁,只有PROCESSING状态才能转为SUCCESS,重复回调只会执行一次;第三层是T+1对账兜底,如果发现多扣,自动发起退款。
详细回答(2分钟)
"不多扣"是支付系统的生命线。多扣一笔意味着用户资金损失,是最严重的支付事故。
防护体系(由上到下层层兜底):
-
请求级幂等(防止重复下单):
merchant_id + merchant_order_no做唯一索引- 同一商户的同一订单号只能创建一笔支付
- 重复请求直接返回已有订单的状态
-
回调级幂等(防止重复状态更新):
- PSP可能发送多次异步回调(网络重传、PSP重试)
- 使用乐观锁:
WHERE status = 'PROCESSING' affected_rows = 0说明已经处理过,直接返回SUCCESS给PSP
-
消费级幂等(防止重复记账):
- MQ消息可能重复投递
- 消费端维护
processed_message_id去重表 - 消费前检查是否已处理
-
对账级兜底(最终一致性保障):
- T+1对账比对所有交易
- 如果发现"我方多一笔"(CHANNEL_MISSING),核实后自动退款
- 如果发现"金额不一致",以通道方为准调整
-
防重放攻击:
- API Gateway校验
nonce + timestamp - 同一nonce在5分钟窗口内只接受一次
- API Gateway校验
追问准备
- 如果MySQL唯一索引和Redis缓存不一致怎么办? → MySQL是最终判定,Redis只是加速。即使Redis误判为"不重复",MySQL唯一索引会拦截
- 乐观锁冲突时怎么办? → affected_rows=0 直接查询最新状态返回,不重试UPDATE
- 跨系统一致性怎么保证(支付成功但记账失败)? → MQ可靠投递 + 消费重试 + 对账修正
Q3: 通道路由的降级策略?
简短回答(30秒)
采用三态熔断器实现自动降级。当某通道1分钟内错误率超过50%且请求量超过20笔时,触发熔断,自动切换到备选通道。熔断60秒后进入半开状态,放行10%流量探测,成功则恢复,失败继续熔断。降级顺序:主通道 → 备选通道 → 兜底通道 → 拒绝。
详细回答(2分钟)
熔断器三态模型:
- Closed(正常):所有请求正常发送到该通道。持续统计错误率
- Open(熔断):触发条件——1分钟内错误率 > 50% 且请求 ≥ 20笔。此状态下该通道不参与路由,流量自动切到备选通道
- Half-Open(半开探测):熔断60秒后自动进入。放行10%流量到该通道:
- 如果探测成功率 > 80%(至少5笔成功)→ 恢复Closed
- 如果探测失败 → 重新Open,再等60秒
降级链设计:
级别1:主通道(评分最高)
↓ 熔断
级别2:备选通道(评分次高)
↓ 熔断
级别3:兜底通道(成本可能较高但稳定性好)
↓ 熔断
级别4:拒绝交易 + 告警
→ 返回"支付繁忙,请稍后重试"
→ P0告警通知值班人员
特殊处理:
- 大促前预热:预先测试所有通道,确认备选通道可用
- 计划内维护:通道维护窗口期提前切流量,非熔断触发
- 部分降级:某通道对大额交易失败率高但小额正常,可以按金额区间熔断
追问准备
- 熔断误触发怎么办? → 设置合理阈值(20笔最小样本量),避免少量失败触发熔断。支持手动强制恢复
- 所有通道都挂了怎么办? → 拒绝交易+P0告警。这是极端情况,通常意味着整个PSP网络故障
- 如何避免熔断器振荡? → 半开状态需要积累足够成功样本(≥5笔)才恢复,不会因为一笔成功就全量恢复
Q4: 日终对账发现差异怎么办?
简短回答(30秒)
采用分级处理策略。小额差异(≤100元)系统自动处理:漏单自动补单,掉单自动冲正,精度差异以通道方为准。大额差异(>100元)生成工单人工审核。批量异常(同通道差异率>1%)暂停该通道对账并升级到技术团队排查。
详细回答(2分钟)
差异分类和处理策略:
| 差异类型 | 含义 | 处理方式 |
|---|---|---|
| OUR_MISSING | 通道扣款成功但我方无记录 | 小额自动补单;大额人工确认后补单 |
| CHANNEL_MISSING | 我方记录成功但通道无此交易 | 如果我方状态是TIMEOUT/PROCESSING → 自动关闭;如果是SUCCESS → 人工调查 |
| MISMATCH | 金额不一致 | 差额≤1元以通道方为准调整;差额>1元人工核实 |
自动补单流程(OUR_MISSING):
- 根据通道订单号和金额创建支付订单
- 状态直接设为SUCCESS
- 触发记账分录
- 通知商户
人工审核流程:
- 生成差异工单 → 分配给运营人员
- 运营人员调查原因(查日志、联系PSP)
- 确定处理方案 → 大额需主管审批
- 执行处理 → 记录处理日志
异常模式检测:
- 单通道差异笔数超过日均交易量1% → 暂停该通道对账
- 连续3天同类差异 → 升级到技术团队排查根因
- 对账文件解析失败 → 立即告警
追问准备
- OUR_MISSING但金额很大(如100万),确定要补单吗? → 大额必须人工核实。联系PSP确认扣款真实性,排除对账文件错误
- CHANNEL_MISSING且我方状态是SUCCESS怎么办? → 这是最危险的情况(我方认为成功但通道无记录)。可能原因:我方系统bug错误更新了状态。需要立即人工调查,可能需要给用户退款
- 对账文件格式变了怎么办? → 解析失败会触发L4异常升级。通道适配层的对账文件解析器需要版本管理和格式校验
Q5: 如何支持部分退款?
简短回答(30秒)
在payment_order上维护 refunded_amount 字段记录已退款总额。每次退款前校验:本次退款金额 + 已退款金额 ≤ 原支付金额。每次退款创建独立的refund_order记录。部分退款后订单状态变为PARTIAL_REFUNDED,全额退完变为REFUNDED。
详细回答(2分钟)
数据模型设计:
payment_order:
amount = 10000 (原支付100元)
refunded_amount = 3000 (已退30元)
→ 可退金额 = 10000 - 3000 = 7000 (70元)
refund_order (多条):
refund_1: amount=2000, status=REFUNDED (第一次退20元)
refund_2: amount=1000, status=REFUNDED (第二次退10元)
refund_3: amount=5000, status=CREATED (第三次退50元,处理中)
退款流程:
-
校验:
- 原单状态必须是 SUCCESS 或 PARTIAL_REFUNDED
- 本次退款金额 + 已退款金额 ≤ 原单金额
- 同一原单不能并发退款(分布式锁)
-
创建退款单:生成独立的refund_order
-
调用通道退款:
- 大多数PSP支持部分退款(传入退款金额和原交易号)
- 少数PSP不支持部分退款 → 需要特殊处理(如手动转账)
-
更新状态:
- 退款成功 → 更新refund_order为REFUNDED
- 更新payment_order的refunded_amount
- 判断是否全额退完 → REFUNDED / PARTIAL_REFUNDED
-
记账:生成退款记账分录(反向借贷)
并发控制:
- 同一原单的退款请求加分布式锁(Redis SETNX)
- 防止并发退款导致超退
- 锁的粒度:payment_order_no
追问准备
- 如果通道不支持部分退款怎么办? → 平台层面支持,但通道层可能需要全额退款后重新收款,或者线下转账处理
- 退款和对账如何关联? → 退款交易也会出现在对账文件中,对账引擎需要能处理REFUND类型的交易比对
- 退款金额包含手续费吗? → 通常全额退款会退还手续费,部分退款按比例退还或不退手续费,需要看通道的退费规则
Q6: 支付系统如何做灰度发布?
简短回答(30秒)
支付系统灰度发布需要极其谨慎。采用分层灰度:先灰度非核心服务(通知/对账),再灰度核心服务(Payment Core)。灰度维度以商户为粒度,先选几个小商户灰度,观察成功率和延迟指标无异常后逐步扩大。关键是要有快速回滚能力,以及灰度期间的双重对账验证。
详细回答(2分钟)
灰度策略:
-
灰度维度:按商户分流
- 选择5-10个小型商户(日均交易 < 1000笔)先灰度
- API Gateway根据merchant_id路由到新版本实例
- 绝不用随机流量灰度(支付不允许同一商户的请求被不同版本处理)
-
灰度顺序:从外围到核心
Phase 1: Notification Service(影响最小) Phase 2: Reconcile Service(离线服务) Phase 3: Route Engine(路由逻辑变更) Phase 4: Channel Adapter(通道交互变更) Phase 5: Payment Core(核心流程变更,最后灰度) -
灰度观察指标:
- 支付成功率(核心指标,不能下降)
- P99延迟(不能显著增加)
- 错误率(不能出现新类型错误)
- 对账匹配率(T+1验证资金正确性)
-
灰度放量节奏:
- Day 1: 5个小商户(< 总流量0.1%)
- Day 2: 50个中型商户(< 总流量5%)
- Day 3: 200个商户(< 总流量20%)
- Day 4-5: 全量切换
- 每一步都需要确认指标正常后才进入下一步
-
快速回滚:
- 保留旧版本实例不下线
- 回滚只需要在API Gateway修改路由规则
- 回滚时间目标 < 1分钟
追问准备
- 数据库Schema变更怎么灰度? → 采用兼容性变更策略:先加字段、再改代码、最后删旧字段。Schema变更必须向后兼容
- 灰度期间新旧版本的数据一致性? → 新旧版本共享同一数据库,通过Schema兼容保证一致性
- 回滚后正在处理中的交易怎么办? → 回滚不影响已提交到PSP的交易,状态更新和回调处理新旧版本兼容
Q7: 百万→十亿的扩展路径?
简短回答(30秒)
分四个阶段:100万时单体+读写分离足够;1000万时微服务拆分+MQ解耦+Redis缓存;1亿时分库分表+同城双活+通道集群独立部署;10亿时多地域部署+多币种+全球化PSP接入。每个阶段只做当前规模需要的事,不过度设计。
详细回答(2分钟)
| 阶段 | 日均量级 | TPS峰值 | 核心架构动作 | 瓶颈点 |
|---|---|---|---|---|
| Phase 1 | 100万 | ~120 | 单体应用 + MySQL主从 + Redis | 单库容量 |
| Phase 2 | 1000万 | ~1200 | 微服务拆分 + RocketMQ + 服务治理 | 单表数据量 |
| Phase 3 | 1亿 | ~12000 | 分库分表(64×16) + 同城双活 | 跨机房延迟 |
| Phase 4 | 10亿 | ~120000 | 多地域部署 + 全球化 | 全球一致性 |
Phase 1 → Phase 2 关键动作:
- Payment Core / Route / Channel / Reconcile 拆分为独立服务
- 引入RocketMQ解耦(支付结果→通知/记账不再同步)
- 引入Redis集群做缓存和限流
Phase 2 → Phase 3 关键动作:
- ShardingSphere分库分表,分片键merchant_id
- 订单号嵌入分片路由信息(前4位=分片Hash)
- 同城双活,读写分离升级为双主(数据库中间件路由)
- Channel Adapter独立集群(与PSP的连接池是稀缺资源)
Phase 3 → Phase 4 关键动作:
- 多地域部署(中国/东南亚/欧洲)
- 每个地域独立的PSP接入和对账
- 多币种记账引擎
- 全球ID生成器(Snowflake变体,含地域标识)
- GDPR/PSD2等合规适配
追问准备
- 分库分表后如何做跨商户查询? → 运营后台查询走大数据平台(Hive/ClickHouse),不走分片库
- 怎么判断该扩展了? → 监控数据库CPU使用率>60%、慢查询增加、P99延迟上升作为预警信号
- 扩展过程中如何保证服务不中断? → 双写 → 数据迁移 → 切读 → 切写 → 清理旧数据
Q8: 跨境支付和国内支付的架构差异?
简短回答(30秒)
三大核心差异:一是多币种——需要汇率管理、多币种记账、货币精度处理;二是合规——不同国家法规不同(PSD2/GDPR/反洗钱),需要合规引擎;三是清结算周期长——跨境清算T+N(N可能是3-5天),需要更复杂的资金管理和风控。
详细回答(2分钟)
架构层面的核心差异:
| 维度 | 国内支付 | 跨境支付 |
|---|---|---|
| 币种 | 单一币种(CNY) | 多币种,涉及汇率换算 |
| PSP | 支付宝/微信/银联 | Stripe/Adyen/PayPal/当地PSP |
| 合规 | 央行监管 | 多国监管(PSD2/GDPR/AML/KYC) |
| 清结算 | T+1 | T+3 ~ T+7 |
| 时区 | 单一时区 | 多时区,影响对账和结算 |
| 退款 | 原路退回,秒级/分钟级 | 可能需要数天,汇率差异处理 |
| 欺诈 | 相对可控 | 信用卡Chargeback是大问题 |
跨境支付需要额外的组件:
- 汇率服务:实时汇率获取、汇率锁定(报价有效期)、汇率差异处理
- 合规引擎:KYC/AML检查、交易监控、报告生成(各国报告格式不同)
- Chargeback处理:信用卡拒付管理、证据提交、争议解决流程
- 多时区对账:按PSP所在时区切日,而非统一按北京时间
数据模型差异:
payment_order需要增加:pay_currency,settle_currency,exchange_rate,exchange_rate_locked_at- 金额需要记录三个:
pay_amount(支付币种金额)、settle_amount(结算币种金额)、merchant_amount(商户收到的金额)
追问准备
- 汇率波动怎么处理? → 下单时锁定汇率(有效期15分钟),超时需要重新获取汇率。汇率差异风险由平台或商户承担(看合同约定)
- Chargeback率怎么控制? → 增强3DS验证、地址验证(AVS)、设备指纹、速度检查
- 跨境和国内能复用一套架构吗? → 核心支付流程可以复用,但需要在通道适配层、合规层、记账层做扩展
Q9: 如何防止支付欺诈?
简短回答(30秒)
三层防护:入口层做基础校验(签名/限额/黑名单/频次),风控层做实时规则引擎+ML模型(设备指纹/地理位置/行为分析),事后层做离线分析+案件调查。关键指标:欺诈率控制在0.1%以下,误拒率控制在1%以下——既要防住坏人,也不能误伤好人。
详细回答(2分钟)
三层防护体系:
第一层:入口防护(API Gateway)
- 签名验签:防止请求篡改
- IP/设备黑名单:已知恶意来源直接拦截
- 频次限制:同一用户5分钟内不超过10笔
- 金额限制:单笔/单日/单月限额
第二层:实时风控(Risk Gateway)
- 规则引擎:基于专家经验的规则
- 新注册用户首笔交易 > 5000元 → 拦截
- 同一设备1小时内切换3个以上账号 → 拦截
- 收货地址与IP地理位置不符 → 加强验证
- ML模型:基于历史数据训练
- 特征:交易金额、时间、设备指纹、用户历史行为、商户类别
- 输出:欺诈概率评分(0-100)
- 阈值:< 30放行,30-70加强验证(3DS/短信),> 70拦截
第三层:事后分析
- 离线批量分析:发现新的欺诈模式
- 案件调查:用户投诉"非本人交易"的调查处理
- 规则迭代:将新发现的欺诈模式更新到规则引擎
- Chargeback分析:统计各商户/通道的拒付率
实时风控延迟要求:< 100ms(不能影响支付体验)
追问准备
- 误拒率和欺诈率怎么平衡? → 根据业务类型调整阈值。高价值商品(如电子产品)倾向严格,低价值(如外卖)倾向宽松
- ML模型多久更新一次? → 日级别增量训练,周级别全量重训。新型欺诈模式可能需要紧急更新规则
- 如何防止内部人员欺诈? → 权限分离(开发不能碰生产数据)、操作审计日志、大额操作双人审批
Q10: 支付系统的监控指标有哪些?
简短回答(30秒)
四类核心指标:业务指标(支付成功率、通道成功率、退款率)、性能指标(延迟P50/P99/P999、QPS)、资金指标(对账匹配率、结算差异率)、系统指标(CPU/内存/连接池/MQ堆积)。最关键的三个:支付成功率(>95%)、P99延迟(<3秒)、对账匹配率(>99.99%)。
详细回答(2分钟)
业务指标(最重要):
| 指标 | 计算方式 | 正常范围 | 告警阈值 |
|---|---|---|---|
| 支付成功率 | SUCCESS笔数 / 总笔数 | > 95% | < 90% → P0 |
| 通道成功率 | 按通道维度计算 | > 95% | < 85% → 熔断 |
| 退款率 | 退款笔数 / 支付笔数 | < 5% | > 10% → 告警 |
| 超时率 | TIMEOUT / 总笔数 | < 1% | > 5% → 告警 |
| 通知成功率 | 通知成功 / 通知总数 | > 99% | < 95% → 告警 |
性能指标:
| 指标 | 目标 | 告警 |
|---|---|---|
| P50延迟 | < 500ms | > 1s |
| P99延迟 | < 3s | > 5s |
| P999延迟 | < 5s | > 10s |
| QPS | 按容量规划 | > 80%容量 |
资金指标(最严格):
| 指标 | 说明 | 告警 |
|---|---|---|
| 对账匹配率 | 对账一致笔数/总笔数 | < 99.99% → P0 |
| 资金差异总额 | 未平差异的总金额 | > 10万 → P0 |
| 结算准确率 | 结算金额与应结金额的一致性 | 任何差异 → P0 |
系统指标:
- 数据库:连接池使用率、慢查询数量、主从延迟
- Redis:内存使用率、命中率、连接数
- MQ:消息堆积量、消费延迟
- JVM:GC频率、堆内存使用率
监控大盘设计:
- 实时大盘:交易量曲线、成功率曲线、延迟分布、通道健康状态
- 资金大盘:日交易总额、退款总额、在途资金、对账状态
- 告警看板:当前未恢复告警、历史告警统计、MTTR(平均恢复时间)
追问准备
- 监控数据怎么采集? → 应用层Micrometer/Prometheus SDK埋点 → Prometheus采集 → Grafana展示 → AlertManager告警
- 如何做容量预估? → 基于历史数据预测峰值,通常为日均的5-10倍。大促前做全链路压测验证
- 告警风暴怎么处理? → 告警聚合(同一问题只发一次)、告警抑制(子系统告警被父系统告警覆盖)、分级通知(P0电话、P1短信、P2IM)