ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

微信支付JSAPI、H5、Native三种方式的本质区别与选型指南

微信支付JSAPI、H5、Native三种方式的本质区别与选型指南 1. 为什么微信支付的三种方式不能混着用——从商户后台第一眼就该看清的底层逻辑你刚接手一个新项目技术负责人甩过来一句话“微信支付接入一下H5和JSAPI都得支持。”你打开微信支付商户平台看到“JSAPI支付”“H5支付”“Native支付”三个入口点进去全是密密麻麻的参数、回调地址、签名规则……更糟的是测试环境里用户在iOS微信里能付在安卓微信里跳转失败H5页面在浏览器里能唤起支付在微信内置浏览器里却提示“不支持当前环境”。这时候你才意识到不是微信支付不好用而是你根本没搞懂这三种方式各自存在的物理边界。这三种支付方式本质不是“功能选项”而是微信支付为不同运行容器Runtime Context和用户触达路径量身定制的三套独立协议栈。JSAPI不是“JS版API”H5不是“网页版支付”Native也不是“原生App专用”——这些叫法全是历史遗留的误导性简称。真实情况是JSAPI支付专为微信内嵌浏览器即微信WebView设计依赖微信JS-SDK注入的wx.chooseWXPay全局方法其调用前提是用户已通过wx.config完成当前页面的JS权限校验且必须运行在微信客户端v6.5.7环境中。它不走标准HTTP跳转而是由微信客户端直接接管支付流程因此无法在Safari、Chrome或任何非微信浏览器中执行。H5支付专为外部浏览器如手机自带浏览器、QQ浏览器、UC等设计核心动作是后端统一下单后返回一个mweb_url前端重定向至此URL由微信支付网关在外部浏览器中渲染一个中间页再唤起微信App完成支付。它的存在意义就是解决“用户没装微信App但想用微信支付”的场景——比如用户在淘宝APP内点击商品跳转到商家H5页此时若用户已安装微信AppH5支付可无缝唤起若未安装则降级为二维码支付。Native支付专为原生AppiOS/Android设计不依赖任何Web容器由App SDK直接调用微信支付SDK完成预下单、拉起支付、结果回调全流程。它的关键特征是支付请求由App自身发起而非网页JavaScript因此天然规避了跨域、WebView限制、JS注入失败等Web侧问题。你看到的“扫码支付”“被扫支付”只是Native支付的两种业务形态底层共用同一套API。提示很多团队踩的第一个坑就是把JSAPI的appId、timeStamp、nonceStr、package、signType、paySign六元组直接硬编码进H5页面然后在Chrome里调试——这注定失败。因为paySign的生成依赖jsapi_ticket而jsapi_ticket只能通过微信服务器获取且必须绑定当前域名白名单。你在本地localhost调试时连wx.config都初始化失败更别说生成有效签名。我去年帮一家连锁药店做小程序公众号H5双渠道支付重构他们原来的方案是公众号菜单跳转H5页H5页里同时集成JSAPI和H5支付逻辑用WeixinJSBridge检测环境能调JSAPI就调不能就fallback到H5支付。结果上线后投诉率飙升——原因很简单微信iOS版对WeixinJSBridge的调用有严格沙箱限制某些版本下WeixinJSBridge.invoke(getNetworkType)会静默失败导致H5支付逻辑永远不触发。最后我们砍掉所有环境探测改为服务端根据User-Agent精确识别终端类型主动下发对应支付方式的参数投诉率直接归零。这背后反映的是一个铁律微信支付的三种方式不是“前端适配策略”而是服务端路由决策。你的后端API必须在统一下单接口unifiedorder中根据trade_type参数明确指定支付类型并严格校验调用方身份——JSAPI要求openid用户在公众号/小程序的唯一标识H5支付要求scene_info含h5_info.bill_type和h5_info.bill_noNative支付则要求spbill_create_ip必须是真实公网IP不能是内网或127.0.0.1。漏掉任何一个字段微信服务器就会返回INVALID_REQUEST或PARAM_ERROR而错误码说明文档里往往只写“参数错误”根本不会告诉你缺了哪个。所以别再纠结“前端怎么判断环境”真正该花时间的地方是理清你的业务场景到底属于哪一类容器用户是从微信聊天窗口点击链接进来还是从短信链接点击进来还是从自家App内WebView打开这三类场景分别对应JSAPI、H5、Native没有模糊地带。接下来我们就一层层拆开这三套协议的真实数据流。2. JSAPI支付微信内闭环生态下的“免跳转”支付链路全解析JSAPI支付之所以被称作“最丝滑”的微信支付方式是因为它实现了零页面跳转、零用户感知的支付唤起。但这种丝滑感是以极高的环境约束为代价换来的。它的完整链路不是简单的“前端调API→用户确认→支付成功”而是一条横跨微信客户端、商户服务器、微信支付网关三方的精密协同流水线。我们以一个典型电商下单场景为例还原每一步的真实数据交互与关键校验点。2.1 第一阶段页面初始化——wx.config不是摆设而是安全门禁用户点击公众号菜单进入商品详情页浏览器加载HTML。此时第一步不是调支付而是让微信客户端确认“这个页面有权使用微信JS接口”。这通过wx.config完成其参数必须由商户后端生成// 前端调用伪代码 wx.config({ debug: false, appId: wx1234567890abcdef, timestamp: 1712345678, nonceStr: aBcDeFgHiJkLmNoPqRsTuVwXyZ, signature: xxxxxx, // 关键由后端计算 jsApiList: [chooseWXPay] });这个signature的生成是JSAPI支付的第一道生死线。它不是对上述参数简单拼接MD5而是遵循微信官方规定的SHA1签名算法且必须包含以下要素jsapi_ticket不是access_token而是微信JS-SDK专用票据有效期2小时需缓存并自动刷新。获取路径https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_tokenACCESS_TOKENtypejsapinoncestr随机字符串长度32位以内建议用UUIDv4生成timestamp当前时间戳秒级非毫秒url当前页面的完整URL含query参数不含hash且必须与商户平台配置的JS接口安全域名完全一致注意https://shop.example.com/goods?id123≠https://shop.example.com/goods?id123refweixin签名字符串拼接规则为jsapi_ticketxxxnoncestryyytimestampzzzurlaaa然后对整个字符串做SHA1哈希。很多人在这里栽跟头——把url写成window.location.href结果带上了#section1锚点或者漏掉了query参数中的特殊字符如未编码导致签名永远不匹配。注意wx.config失败时wx.ready回调永远不会触发后续所有JSAPI调用均无效。但微信开发者工具里debug:true会弹出详细错误而真机上只会静默失败。我的经验是在wx.error回调里打印res对象重点关注errMsg字段常见错误如config:invalid signature签名错、config:invalid url domain域名未备案、config:permission deniedJS接口未开通。2.2 第二阶段统一下单——后端必须扛起全部责任当用户点击“立即支付”前端调用wx.chooseWXPay前必须先向商户后端发起统一下单请求。这里的关键是JSAPI支付的下单参数与H5/Native完全不同。商户后端调用微信支付统一下单APIhttps://api.mch.weixin.qq.com/pay/unifiedorder时必须传入字段值说明trade_typeJSAPI必填声明支付类型openidoABC1234567890xyz必填用户在当前公众号/小程序的唯一标识。绝不能用unionid替代bodyiPhone 15 Pro 256GB商品描述out_trade_noORD20240405123456商户订单号32位内建议用时间戳随机数total_fee899900单位为分整数不可带小数点spbill_create_ip123.123.123.123用户真实IP用于风控不能填代理IP特别注意openid的获取方式。如果你的H5页面运行在公众号内openid可通过OAuth2.0网页授权获取snsapi_base即可无需用户信息如果运行在小程序WebView里则需通过wx.miniProgram.getEnv判断环境再调用小程序API获取。绝对禁止在JSAPI支付中传入H5支付所需的scene_info字段否则微信服务器会直接拒单。我曾遇到一个诡异问题同一套下单代码在测试环境总返回FAIL错误信息是{err_code:INVALID_REQUEST,err_code_des:参数错误}。排查三天才发现测试环境的Nginx反向代理配置了proxy_set_header X-Real-IP $remote_addr;但后端Java代码里读取IP时用了request.getRemoteAddr()拿到的是Nginx内网IP10.x.x.x而微信支付要求spbill_create_ip必须是公网IP。解决方案是在Nginx里加proxy_set_header X-Forwarded-For $remote_addr;后端改用request.getHeader(X-Forwarded-For)读取。2.3 第三阶段支付唤起——六元组不是前端拼的而是后端算的统一下单成功后微信支付网关返回prepay_id预支付交易会话标识。此时商户后端需用此prepay_id结合微信支付密钥生成paySign——这才是前端wx.chooseWXPay真正需要的签名。生成逻辑如下构造待签名字符串appIdxxxnonceStryyypackageprepay_idzzzsignTypeMD5timeStampaaakeybbb对字符串做MD5哈希注意key放在末尾且不参与URL编码转为大写十六进制字符串前端收到的六元组必须严格按此格式传递wx.chooseWXPay({ timestamp: 1712345678, // 注意此处是秒级时间戳与wx.config的timestamp无关 nonceStr: aBcDeFgHiJkLmNoPqRsTuVwXyZ, package: prepay_idwx1234567890abcdef1234567890abcdef, signType: MD5, paySign: A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6 // 后端生成前端只负责传入 });这里有个致命陷阱package字段的值必须是prepay_idxxx不能多一个空格不能少一个等号不能有任何URL编码。我见过最离谱的案例后端用URLEncoder.encode(prepayId, UTF-8)对prepay_id做了编码结果前端传给微信的package变成prepay_id%3Dwx123...微信客户端解析失败直接黑屏。2.4 第四阶段结果回调——别信前端只信微信服务器的POST通知用户完成支付后微信客户端会触发success或fail回调。但这些回调仅作前端用户体验优化绝不能作为支付成功的依据。真实支付结果必须依赖微信支付服务器向商户后台notify_url发送的异步通知。该通知是HTTP POST请求Content-Type: application/xmlBody为XML格式。关键点在于必须验签微信会在XML中附带sign字段其值是对除sign外所有字段按字典序拼接后用API密钥MD5得到。验签失败必须返回xmlreturn_code![CDATA[FAIL]]/return_codereturn_msg![CDATA[签名失败]]/return_msg/xml否则微信会持续重发。必须校验result_code和return_code两者都必须为SUCCESS才算真正成功。必须校验out_trade_no防止重复通知。必须校验total_fee与订单金额是否一致避免金额被篡改。我处理过一个线上事故某次大促期间notify_url接口因数据库连接池耗尽响应超时5秒微信服务器判定通知失败开始每分钟重发一次持续24小时。结果订单系统收到1440次重复通知若没做幂等处理如用Redis记录out_trade_no已处理就会导致库存扣减1440次。解决方案是在通知处理开头用SETNX指令尝试获取分布式锁锁住out_trade_no超时时间设为30秒确保同一订单只被处理一次。JSAPI支付的终极价值在于它把支付流程完全封装在微信生态内用户无需离开当前页面也无需理解“跳转到微信App”这个概念。但这份便利的背后是商户必须对微信的每个校验环节都做到毫米级精准。它不是“前端技术”而是“微信生态合规工程”。3. H5支付外部浏览器里的“微信支付体验”如何实现——从URL跳转到支付完成的全链路当你在淘宝APP里点开一个第三方店铺的商品页页面底部出现“微信支付”按钮点击后跳转到一个微信风格的中间页再唤起微信App完成支付——这就是H5支付的典型场景。它的设计哲学很清晰在非微信环境下复刻微信支付的用户体验。但要实现这一点必须绕过微信客户端的JSAPI限制转而依赖HTTP重定向和微信支付网关的中间页渲染能力。这条链路看似简单实则暗藏多个极易被忽略的细节。3.1 核心前提H5支付不是“网页调微信”而是“网页跳微信网关”很多开发者误以为H5支付是前端JavaScript调用某个API其实完全相反H5支付的整个支付流程由HTTP 302重定向驱动。商户后端调用统一下单API后微信支付网关返回一个mweb_url前端只需window.location.href mweb_url剩下的事全部交给微信服务器。这个mweb_url长这样https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_idwx1234567890abcdef1234567890abcdefpackage38271234567890123456789012345678redirect_urlhttps%3A%2F%2Fshop.example.com%2Fpay%2Fcallback其中redirect_url是关键——它定义了用户支付完成后微信网关将用户重定向回哪里。但这里有个巨大误区很多人把redirect_url设为自己的支付成功页如/pay/success?order_id123结果发现支付完成后页面一片空白。原因在于redirect_url接收的不是GET参数而是微信网关的POST通知。提示H5支付的redirect_url必须是一个能接收POST请求的接口且该接口必须返回HTTP 200状态码。微信网关会向此URL POST一个XML格式的通知内容与JSAPI支付的notify_url完全相同含out_trade_no、result_code等字段。你不能把它当成普通跳转链接来用。3.2 统一下单的特殊要求scene_info字段是H5支付的身份证H5支付的统一下单请求与JSAPI支付最大的区别在于trade_type和scene_info字段字段值说明trade_typeMWEB注意微信官方文档写的是MWEB不是H5这是历史命名务必写对scene_info{h5_info: {type:WAP,wap_url:https://shop.example.com,wap_name:商城}}必填且JSON必须是字符串格式scene_info字段的JSON结构必须严格符合规范h5_info.type固定为WAP表示Web Apph5_info.wap_url用户支付完成后微信网关将重定向至此URL即上面说的redirect_url的父路径h5_info.wap_name在微信中间页上显示的网站名称20个字符内建议用品牌名这个字段的作用是让微信支付网关知道“这个H5页面属于谁”从而在中间页展示正确的品牌信息并控制重定向行为。如果wap_url与redirect_url的域名不一致微信网关会拒绝跳转返回INVALID_REQUEST错误。我曾帮一家教育机构接入H5支付他们把wap_url设为https://edu.example.com而redirect_url设为https://pay.edu.example.com/callback结果用户支付完成后卡在中间页提示“网络错误”。排查发现微信网关要求redirect_url的域名必须是wap_url的子域名或完全一致。解决方案是把redirect_url改为https://edu.example.com/pay/callback问题立刻解决。3.3 支付中间页的三大隐藏行为你看到的不只是跳转当用户访问mweb_url时微信支付网关会渲染一个中间页。这个页面的行为远比表面看到的复杂自动唤起微信App如果用户手机已安装微信App中间页会自动尝试唤起微信通过intent://或weixin://协议用户无需点击“打开微信”按钮。这是H5支付体验优于传统跳转的核心。降级为二维码如果用户未安装微信App中间页会动态生成一个支付二维码用户可用其他设备扫码完成支付。二维码的有效期为2小时。超时自动关闭如果用户在中间页停留超过15分钟未操作页面会自动关闭并返回redirect_url此时redirect_url收到的POST通知中result_code为FAILerr_code为PAYERROR。这三个行为都是微信网关在服务端完成的前端无法干预。但你可以通过mweb_url中的redirect_url参数控制用户最终回到哪里。例如你可以在redirect_url里带上订单ID这样用户无论支付成功或失败都能回到对应的订单详情页。3.4 移动端适配的致命陷阱iOS微信内H5支付的“假死”现象H5支付在iOS微信客户端内有一个著名Bug当用户从微信聊天窗口点击H5链接进入页面页面内调用window.location.href mweb_url后微信会弹出“即将离开微信是否继续”的提示框。用户点击“继续”后页面白屏再也无法唤起微信App。根本原因在于iOS微信的WKWebView对window.location.href跳转有严格限制尤其是跳转到微信自有域名wx.tenpay.com时会触发安全拦截。解决方案只有一个改用a标签模拟跳转。!-- 错误做法 -- script document.getElementById(payBtn).onclick function() { window.location.href https://wx.tenpay.com/...; }; /script !-- 正确做法 -- a idh5PayLink hrefhttps://wx.tenpay.com/... styledisplay:none;/a script document.getElementById(payBtn).onclick function() { document.getElementById(h5PayLink).click(); }; /script原理是a标签的click()事件被iOS微信视为用户主动触发不受WKWebView的跳转限制。而window.location.href赋值被视为脚本自动跳转被拦截。这个技巧在2023年iOS微信v8.0.45之后依然有效是我在线上环境反复验证过的。H5支付的价值在于它打破了微信生态的围墙让微信支付能力可以延伸到任何浏览器环境。但它不是“万能钥匙”而是“条件反射式”的支付通道——你必须精确满足微信网关的所有前置条件它才会为你打开那扇门。4. Native支付原生App里的“无感支付”如何落地——扫码与被扫的本质统一Native支付常被误解为“只有App才能用”其实它的本质是脱离Web容器的、由原生代码直接驱动的支付协议。无论是你家楼下超市的扫码枪扫你手机上的付款码还是你用美团APP点外卖时右上角弹出的微信支付弹窗背后都是Native支付在工作。它的优势在于彻底摆脱了WebView的兼容性问题、JS注入失败风险、跨域限制等Web侧顽疾但代价是开发成本更高且必须为iOS和Android分别集成SDK。4.1 Native支付的两种形态扫码支付模式一与被扫支付模式二Native支付在微信支付文档中分为两种模式但它们共享同一套API体系区别仅在于支付请求的发起方不同模式一扫码支付商户系统生成一个支付二维码用户打开微信“扫一扫”扫描该码微信客户端解析后发起支付。典型场景餐厅桌牌、便利店收银台、共享单车。模式二被扫支付用户在微信内打开“收付款”页面出示付款码商户用扫码设备扫描该码商户系统拿到auth_code后调用统一下单API完成支付。典型场景地铁闸机、医院挂号、菜市场摊贩。这两种模式的共同点是支付请求均由商户系统而非用户手机发起。这意味着商户必须有自己的服务器能调用微信支付API且能安全存储API密钥。你无法在纯前端H5页面里实现Native支付——因为auth_code被扫或code_url扫码的生成必须经过商户服务器与微信支付网关的密钥协商。4.2 扫码支付模式一从生成二维码到用户扫码的完整闭环扫码支付的流程是Native支付中最直观的一种。我们以一个自助咖啡机为例用户选择商品咖啡机屏幕显示“美式咖啡 18元”用户点击确认。咖啡机发起统一下单咖啡机内置的Linux系统或联网的MCU调用unifiedorderAPItrade_typeSCANbody美式咖啡out_trade_noCOFFEE20240405123456total_fee1800。微信返回code_urlAPI成功响应中包含code_url字段值为weixin://wxpay/bizpayurl?prAbcDef123。咖啡机渲染二维码将code_url用QR Code库如qrcode.js生成图片显示在屏幕上。用户扫码用户打开微信“扫一扫”对准屏幕上的二维码微信客户端解析weixin://协议自动唤起支付界面。支付结果通知用户确认支付后微信服务器向咖啡机的notify_url发送POST通知咖啡机收到后启动出杯电机。这里的关键是code_url的生成。它不是一个普通URL而是微信定义的自定义协议URI。pr后面的字符串是微信支付网关生成的预支付凭证有效期2小时。咖啡机无需理解其含义只需原样渲染为二维码即可。注意code_url必须用标准QR Code格式Version 2Error Correction Level M否则部分老旧扫码枪无法识别。我测试过用qrcode-generator库生成的二维码在华为Mate 20的扫码枪上识别率99%但在某些国产POS机上只有70%。解决方案是生成二维码后用zxing库在服务端做一次解码验证确保code_url能被正确还原。4.3 被扫支付模式二商户扫码枪如何安全获取用户付款码被扫支付的难点不在技术而在用户隐私与风控。用户在微信“收付款”页面出示的付款码每分钟刷新一次且包含设备指纹信息。商户扫码枪扫到的auth_code是一个6位数字22位字母数字组合如123456abcd1234567890efgh它本身不包含金额只是一个临时授权凭证。商户系统拿到auth_code后必须立即调用统一下单APItrade_typeAUTH_CODE并传入auth_code扫码枪读取的28位字符串body商品描述out_trade_no商户订单号total_fee订单金额单位分微信支付网关会实时校验auth_code的有效性是否过期、是否已被使用、是否来自合法设备并冻结对应用户的账户余额。如果校验通过返回prepay_id商户系统再调用payAPI完成支付。这里有个严重安全风险auth_code一旦泄露攻击者可在1分钟内盗刷用户账户。因此商户系统必须确保auth_code只在内存中短暂存在绝不写入日志、数据库或网络传输明文。最佳实践是扫码枪通过USB串口将auth_code直接传给商户服务器进程服务器收到后立即调用APIAPI响应后立即将auth_code变量置为null。我曾审计过一家连锁超市的收银系统发现他们的日志文件里明文记录了auth_code且日志文件权限为644所有人可读。这意味着任何能访问服务器的员工都可以用这些auth_code在测试环境模拟支付。整改方案是在日志框架中增加敏感字段过滤器对auth_code、openid、key等字段自动脱敏为***。4.4 App内集成React Native与Flutter如何安全调用Native支付当你的业务跑在React Native或Flutter这样的跨平台框架中时“Native支付”意味着你必须桥接原生模块。以React Native为例iOS端需集成WechatOpenSDK调用[WXApi sendReq:req]发起支付。req对象包含partnerId商户号、prepayId预支付ID、nonceStr、timeStamp、package、sign等字段全部由商户后端生成并下发。Android端需集成com.tencent.mm.opensdk调用IWXAPI.sendReq(req)参数结构与iOS一致。关键点在于签名计算必须在服务端完成。因为sign的生成依赖API密钥而密钥绝不能硬编码在前端代码中会被反编译提取。正确流程是RN前端调用自己封装的wxPay({orderId: 123})方法该方法向商户后端发起/api/wx/prepay请求传入订单ID后端查询订单调用unifiedorderAPI获取prepay_id后端用prepay_id API密钥生成sign连同其他参数一起返回给RN前端RN前端将参数透传给原生模块由原生代码调用微信SDKFlutter同理需用platform_channel与iOS/Android原生代码通信。切记任何试图在Dart或JS层计算sign的做法都是重大安全隐患。Native支付的终极价值在于它把支付能力下沉到了操作系统层面让用户感觉“支付就是点一下的事”。但这份“无感”建立在商户对微信支付协议的深度理解和对原生开发的扎实功底之上。它不是“选一种支付方式”而是“选择一种与微信支付网关直连的通信范式”。5. 三种方式的交叉场景与避坑指南当用户环境模糊时如何做出最优决策现实业务中用户从来不会按教科书的方式访问你的页面。他可能在微信里点击链接也可能在短信里点击链接可能用iPhone也可能用安卓可能微信版本很老也可能刚升级到最新版。当“JSAPI、H5、Native”的边界变得模糊时如何设计一套鲁棒的支付路由策略这是我过去三年在多个高并发项目中沉淀下来的实战经验。5.1 环境识别的黄金法则User-Agent Referer Cookie三位一体单纯依赖前端JavaScript检测如WeixinJSBridge、navigator.userAgent.indexOf(MicroMessenger)是危险的。因为iOS微信v8.0.30移除了WeixinJSBridge全局对象Android微信对userAgent做了伪装MicroMessenger字符串可能被隐藏用户可能禁用JavaScript真正的环境识别必须由服务端主导综合以下三个HTTP HeaderHeader作用可靠性User-Agent判断是否为微信客户端、iOS/Android、微信版本号高但可伪造Referer判断来源是否为微信域名https://mp.weixin.qq.com/、https://servicewechat.com/中可为空Cookie检查是否携带微信OAuth2.0授权后的openid如wx_openidxxx高需提前埋点我的标准做法是在用户首次访问时后端生成一个临时session_id存入RedisTTL设为30分钟。同时如果检测到微信环境立即发起OAuth2.0静默授权snsapi_base获取openid并存入该session_id对应的Hash中。后续所有支付请求都携带此session_id后端据此判断用户身份和环境。例如一个请求的Header如下User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.45(0x18002d2d) NetType/WIFI Language/zh_CN Referer: https://mp.weixin.qq.com/ Cookie: session_idabc123; wx_openidoAbc1234567890xyz后端逻辑若Referer以mp.weixin.qq.com开头且Cookie中有wx_openid→JSAPI支付若Referer为空或非微信域名且User-Agent含MicroMessenger→H5支付若User-Agent含Mobile但不含MicroMessenger如短信链接→H5支付若请求来自App内WebView且User-Agent含MyApp/1.0.0→Native支付这套逻辑覆盖了99%的线上场景。剩下1%的异常比如用户清除了Cookie但仍在微信内我们会fallback到JSAPI支付并在wx.config失败时前端自动重试H5支付。5.2 支付失败的优雅降级从JSAPI到H5的平滑过渡用户点击支付按钮后理想流程是JSAPI唤起支付。但如果wx.config失败或wx.chooseWXPay调用报错如invoke:fail invalid signature前端必须能无感切换到H5支付。关键在于降级动作必须由前端触发但参数必须由后端重新生成。流程如下前端调用wx.chooseWXPay监听fail回调在fail回调中向后端发起/api/wx/fallback请求传入原始订单ID后端收到请求重新调用unifiedorderAPItrade_typeMWEB生成新的mweb_url前端拿到mweb_url执行window.location.href mweb_url这里有两个细节决定成败订单幂等性/api/wx/fallback接口必须检查该订单是否已存在H5支付记录避免重复下单。我在Redis里用HSET fallback_orders {order_id} {mweb_url}TTL设为10分钟。用户体验降级过程不能让用户感知到“失败”。我的做法是在支付按钮上加一个旋转loading图标fail回调触发后图标继续旋转同时静默跳转用户只看到页面刷新了一下。5.3 安全红线永远不要在前端暴露API密钥所有签名计算JSAPI的paySign、H5的mweb_url签名、Native的sign都必须在服务端完成。我见过太多团队为了“减少请求”把密钥硬编码在React Native的index.js里结果被反编译工具一键提取。微信支付密钥一旦泄露攻击者可伪造任意订单你的资金池将在
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进