返回金融系统设计
支付网关 · 面试题集

支付网关系统 - 面试追问 Q&A

01-payment-gateway/interview-qa.md

支付网关系统 - 面试追问 Q&A

10道高频面试追问,每道按"30秒简答 + 2分钟详答 + 追问准备"结构整理。


Q1: PSP返回超时怎么处理?

简短回答(30秒)

PSP超时不能假设成功也不能假设失败,因为"不知道有没有扣款"。正确做法是:将订单标记为TIMEOUT,立即启动定时轮询(每5分钟查询PSP),同时T+1对账作为最终兜底。绝对不能在超时后重新发起扣款,否则可能导致重复扣款。

详细回答(2分钟)

PSP超时是支付系统最棘手的场景之一,核心困难在于"不确定性"——我们不知道PSP是否已经完成了扣款。

处理流程

  1. 立即标记TIMEOUTUPDATE payment_order SET status = 'TIMEOUT' WHERE status = 'PROCESSING'。注意用乐观锁,防止与异步回调并发冲突

  2. 定时查询机制

    • 超时后立即查询一次PSP的订单状态
    • 如果PSP也返回"处理中"或查询也超时,启动定时任务每5分钟查询一次
    • 最多查询24次(覆盖到T+1对账时间)
  3. 查询结果处理

    • PSP返回"成功" → 更新为SUCCESS,触发记账和通知
    • PSP返回"失败/不存在" → 更新为CLOSED
    • PSP返回"处理中" → 继续等待下次查询
  4. T+1对账兜底:无论定时查询是否得到结果,第二天的对账一定会确认最终状态

  5. 超时统计和告警:如果某通道超时率突然上升,触发熔断器,将流量切换到备选通道

关键原则:超时 ≠ 失败。绝不能在超时后重新发起扣款请求(除非PSP明确返回"订单不存在")。

追问准备

  • 如果PSP的查询接口也超时怎么办? → 继续定时重试,同时人工联系PSP确认。最坏情况等T+1对账
  • 用户在等待页面怎么体验? → 告知用户"支付处理中,请稍后查看结果",提供订单查询入口
  • 超时时间怎么设置? → 通常30秒。太短会误判正常请求,太长影响用户体验

Q2: 如何保证"不多扣"?

简短回答(30秒)

"不多扣"的核心是幂等性。通过三层防护:第一层是商户订单号唯一索引,同一笔订单只能创建一次;第二层是状态机乐观锁,只有PROCESSING状态才能转为SUCCESS,重复回调只会执行一次;第三层是T+1对账兜底,如果发现多扣,自动发起退款。

详细回答(2分钟)

"不多扣"是支付系统的生命线。多扣一笔意味着用户资金损失,是最严重的支付事故。

防护体系(由上到下层层兜底):

  1. 请求级幂等(防止重复下单):

    • merchant_id + merchant_order_no 做唯一索引
    • 同一商户的同一订单号只能创建一笔支付
    • 重复请求直接返回已有订单的状态
  2. 回调级幂等(防止重复状态更新):

    • PSP可能发送多次异步回调(网络重传、PSP重试)
    • 使用乐观锁:WHERE status = 'PROCESSING'
    • affected_rows = 0 说明已经处理过,直接返回SUCCESS给PSP
  3. 消费级幂等(防止重复记账):

    • MQ消息可能重复投递
    • 消费端维护 processed_message_id 去重表
    • 消费前检查是否已处理
  4. 对账级兜底(最终一致性保障):

    • T+1对账比对所有交易
    • 如果发现"我方多一笔"(CHANNEL_MISSING),核实后自动退款
    • 如果发现"金额不一致",以通道方为准调整
  5. 防重放攻击

    • API Gateway校验 nonce + timestamp
    • 同一nonce在5分钟窗口内只接受一次

追问准备

  • 如果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)

  1. 根据通道订单号和金额创建支付订单
  2. 状态直接设为SUCCESS
  3. 触发记账分录
  4. 通知商户

人工审核流程

  1. 生成差异工单 → 分配给运营人员
  2. 运营人员调查原因(查日志、联系PSP)
  3. 确定处理方案 → 大额需主管审批
  4. 执行处理 → 记录处理日志

异常模式检测

  • 单通道差异笔数超过日均交易量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元,处理中)

退款流程

  1. 校验

    • 原单状态必须是 SUCCESS 或 PARTIAL_REFUNDED
    • 本次退款金额 + 已退款金额 ≤ 原单金额
    • 同一原单不能并发退款(分布式锁)
  2. 创建退款单:生成独立的refund_order

  3. 调用通道退款

    • 大多数PSP支持部分退款(传入退款金额和原交易号)
    • 少数PSP不支持部分退款 → 需要特殊处理(如手动转账)
  4. 更新状态

    • 退款成功 → 更新refund_order为REFUNDED
    • 更新payment_order的refunded_amount
    • 判断是否全额退完 → REFUNDED / PARTIAL_REFUNDED
  5. 记账:生成退款记账分录(反向借贷)

并发控制

  • 同一原单的退款请求加分布式锁(Redis SETNX)
  • 防止并发退款导致超退
  • 锁的粒度:payment_order_no

追问准备

  • 如果通道不支持部分退款怎么办? → 平台层面支持,但通道层可能需要全额退款后重新收款,或者线下转账处理
  • 退款和对账如何关联? → 退款交易也会出现在对账文件中,对账引擎需要能处理REFUND类型的交易比对
  • 退款金额包含手续费吗? → 通常全额退款会退还手续费,部分退款按比例退还或不退手续费,需要看通道的退费规则

Q6: 支付系统如何做灰度发布?

简短回答(30秒)

支付系统灰度发布需要极其谨慎。采用分层灰度:先灰度非核心服务(通知/对账),再灰度核心服务(Payment Core)。灰度维度以商户为粒度,先选几个小商户灰度,观察成功率和延迟指标无异常后逐步扩大。关键是要有快速回滚能力,以及灰度期间的双重对账验证。

详细回答(2分钟)

灰度策略

  1. 灰度维度:按商户分流

    • 选择5-10个小型商户(日均交易 < 1000笔)先灰度
    • API Gateway根据merchant_id路由到新版本实例
    • 绝不用随机流量灰度(支付不允许同一商户的请求被不同版本处理)
  2. 灰度顺序:从外围到核心

    Phase 1: Notification Service(影响最小)
    Phase 2: Reconcile Service(离线服务)
    Phase 3: Route Engine(路由逻辑变更)
    Phase 4: Channel Adapter(通道交互变更)
    Phase 5: Payment Core(核心流程变更,最后灰度)
    
  3. 灰度观察指标

    • 支付成功率(核心指标,不能下降)
    • P99延迟(不能显著增加)
    • 错误率(不能出现新类型错误)
    • 对账匹配率(T+1验证资金正确性)
  4. 灰度放量节奏

    • Day 1: 5个小商户(< 总流量0.1%)
    • Day 2: 50个中型商户(< 总流量5%)
    • Day 3: 200个商户(< 总流量20%)
    • Day 4-5: 全量切换
    • 每一步都需要确认指标正常后才进入下一步
  5. 快速回滚

    • 保留旧版本实例不下线
    • 回滚只需要在API Gateway修改路由规则
    • 回滚时间目标 < 1分钟

追问准备

  • 数据库Schema变更怎么灰度? → 采用兼容性变更策略:先加字段、再改代码、最后删旧字段。Schema变更必须向后兼容
  • 灰度期间新旧版本的数据一致性? → 新旧版本共享同一数据库,通过Schema兼容保证一致性
  • 回滚后正在处理中的交易怎么办? → 回滚不影响已提交到PSP的交易,状态更新和回调处理新旧版本兼容

Q7: 百万→十亿的扩展路径?

简短回答(30秒)

分四个阶段:100万时单体+读写分离足够;1000万时微服务拆分+MQ解耦+Redis缓存;1亿时分库分表+同城双活+通道集群独立部署;10亿时多地域部署+多币种+全球化PSP接入。每个阶段只做当前规模需要的事,不过度设计。

详细回答(2分钟)

阶段日均量级TPS峰值核心架构动作瓶颈点
Phase 1100万~120单体应用 + MySQL主从 + Redis单库容量
Phase 21000万~1200微服务拆分 + RocketMQ + 服务治理单表数据量
Phase 31亿~12000分库分表(64×16) + 同城双活跨机房延迟
Phase 410亿~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+1T+3 ~ T+7
时区单一时区多时区,影响对账和结算
退款原路退回,秒级/分钟级可能需要数天,汇率差异处理
欺诈相对可控信用卡Chargeback是大问题

跨境支付需要额外的组件

  1. 汇率服务:实时汇率获取、汇率锁定(报价有效期)、汇率差异处理
  2. 合规引擎:KYC/AML检查、交易监控、报告生成(各国报告格式不同)
  3. Chargeback处理:信用卡拒付管理、证据提交、争议解决流程
  4. 多时区对账:按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)