1. 这不是一份“拿来就抄”的测试清单,而是一份支付功能测试的实战地图
你点开这个标题,大概率正被三件事压着:明天就要上线的支付模块突然冒出个偶发扣款失败、测试用例评审会上被开发反问“这个场景真会发生?”,或者刚入职的新人对着支付流程图发呆——到底该测什么、怎么测、为什么这么测。我干了11年测试,从银行核心系统到电商大促,从扫码付到跨境结算,亲手写过2700+条支付相关用例,也踩过无数“以为测了其实没测”的坑。这份整理,不讲教科书定义,不列空泛分类,只告诉你:在真实业务场景里,一个支付功能从用户点击“确认支付”那一刻起,到最终账单生成,中间到底有多少个“可能断掉的链条”,以及每个链条上你必须亲手去拧紧的螺丝。它覆盖了微信/支付宝/银联云闪付等主流通道的共性逻辑,也标注了不同通道特有的“雷区”。适合测试工程师快速查漏补缺,更适合开发同学理解测试视角下的支付健壮性要求——毕竟,很多线上故障,根源不在代码bug,而在测试用例没覆盖到那个“看似不可能但偏偏发生了”的边界条件。下面直接进入硬核拆解。
2. 支付功能测试的整体设计思路:从“资金流”和“信息流”双线穿透
2.1 为什么不能只盯着“支付成功/失败”两个结果?
新手常犯的错误,是把支付测试简化为“输入正确参数→看是否跳转成功页”。这就像检查一辆汽车,只看它能不能点火启动,却不管刹车是否灵敏、油路是否通畅、仪表盘报警灯是否正常。支付的本质,是资金在多个主体(用户、商户、银行、清算机构)之间,依据严格协议进行转移的复杂过程。这个过程天然存在两个平行世界:
- 资金流(Money Flow):钱实际从哪里来、经过哪些账户、最终到哪里去。它受银行风控、账户余额、支付通道限额等物理规则约束,不可逆、强一致性。
- 信息流(Message Flow):订单状态、支付指令、回调通知、对账文件等数据在系统间传递。它受网络延迟、消息队列积压、接口幂等性等软件规则影响,存在时序错乱、重复、丢失风险。
提示:所有支付故障,要么是资金流卡在某个环节(如银行拒付),要么是信息流没同步到位(如商户系统没收到成功通知),或是两者错配(如钱扣了但订单没改状态)。测试设计必须同时覆盖这两条线,并验证它们的最终一致性。
2.2 测试范围的三层金字塔:从通道层到业务层
我习惯用三层金字塔来规划支付测试范围,确保不遗漏也不冗余:
底层:支付通道能力验证(占30%)
这是支付的“地基”。必须验证你接入的微信/支付宝/银联等通道本身是否稳定、合规、可配置。例如:- 微信JSAPI支付是否支持新版本iOS的SFSafariViewController;
- 银联云闪付是否兼容最新版华为鸿蒙系统的NFC芯片;
- 支付宝小程序支付在安卓端是否因WebView内核升级导致签名验签失败。
这些问题往往与SDK版本、系统兼容性、通道策略变更强相关,需定期回归。
中层:支付核心链路闭环(占50%)
这是测试的“主干”。聚焦用户从下单到完成支付的完整旅程,重点验证状态机流转、异常处理、幂等性。典型场景包括:- 用户支付中途关闭页面,后台如何识别并释放库存;
- 同一笔订单被重复提交三次,最终只扣一次款且订单状态唯一;
- 支付成功后,商户系统回调超时,平台如何通过主动查询补全状态。
这里需要大量模拟网络抖动、服务降级、超时重试等真实环境。
顶层:业务规则与风控联动(占20%)
这是支付的“大脑”。测试支付如何与业务规则(如优惠券叠加、会员等级折扣)和风控策略(如单日交易限额、设备指纹识别、异地登录拦截)协同工作。例如:- 用户用新设备首次支付,触发风控要求短信验证,此时支付流程如何优雅中断并恢复;
- 满减活动与支付通道优惠叠加时,分账比例计算是否准确;
- 虚拟商品(如游戏点卡)支付成功后,是否按规则自动发放,而非人工干预。
这部分最容易被忽略,却是线上资损的高发区。
2.3 方案选型:为什么放弃“纯手工用例表”,转向“场景化用例矩阵”
早期我们用Excel维护支付用例,按“前置条件-操作步骤-预期结果”三栏填写。但很快发现三个致命问题:
- 覆盖盲区:当新增一个“微信分付”通道时,需手动复制粘贴原有用例,再逐条修改通道名,极易漏改;
- 状态耦合:测试“支付超时”时,需先构造“订单已创建但未支付”状态,但Excel里无法体现状态间的依赖关系;
- 数据难管理:测试“余额不足”需准备特定金额的测试账户,但Excel里只写“余额不足”,不记录具体账号和金额,执行时总要临时找人要号。
于是我们转向“场景化用例矩阵”,核心是以“支付状态机”为轴心,将所有测试点映射到状态转换的边上。例如:
| 当前状态 | 触发事件 | 期望结果 | 关键校验点 | 数据准备要求 |
|---|---|---|---|---|
| 订单待支付 | 用户点击微信支付 | 跳转微信H5 | URL含正确prepay_id | 已配置微信商户号、密钥 |
| 订单待支付 | 用户点击微信支付 | 网络超时 | 显示“支付失败,请重试” | 模拟弱网环境(Charles断网) |
| 支付中 | 微信回调成功 | 订单状态变“已支付” | 商户系统收到回调且验签通过 | 准备合法回调签名私钥 |
这种结构让测试人员一眼看清:在哪个状态下,做哪个动作,会走到哪个结果,需要什么数据支撑。更重要的是,它天然支持自动化——状态机可以导出为JSON,由自动化脚本驱动,数据准备逻辑也可封装成独立服务。我们团队用这套矩阵后,支付用例维护效率提升40%,回归测试时间缩短60%。
3. 核心测试点深度解析:从用户点击到资金到账的17个关键节点
3.1 节点1:支付入口校验——别让错误的订单进入支付流程
支付按钮不是万能的“开始键”,它必须对订单做前置过滤。常见漏测点:
- 订单状态非法:已取消、已退款、已过期的订单,前端按钮应置灰且不可点击。但很多项目只做前端校验,绕过JS即可发起支付请求,后端若没二次校验,会导致“已取消订单仍被支付”的资损。
- 商品库存不足:用户下单时有库存,但支付时被其他用户抢光。此时支付请求应被拒绝,并提示“商品已售罄”,而非扣款失败。
- 用户资质不符:如虚拟商品购买需实名认证,未认证用户点击支付应跳转认证页,而非直接报错。
实操心得:我曾遇到一个案例,某教育平台允许用户用“课程抵扣券”支付,但抵扣券有效期截止时间为支付发起时刻。测试时只验证了“有券可支付”,没测“券刚好过期1秒”的场景,上线后用户投诉“券明明没过期却不能用”。后来我们在用例矩阵里新增一条:“当前时间=券有效期截止时间+1秒”,强制要求后端在支付接口内做毫秒级时间比对。
3.2 节点2:支付参数组装——那些藏在URL和Body里的魔鬼细节
支付请求的参数不是简单拼接,每个字段都有严格规范:
- 金额单位:微信/支付宝要求传“分”,银联要求传“元”,且必须为整数。传错会导致“支付0.01元变成1元”。
- 订单号唯一性:同一笔订单,多次调用统一下单接口,必须返回相同prepay_id。若每次生成新号,会导致用户支付后,平台无法匹配到原订单。
- 签名算法:微信用HMAC-SHA256,支付宝用RSA,银联用SM2。密钥泄露或算法选错,会导致签名验签失败,支付请求被拒。
注意:参数校验必须在统一下单接口的最外层做。曾有个项目,开发为图省事,在下单前不做金额校验,认为“支付通道会拦”,结果通道只校验格式,不校验业务逻辑(如金额是否超过用户余额),导致超限支付成功,引发资损。
3.3 节点3:支付通道选择与路由——别让用户的银行卡走错路
多通道接入不是简单罗列选项,而是智能路由:
- 卡类型识别:用户输入建行储蓄卡,应默认选银联通道;输入招行信用卡,应优先走快捷支付而非网银。
- 通道可用性:某支付通道因银行维护临时关闭,前端应自动降级到备用通道,并提示“正在切换支付方式”。
- 手续费分摊:B2B场景中,商户可设置“买家承担手续费”,此时支付金额需包含手续费,且发票金额需单独列示。
实测下来很稳的做法:在网关层部署“通道健康度探针”,每5分钟调用各通道的ping接口,根据成功率、平均耗时动态调整路由权重。避免把流量导向已劣化的通道。
3.4 节点4:前端支付SDK集成——WebView、小程序、APP的三套打法
不同端集成SDK的坑完全不同:
- APP端(Android/iOS):
- Android需处理Activity生命周期,支付回调时若Activity被回收,需在onNewIntent中重新捕获intent;
- iOS需在Info.plist中配置LSApplicationQueriesSchemes,否则无法唤起微信APP。
- 小程序端:
- 微信小程序需在app.json中声明requiredPrivateInfos,否则无法获取用户手机号;
- 支付宝小程序需注意沙箱环境与正式环境的appid隔离,测试时用错环境会导致“支付成功但回调不到”。
- H5端(WebView):
- iOS WKWebView默认禁用JavaScript弹窗,需在config中开启allowsInlineMediaPlayback;
- 安卓WebView需重写shouldOverrideUrlLoading,否则支付跳转会打开系统浏览器而非当前页。
踩过的坑:某金融APP的H5支付,在华为EMUI系统上频繁白屏。排查发现是华为浏览器对iframe嵌入支付页做了安全限制。解决方案:放弃iframe,改用window.open新窗口,并监听其关闭事件。
3.5 节点5:用户授权与身份核验——风控不是摆设,是支付的守门员
现代支付早已不是“输密码就完事”,而是多因子核验:
- 生物识别:指纹/面容ID支付,需验证设备是否支持、用户是否开启、权限是否授予。
- 短信验证码:发送频率限制(如1分钟1条)、验证码有效期(通常5分钟)、错误次数锁定(连续5次错误冻结15分钟)。
- 设备指纹:同一设备30分钟内多次支付失败,应触发增强验证(如人脸识别)。
关键点在于:核验失败后的流程必须可逆且无副作用。例如,用户输错3次短信码,系统应保持订单状态为“待支付”,而非直接关闭订单。否则用户换手机重试时,会发现订单已失效。
3.6 节点6:支付过程中的用户交互——别让用户在“加载中”无限等待
支付页面的用户体验直接影响转化率:
- 加载态设计:不能只显示“加载中…”,需明确告知进度(如“正在连接银行…”、“正在验证身份…”)。
- 中断处理:用户点击返回键或Home键,支付流程应暂停而非终止。再次进入时,应恢复到中断点(如继续人脸识别),而非从头开始。
- 网络异常反馈:弱网下支付请求超时,应提示“网络不稳定,请稍后重试”,而非“支付失败”,避免用户误以为钱已扣。
个人经验:我们曾用“网络模拟器”测试3G弱网(100ms延迟+5%丢包),发现80%的支付失败源于前端未设置合理的超时阈值(默认15秒太长),用户早已失去耐心。后来将超时设为8秒,并增加“网络检测”按钮,让用户自主判断网络质量。
3.7 节点7:支付通道响应解析——别把“系统繁忙”当成“支付失败”
支付通道返回的code不是非0即1:
- 微信返回码:
return_code=SUCCESS仅表示请求接收成功,result_code=SUCCESS才表示支付成功;err_code=SYSTEMERROR需重试,err_code=ORDERPAID说明已支付成功,勿重复扣款。 - 支付宝返回码:
code=10000是成功,code=20000是业务失败(如余额不足),code=20003是参数错误(如金额格式不对)。 - 银联回码:
respCode=00是成功,respCode=15是交易超时,respCode=77是持卡人密码错误。
注意:必须建立“通道返回码-业务动作”映射表。例如,银联返回
respCode=15,应触发“主动查询订单状态”,而非直接提示用户“支付失败”。
3.8 节点8:异步回调的可靠性保障——支付成功的“最后一公里”
90%的支付问题出在回调:
- 幂等性设计:同一笔支付,微信可能因网络原因发送3次回调。商户系统必须根据
out_trade_no去重,确保只处理一次。 - 验签必做:回调参数中
sign字段必须用商户私钥验签,防止被伪造。 - 回调超时重试:微信回调失败后,会在15分钟内最多重试8次。商户系统需记录每次回调时间,避免因重试导致重复发货。
实操中,我们要求所有回调接口必须:
- 先记录原始请求日志(含时间戳、IP、全部参数);
- 验签通过后,再查数据库确认订单状态;
- 若状态已是“已支付”,直接返回success,不执行任何业务逻辑;
- 若状态为“待支付”,更新状态并触发后续流程(如发货)。
这样即使重试100次,结果也唯一。
3.9 节点9:支付结果的前端同步——别让用户刷新页面才知道成败
回调是异步的,但用户需要即时反馈:
- 轮询机制:前端每隔3秒调用“查询订单状态”接口,直到返回“已支付”或“已关闭”。
- WebSocket推送:高并发场景下,用WebSocket实时推送支付结果,降低服务器压力。
- 本地缓存兜底:轮询超时(如30秒)后,若仍未收到结果,可读取本地缓存的支付凭证(如prepay_id),调用通道查询接口确认。
提示:轮询接口必须带防刷机制。曾有项目未加限制,被恶意脚本高频轮询,导致数据库CPU飙升。解决方案:对同一订单号,1分钟内最多允许5次轮询请求。
3.10 节点10:支付成功后的业务联动——钱到了,但事情还没完
支付成功只是起点,后续业务必须无缝衔接:
- 库存扣减:虚拟商品需立即释放库存,实物商品需锁定库存并生成出库单。
- 优惠券核销:使用过的优惠券状态必须变更为“已使用”,且不可再次使用。
- 积分发放:按规则计算积分,并实时更新用户账户。
关键风险点:这些操作必须在一个分布式事务中完成。我们采用“本地消息表+定时任务”方案:支付成功后,先在本地数据库插入一条消息记录(含订单号、操作类型),再由独立服务扫描该表,执行对应业务操作。若操作失败,消息记录保留,定时任务持续重试,直到成功。
3.11 节点11:支付失败的优雅降级——别让用户卡在死胡同
失败不是终点,而是另一个流程的开始:
- 失败原因透出:不能只显示“支付失败”,需明确告知(如“余额不足”、“银行卡限额已用完”、“网络异常”)。
- 自动重试:对“系统繁忙”类错误,前端可自动重试2次,避免用户手动操作。
- 替代方案引导:支付失败后,推荐其他通道(如“微信支付失败,试试支付宝?”)或支付方式(如“余额不足,可先充值”)。
实操心得:某电商大促期间,支付宝通道因瞬时流量过大返回
code=20000(业务失败)。我们没做区分,统一提示“支付失败”,导致大量用户反复点击,加剧拥堵。后来改为:code=20000且sub_code=ACQ.NETWORK_ERROR时,提示“网络繁忙,请稍后再试”,并禁用按钮5秒。
3.12 节点12:退款流程的双向一致性——退的钱,必须和扣的钱严丝合缝
退款不是“反向支付”,而是独立流程:
- 退款金额限制:单次退款不能超过原支付金额,累计退款不能超过原订单总金额。
- 通道限制:微信支付需在支付成功后180天内退款,超期只能线下处理;支付宝支持原路退回,但银联部分通道不支持。
- 状态同步:退款成功后,订单状态应变为“已退款”,且需同步更新用户余额、优惠券状态、积分记录。
注意:退款接口必须校验“原支付订单号”与“退款单号”的绑定关系。曾有项目因数据库事务未提交,导致同一笔订单生成两个退款单号,引发重复退款。
3.13 节点13:对账与差错处理——每天睁眼第一件事就是看对账单
支付系统必须每日与通道对账:
- 对账文件解析:微信/支付宝/银联每日提供CSV对账文件,需校验文件完整性(MD5)、字段格式、金额精度。
- 差异定位:若平台流水与通道流水不一致,需按“订单号”逐笔比对,区分是平台漏单、通道漏单、还是金额误差。
- 差错处理:发现通道少付,需发起“补单”;发现平台多扣,需发起“原路退款”。
我们自研了对账引擎,核心逻辑:
- 加载通道对账文件;
- 查询平台当日所有支付/退款订单;
- 按订单号、金额、时间三字段匹配;
- 输出差异报告(含缺失订单号、金额偏差、状态不一致)。
整个过程10分钟内完成,准确率99.99%。
3.14 节点14:风控规则的动态生效——让规则像水一样流动
风控不是静态配置,而是实时策略:
- 规则热更新:无需重启服务,即可上线新规则(如“单日交易超5万元需人工审核”)。
- 规则组合:支持“且/或/非”逻辑,如“(设备异常)且(交易金额>1万元)”触发拦截。
- 灰度发布:新规则先对1%流量生效,观察效果后再全量。
提示:风控规则必须有“熔断开关”。某次上线新规则后,误判率飙升,我们5分钟内通过开关关闭规则,避免资损扩大。
3.15 节点15:敏感信息脱敏与审计——你的测试数据,不能成为攻击者的跳板
测试环境的数据安全常被忽视:
- 生产数据脱敏:从生产库导出测试数据时,必须脱敏手机号(1381234)、身份证号(110101********123X)、银行卡号(6228******1234)。
- 日志审计:所有支付相关日志(含请求参数、响应体)必须加密存储,且禁止记录明文密码、密钥。
- 测试账号隔离:专用测试账号的余额、优惠券、积分必须与生产账号完全隔离,避免交叉污染。
我们用开源工具“DataMasker”做自动化脱敏,配置规则后,一键生成符合GDPR要求的测试数据集。
3.16 节点16:多币种与跨境支付——当人民币遇上美元、欧元、日元
跨境支付增加三大维度:
- 汇率计算:用户支付时显示人民币金额,但实际扣款为外币。需验证汇率是否按支付时刻实时汇率计算,而非固定汇率。
- 通道合规:PayPal、Stripe等通道需遵守当地金融监管,如欧盟PSD2要求SCA强认证。
- 税务处理:跨境订单需自动计算VAT/GST,并在发票中单独列示。
实操难点:某项目接入Stripe,测试时用测试卡号(4242 4242 4242 4242)能成功,但上线后真实卡失败。排查发现是测试卡不校验3D Secure,而真实卡强制校验。解决方案:在测试环境启用3DS模拟模式。
3.17 节点17:灾备与降级预案——当主通道崩了,你的Plan B在哪?
支付是核心链路,必须有B计划:
- 通道降级:主通道(如微信)不可用时,自动切换至备用通道(如支付宝),并记录降级日志。
- 支付暂停:所有通道均不可用时,前端显示“支付服务暂时不可用”,并引导用户稍后重试,而非报错。
- 离线支付:极端情况下(如全网断连),允许用户生成离线支付码,待网络恢复后自动上传。
我们每年组织两次“支付通道熔断演练”,模拟微信/支付宝同时宕机2小时,验证降级流程、客服话术、用户通知是否完备。最近一次演练,从故障发现到全量切流,用时3分27秒。
4. 实操过程详解:以“微信JSAPI支付”为例的全流程验证
4.1 环境准备:搭建可复现的测试沙箱
真实测试绝不能只靠生产环境,必须构建四层沙箱:
- 前端沙箱:用Webpack DevServer模拟H5页面,注入微信JS-SDK调试模式(
config.debug = true),可查看签名、调起日志。 - 后端沙箱:部署独立测试服务,连接测试数据库、测试Redis、测试MQ,所有外部依赖(微信API、短信网关)用Mock Server模拟。
- 通道沙箱:微信开放平台提供“沙箱环境”,有独立的测试商户号、测试密钥、测试支付账号(余额100万元),支持所有支付场景。
- 网络沙箱:用Charles或Fiddler模拟弱网(3G/4G)、DNS劫持、SSL证书错误等网络异常。
注意:沙箱环境必须与生产环境配置一致。曾有项目因测试环境Redis过期时间设为1小时,生产环境为24小时,导致“支付超时”用例在测试环境无法复现。
4.2 核心环节实现:统一下单接口的12个必测参数
微信JSAPI支付的统一下单接口(https://api.mch.weixin.qq.com/pay/unifiedorder)是支付链路的起点,以下12个参数必须逐一验证:
| 参数名 | 必填 | 测试要点 | 风险示例 |
|---|---|---|---|
appid | 是 | 必须与微信开放平台注册的APPID一致 | 用错APPID,返回INVALID_APPID |
mch_id | 是 | 必须与微信商户平台的商户号一致 | 商户号错误,返回INVALID_MCHID |
nonce_str | 是 | 随机字符串,长度32位以内 | 重复使用nonce_str,返回INVALID_NONCE_STR |
sign | 是 | 用商户密钥对所有参数按字典序拼接后SHA256签名 | 签名错误,返回SIGNERROR |
body | 是 | 商品描述,UTF-8编码,不超过128字符 | 含特殊字符(如&、<),需URL编码 |
out_trade_no | 是 | 商户系统内唯一订单号,32位内 | 重复订单号,返回ORDERNOTEXIST |
total_fee | 是 | 金额单位为分,整数,不能带小数点 | 传100.00,返回INVALID_TOTAL_FEE |
spbill_create_ip | 是 | 用户客户端IP,需真实有效 | 传127.0.0.1,返回INVALID_SPBILL_CREATE_IP |
notify_url | 是 | 支付结果回调地址,必须为HTTPS | HTTP地址,返回INVALID_NOTIFY_URL |
trade_type | 是 | 固定为JSAPI | 传NATIVE,返回INVALID_TRADE_TYPE |
openid | 是 | 用户在商户公众号下的唯一标识 | openid错误,返回INVALID_OPENID |
scene_info | 否 | 支付场景信息,如{"payer_client_ip":"123.123.123.123"} | 缺失时不影响,但风控需此字段 |
实操中,我们用Postman编写集合,每个参数单独建一个请求,用pm.test断言返回结果。例如:
// 测试total_fee为小数 pm.test("total_fee must be integer", function () { pm.expect(pm.response.text()).to.include("INVALID_TOTAL_FEE"); });4.3 支付流程验证:从prepay_id到支付成功
完整流程分五步,每步都需验证:
- 调用统一下单:传入正确参数,返回
return_code=SUCCESS且result_code=SUCCESS,获取prepay_id。 - 前端调起支付:用
wx.chooseWXPay传入prepay_id,观察是否唤起微信支付页。 - 用户完成支付:在微信沙箱环境用测试账号支付,观察是否跳转回商户页面。
- 接收回调:微信服务器向
notify_url发送POST请求,验证sign、out_trade_no、result_code。 - 查询订单状态:调用
https://api.mch.weixin.qq.com/pay/orderquery,确认trade_state=SUCCESS。
关键技巧:在回调接口中,我们打印了完整的
$_POST数组,发现微信回调参数中total_fee是字符串类型(如"100"),而我们的数据库字段是INT。若直接插入,PHP会自动转为整数,但某些框架会报类型错误。解决方案:回调中显式intval($_POST['total_fee'])。
4.4 异常场景模拟:用工具制造“不可能发生”的情况
真实世界充满意外,测试必须主动制造:
- 网络抖动:用Charles设置“Throttle”规则,将统一下单接口响应时间设为10秒,验证前端超时逻辑。
- 签名篡改:用Burp Suite拦截回调请求,修改
sign字段,验证验签失败是否返回FAIL。 - 重复支付:同一订单号,连续调用统一下单接口3次,验证返回的
prepay_id是否相同。 - 金额溢出:
total_fee传2147483647(INT_MAX),验证是否被截断或报错。
我们编写了Python脚本,自动化执行这些异常测试:
import requests import time def test_timeout(): # 模拟超时 start = time.time() resp = requests.post("https://test-api/unifiedorder", timeout=5) end = time.time() assert end - start > 4.5, "Timeout not triggered" test_timeout()4.5 自动化回归:用Pytest+Allure构建支付测试报告
手工测试无法覆盖全量场景,我们用自动化保障核心链路:
- 框架选型:Pytest(简洁灵活)+ Requests(HTTP请求)+ Allure(可视化报告)。
- 用例组织:按“通道”分目录,
wechat/、alipay/、unionpay/,每个目录下按“场景”分文件,test_success.py、test_fail.py、test_refund.py。 - 数据驱动:用
@pytest.mark.parametrize传入不同参数组合,如:@pytest.mark.parametrize("amount,expected_code", [ (100, "SUCCESS"), (0, "INVALID_TOTAL_FEE"), (-1, "INVALID_TOTAL_FEE"), ]) def test_total_fee(amount, expected_code): # 执行下单请求 assert get_result_code() == expected_code - 报告生成:Allure报告清晰展示每个用例的步骤、截图、日志,失败用例自动高亮,并关联Jira缺陷号。
实测效果:核心支付链路自动化覆盖率92%,每次回归测试耗时从4小时缩短至22分钟,且能7x24小时无人值守运行。
5. 常见问题与排查技巧实录:来自线上事故的血泪总结
5.1 问题1:支付成功,但订单状态仍是“待支付”
现象:用户收到微信支付成功通知,但APP内订单状态未变,客服接到大量投诉。
排查思路:
- 查微信回调日志:发现回调请求到达商户服务器,但返回
500 Internal Server Error; - 查应用日志:发现回调接口因数据库连接池耗尽,抛出
Connection refused; - 查数据库监控:发现某慢SQL(未加索引的
WHERE out_trade_no查询)导致连接池堵塞。
根因:回调接口未做连接池隔离,与前台业务共用同一连接池,高并发时被挤占。
解决方案:
- 为回调接口配置独立数据库连接池(最小连接数5,最大20);
- 在
out_trade_no字段上添加唯一索引; - 增加回调失败告警,5分钟内未收到成功回调即触发短信通知。
独家技巧:我们在回调接口最开头加入
time.sleep(0.1),人为制造100ms延迟,强制暴露连接池瓶颈。这是“压力测试”的另类用法。
5.2 问题2:同一笔订单,被扣了两次款
现象:用户投诉“明明只点了一次支付,却扣了两笔钱”。
排查思路:
- 查平台订单表:发现两条记录,
out_trade_no相同,但transaction_id(微信交易号)不同; - 查微信支付记录:发现微信确实返回了两个不同的
transaction_id; - 查统一下单日志:发现同一
out_trade_no,被调用了两次统一下单接口,间隔3秒。
根因:前端防重提交失效。用户点击支付按钮后,页面未置灰,网络慢导致用户误以为没点上,再次点击。
解决方案:
- 前端按钮点击后立即置灰,并显示“支付中…”;
- 后端统一下单接口增加Redis锁:
SETNX lock:out_trade_no:{out_trade_no} 1 EX 30,锁住30秒; - 若加锁失败,返回
BUSY,前端提示“请勿重复提交”。
注意:Redis锁必须带过期时间,否则服务宕机后锁永不释放。我们用
SETNX + EXPIRE原子操作,或直接用SET key value EX seconds NX。
5.3 问题3:退款成功,但用户没收到钱
现象:商户后台显示“退款成功”,用户账户余额未增加,微信账单无记录。
排查思路:
- 查微信退款接口返回:
return_code=SUCCESS,result_code=SUCCESS,refund_id已生成; - 查微信退款查询接口:
refund_status=PROCESSING(处理中),非SUCCESS; - 查微信商户平台:发现该笔退款处于“退款中”状态,需T+1到账。
根因:混淆了“退款申请成功”与“退款到账成功”。微信退款分两步:先受理(返回SUCCESS),再执行(refund_status变SUCCESS)。
解决方案:
- 退款接口返回后,启动定时任务,每5分钟调用
https://api.mch.weixin.qq.com/pay/refundquery查询状态; - 直到
refund_status=SUCCESS,才更新订单状态为“已退款”,并通知用户; - 若24小时未成功,自动触发人工介入流程。
5.4 问题4:H5支付在iOS Safari上白屏
现象:iPhone用户点击支付,页面白屏,无任何错误提示。
排查思路:
- 用Mac Safari远程调试iPhone:发现控制台报错
TypeError: undefined is not an object (evaluating 'WeixinJSBridge.invoke'); - 查微信JS-SDK文档:iOS Safari需先调用
WeixinJSBridgeReady事件,再执行支付; - 查代码:前端未