ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

微信H5支付调不起?从weixin://到h5_url的正确跳转姿势

微信H5支付调不起?从weixin://到h5_url的正确跳转姿势 做微信支付对接这几年遇到最多的一个咨询就是后端明明已经把weixin://wap/pay?prepayidxxx返回给前端了前端也老老实实用location.href去跳结果手机上要么没反应要么提示“已停止访问该网页”要么虽然跳到了微信但收银台根本不出来。如果你也卡在这一步先别急着怀疑是证书、签名或者参数问题大概率是这一个“支付链接”本身就站错了位置。这篇内容我会从微信H5支付的完整调起链路讲起把weixin://wap/pay?prepayid到底什么时候能用、什么时候不能用、以及真正能拉起微信客户端的正确姿势一次性说清楚适合正在对接微信H5支付的前端、后端和联调测试同学参考。1. 先把H5支付的调用链路彻底捋清楚1.1 官方定义的H5支付流程到底是什么微信H5支付官方也叫WAP支付核心使用场景是用户在手机浏览器里打开一个H5页面然后通过这个页面唤起微信App完成付款。注意这个场景明确说的是“非微信内置浏览器”。你在微信里聊天收到一个链接点进去后是在微信内置浏览器中打开的这种场景不能走H5支付应该走公众号支付JSAPI支付。很多同学一开始就把这两条路混在一起后面所有的问题都跟着乱。官方完整的H5支付链路是这样的用户在H5页面点击支付按钮前端把订单信息交给自己的后端后端调用微信支付的下单接口微信支付返回一个h5_url前端拿着这个h5_url跳转手机浏览器打开这个链接后进入微信支付的中间页中间页做环境判断、来源校验、参数校验校验通过后再拉起微信App用户收银台输入密码完成支付最后回到原浏览器同时微信异步通知商户后端订单结果。这个链路里有几个关键角色商户H5页面、商户后端、微信支付API、微信支付的中间页、微信App客户端。weixin://wap/pay?prepayidxxx在这条链路里只出现在“中间页拉起微信App”的那一瞬而且是由微信支付中间页内部触发的不是给商户H5页面直接拿来用的。你可以把它理解成一把保险柜钥匙钥匙本身是真的但只有银行工作人员在特定流程里才能用它打开柜门。你拿着钥匙在银行门口自己捅锁孔当然开不了。1.2 weixin://wap/pay?prepayid 到底是什么东西weixin://wap/pay?prepayidxxx是一个URL Schemeweixin://是微信App注册给系统的协议头后面的wap/pay是路由prepayid是订单预支付ID。这类Scheme的作用是告诉操作系统“把后面的参数交给微信App处理”。微信App收到之后会根据prepayid去后台查询订单详情然后弹出收银台。听起来好像很简单问题在于微信App不是谁给一个Scheme它都认。出于安全和风控考虑微信客户端会校验这个Scheme是从哪个页面发起的。如果是从微信支付官方中间页跳过来的微信App认为来源可信才会继续处理如果是从一个普通商户网页直接发起的微信App会判定为可疑调用直接忽略或者中断表现出来就是没反应、无法打开网页、或者打开了微信但收银台不弹。还有一个很容易被忽略的点prepayid只是一个订单标识它本身不包含订单金额、商品名称、商户号这些展示信息。收银台要展示什么是微信App根据prepayid去后台实时查的。所以就算你把这个Scheme拼出来了只要来源校验不过一切都白搭。微信官方从来不会要求开发者主动去拼这个Scheme所有调起动作都由微信支付中间页完成。2. 为什么拿着正确参数还是调不起五个高频原因2.1 绕过官方中间页直接跳Scheme导致来源校验失败这是我在大量咨询里见到最多的情况。很多开发者的后端SDK或者同事封装的老代码里会把下单返回的prepay_id拿出来拼成weixin://wap/pay?prepayidxxx觉得这样可以少跳一个中间页支付体验更快。实测下来这种写法在大部分手机上都是失效的。原因前面已经解释了微信客户端对收银台的拉起有严格的来源校验。正确的做法是让前端跳转到官方返回的h5_url这个URL以https://wx.tenpay.com开头是一个正常的网页地址。浏览器先打开这个网页中间页再通过自己的逻辑去触发weixin://协议拉起微信App。你直接在页面里跳到weixin://等于把最关键的校验环节跳过了微信App当然不认。注意一旦发现代码里有人在拼weixin://wap/pay?prepayid第一反应不应该是去调试前端为什么没跳转而应该先回去看后端为什么返回了这样一个错误链接。链路源头错了后面怎么调都是白费。2.2 下单接口用错JSAPI订单被当成H5订单用微信支付的下单接口是按场景区分的。H5支付用的是/v3/pay/transactions/h5接口公众号/微信内网页支付JSAPI用的是/v3/pay/transactions/jsapi接口。两个接口都会返回prepay_id但后续的调起方式完全不同。如果你后端点错了下单接口比如用了JSAPI下单然后把返回的prepay_id拼成weixin://wap/pay?prepayid交给前端前端在微信外浏览器里跳转基本是必挂的。JSAPI支付必须在微信内置浏览器里通过wx.chooseWXPay调起前端还需要先拿appId、timestamp、nonceStr、signature去调用微信JSSDK的wx.config根本不是拿到一个prepay_id就能跳的。检查方法很简单让后端把实际调用下单接口的URL打出来看一眼确认用的是H5接口、接收的是h5_url字段。老版本V2接口里这个字段叫mweb_url虽然名字变了但它同样是一个https开头的链接而不是weixin://。如果后端返回给你的东西里压根没有h5_url或mweb_url那基本可以断定链路不对。2.3 商户平台域名白名单、场景信息与支付页面不匹配H5支付还有一个非常容易忽略的配置项H5支付域名。商户号开通H5支付权限后需要在微信支付商户平台的“产品中心-开发配置-H5支付”里填写可用的H5支付域名这个域名必须是你实际发起支付页面的域名而且通常要求是已备案的HTTPS域名。同时后端调用H5下单接口时scene_info.h5_info里也要传wap_url和wap_name这个wap_url应该指向真实支付页面的域名。如果商户平台配置的域名是m.yourdomain.com但实际支付页面跑在test.yourdomain.com或者下单接口里填的wap_url是www.yourdomain.com一旦微信风控校验出实际页面域名和下单信息不一致就可能出现调不起、报错、甚至直接拦截订单的情况。我遇到过最典型的一种情况是线上环境一切正常测试环境怎么跳都起不来最后发现测试环境用的域名和商户平台配置的H5支付域名不一致。所以联调时要么用同一个域名要么提前把测试域名也加到商户平台的域名白名单里不要等到手机上报障了才反应过来。2.4 前端跳转姿势不对自动跳、iframe跳、异步回调慢半拍即使你拿到了正确的h5_url前端怎么写跳转也有讲究。iOS和Android浏览器对“网页主动唤起第三方App”这件事有比较严格的限制普遍要求必须是用户主动点击触发的行为也就是用户手势User Gesture。如果页面一加载就在onload或DOMContentLoaded里自动执行window.location.href h5_url很多浏览器会直接拦截表现是页面没反应或静默失败。还有的开发者把h5_url放在iframe里想在页面内静默打开中间页这更不可行。微信支付中间页很可能设置过X-Frame-Options或 CSP 响应头禁止被内嵌到其他页面就算能打开scheme唤醒App在iframe里也经常不生效。正确做法是用一个按钮用户点击后在click事件回调里同步执行window.location.href h5Url不要在跳转前插入异步的接口请求或者setTimeout否则手势上下文可能失效。这个点在后端同学看来可能匪夷所思但在iOS Safari上真的有差别。用户的点击手势是有“有效期”的你在click回调里先做一次await ajax(...)等接口返回再跳转iOS会认为这次跳转不是用户主动触发的直接把scheme拦截掉。谁写谁知道。2.5 支付环境本身不支持微信内置浏览器、PC浏览器、未装微信H5支付只适用于非微信内置浏览器的手机网页环境。用户在微信里点链接打开你的H5页面然后在这个页面里发起H5支付微信会提示“请在微信外打开链接”或者干脆不拉起收银台因为微信内置浏览器对这种唤起自己App的操作限制得非常死。这种情况正确方案是后端用JSAPI下单前端在微信里用wx.chooseWXPay调起。PC浏览器场景也类似电脑上没有微信客户端可以拉起虽然现在有扫码登录版微信但H5支付的设计不是给PC端的。PC端要用Native扫码支付让用户用微信扫码完成付款。另外如果手机上没有安装微信就算h5_url跳转得再正确系统也没有App能响应weixin://协议一样失败。这些属于业务场景选型问题不属于调起Bug但实际联调时经常被混在一起排查浪费时间。3. 从拿到prepayid到成功拉起收银台的实操流程3.1 第一步确认后端返回的到底是h5_url还是拼出来的weixin://不要看任何人的口头说法直接看真实接口返回。打开浏览器开发者工具或者用vConsole看前端收到的下单返回数据。如果返回的字段叫h5_url且值是以https://wx.tenpay.com开头的地址说明后端接口基本对了。如果返回的是weixin://wap/pay?prepayidxxx那就要把后端代码拎出来看到底是怎么生成这个值的。后端如果拿的是官方SDK正确写法应该是读取下单接口返回里的h5_url字段原样返回给前端不要去解析prepay_id更不要自己拼接。有些老项目里有同学为了“通用”写了一个工具方法把prepay_id包成各种支付协议这种封装害人不浅建议看到这种代码直接重构。为了方便判断你可以把页面里实际拿到的返回值和后端日志对照。后端日志里如果只有prepay_id没有h5_url那大概率是下单接口调错了或者SDK调用方式有问题。还有一个排查技巧直接在手机浏览器地址栏手动输入后端返回的h5_url按回车访问。如果手机能正常唤起微信收银台说明后端链路没问题问题出在前端页面跳转方式上如果手动输入也不行继续往后查。3.2 第二步前端用按钮点击触发跳转不要自动跳确认后端返回的是h5_url之后前端代码可以写成下面这样最简单也最稳button idpayBtn typebutton确认支付/button script document.getElementById(payBtn).addEventListener(click, function () { // 这里的 h5Url 应该由后端接口返回不要在前端写死 var h5Url https://wx.tenpay.com/cgi-bin/mmpayweb/bin/checkup?prepay_idxxx; window.location.href h5Url; }); /script核心要点就两条第一跳转动作必须发生在click事件里第二跳转前不要插任何异步操作。如果你确实需要先向后端要h5_url那就在用户点击按钮时先请求下单接口接口成功返回后立刻把h5_url赋值给一个隐藏的a链接然后用a.click()模拟点击或者直接改成两段式按钮先调下单接口拿到h5_url后页面弹出一个“点击去微信支付”的二次确认按钮让用户再点一次。虽然多了一次点击但成功率会高很多也符合支付平台的交互安全规范。3.3 第三步核对商户平台配置与下单场景参数如果跳转了但最终拉起失败或者压根下单就报错回头检查这张配置表检查项要求不一致时的典型表现商户号是否开通H5支付产品中心里存在H5支付产品下单报NOAUTHH5支付域名与实际支付页面域名完全一致含协议头中间页拒绝跳转或风控拦截下单接口类型使用/v3/pay/transactions/h5返回字段不是h5_urlscene_info.h5_info.wap_url与实际页面域名一致下单报INVALID_SCENE_INFOamount.total单位是分1元传100收银台金额不对或报参数错误out_trade_no同一商户号下唯一不能重复使用下单报OUT_TRADE_NO_USED其中最容易出错的是amount.total的单位问题。很多人联调时拿一个1元订单测试结果把1直接传给接口实际上要传100。单位不对时微信收银台可能显示0.01元也可能直接报“商家参数格式有误”。这个坑和调不拉起微信客户端是两个不同层面的问题但经常一起出现排查时不要漏。另外H5支付要求后端下单时传scene_info.payer_client_ip这个IP应该是发起支付用户的真实客户端IP不能写死或随便填。如果IP非法或者缺失H5下单接口可能直接报错。测试环境没有真实外网IP时可以先填一个公网出口IP兜底但上生产前一定要改成动态获取真实IP。3.4 第四步真机验证准备一台Android和一台iOSH5支付的最终效果必须在真机上验证而且最好Android和iOS各准备一台。微信开发者工具里模拟不出真实的浏览器唤起App行为PC端浏览器上的表现也不能代表手机端。Android手机建议用Chrome或者系统自带浏览器测试iOS用Safari测试这是H5支付最主流的两个浏览器环境。Android上可以用chrome://inspect连远程调试iOS上可以用Safari的“开发”菜单调试真机页面或者更简单直接在页面上挂一个vConsole把前端收到的接口返回和错误信息都打出来。重点看三个时间点点击按钮时h5_url是否正确window.location.href是否执行了页面有没有被系统拦截并停留在原页面。如果点击后页面短暂白屏又回到原页面大概率是浏览器拦截了跳转优先调整跳转方式。如果页面跳到了微信中间页但没能继续拉起微信App重点看微信中间页上的具体报错文案大多数情况下文案会直接告诉你是域名问题、参数问题还是场景问题比你自己瞎猜快得多。4. 高频报错和排查实录4.1 报错“无法打开网页”或“已停止访问该网页”这是直接跳weixin://wap/pay?prepayidxxx时最经典的报错。在iOS Safari上系统弹窗会提示“无法打开网页因为网址无效”在Android Chrome上可能显示“已停止访问该网页”。本质都是系统没能把weixin://协议正确传递给微信App或者微信App收到了协议但校验不通过。解决办法就是前面说的不要直接跳Scheme改成跳官方h5_url。如果你已经在跳h5_url但还是出现这个报错再检查中间页是否被浏览器拦截比如跳转时是否脱离了用户点击手势或者h5_url是否在中间被某些脚本拦截修改了。4.2 跳转后提示“请在微信外部浏览器打开链接”或“请在微信客户端打开链接”这个提示的意思是当前支付页面是在微信内置浏览器里打开的而H5支付不允许这种环境。常见场景是开发者在微信里拿手机号登录测试点开自己的H5页面然后发起支付微信直接拦住了。正确的做法是在代码里检测当前是否在微信内置浏览器里。如果是优先引导用户“右上角打开浏览器”再继续支付或者直接切换成JSAPI支付流程。JSAPI支付需要后端额外调用JSAPI下单接口前端引入微信JSSDK通过wx.chooseWXPay调起收银台整个逻辑和H5支付完全不同不要试图用同一个后端下单接口去适配两种场景。4.3 拉起微信后收银台提示“商家参数格式有误”或“网络不给力”能拉起微信App说明h5_url和中间页链路是通的问题出在订单参数或预支付ID本身。这时候优先怀疑这几个点订单是否已经关闭或过期prepay_id有效期大约2小时但实际测试环境往往更短appid和mchid是否匹配prepay_id对应的订单金额、商品信息是否与下单时一致后端是否在返回h5_url之前误操作把订单给关了。建议打开微信支付商户平台在“交易中心-订单查询”里用out_trade_no查一下订单实际状态。如果订单存在且状态正确再对比下单请求参数和商户平台配置。如果提示“网络不给力”除了参数问题还有可能是商户号在风控中或者H5支付域名没有通过审核。这些信息在商户平台的消息中心一般都能查到。4.4 同一套代码Android正常iOS无法拉起Android和iOS对URL Scheme的唤起机制差异很大。Android系统中很多浏览器允许页面通过Scheme唤起App限制相对宽松而iOS Safari要求页面跳转必须由用户手势触发还会在跳转前弹窗询问用户是否允许打开微信如果开发者用了自动跳转或异步跳转iOS会直接认为不是用户主动行为不弹窗也不跳。另一个常见情况是iOS微信版本过低或系统设置里限制了跨App跳转。遇到这种问题先换一台纯系统环境、Safari没被改过的iPhone试试排除用户侧原因。代码层面最稳妥的写法就是用户点击按钮后同步跳转不要存任何异步等待。如果业务上必须在下单接口返回后再跳那就做成两段式第一个按钮调下单接口拿到h5_url之后再展示第二个“去微信支付”按钮用户再点一次。4.5 常见返回码速查表返回码/错误含义解决方法NOAUTH商户号没有H5支付权限商户平台申请开通H5支付APPID_MCHID_NOT_MATCHAppID与商户号不匹配检查商户平台绑定的AppIDPARAM_ERROR请求参数不合法按接口文档逐项核对参数H5_URL_INVALIDH5场景信息不合法检查scene_info.h5_info配置OUT_TRADE_NO_USED商户订单号重复重新生成唯一订单号ORDERPAID订单已支付去商户平台核对订单状态SYSTEMERROR微信支付系统异常按相同参数重试或稍后再试5. 多踩几次坑之后总结的几条实在经验5.1 别再手写weixin://也别让后端封装这种协议微信支付官方文档里所有H5支付的示例返回给前端的都是h5_url或者V2时代的mweb_url从来没有让商户自己拼weixin://wap/pay?prepayidxxx。我见过不少老项目为了兼容多种支付方式在后端封装了一个“万能支付链接生成器”把支付宝的alipays://和微信的weixin://放在一起处理结果微信这边频繁失灵。微信和支付宝的唤起机制不一样混在一个封装里只会让问题更复杂。微信H5支付就是把h5_url原样返回、原样跳转不要画蛇添足。5.2 测试环境尽量用和线上一致的域名这里说的“一致”不仅仅是主域名一致而是协议头、域名、端口、路径前缀都要尽可能贴近线上。微信H5支付的风控会校验页面域名和下单信息测试域名如果没有提前配置很容易出现线上没问题、测试调不起的情况。如果条件允许测试环境直接使用线上同一域名的不同子路径或者预先在商户平台把测试域名加进H5支付白名单。另外联调时不要一直在微信开发者工具里点。微信开发者工具体现不出手机浏览器的Scheme唤起行为很多问题在工具里根本复现不了。老老实实用手机浏览器测出现问题时用真机调试看日志比在工具里换各种配置高效得多。5.3 支付完成后的回跳不要依赖微信自动处理H5支付完成之后微信默认会返回到发起支付的浏览器页面但具体落到哪个页面、触发什么回调在不同手机和不同浏览器上表现不完全一样。不要在前端写死“支付完成后自动跳转到结果页”更不要在支付回调里做强制跳转很容易出现用户已经在微信里付完款、回到浏览器后页面还停在支付页的情况。我习惯的做法是用户点击支付后在前端监听visibilitychange或pageshow事件当用户从微信切回浏览器时主动向后端查一次订单状态根据订单状态更新页面内容。这样即使微信没有按预期回跳页面也能在用户重新可见时刷新成“已支付”状态。别小看这一点H5支付项目里大量客诉其实不是支付失败而是支付成功后页面没刷新用户以为钱白花了。5.4 用户点击到真正跳转之间的“手势有效期”问题最后再分享一个我自己踩得很深的坑。有一阵子用户反馈“支付按钮点了没反应”排查半天发现前端在点击事件里先上报PV日志等日志接口返回成功后才去跳转h5_url。日志接口快的时候没问题慢的时候几百毫秒iOS Safari直接就把这次跳转当成非用户手势触发的行为拦截了。后来改成点击后立即跳转h5_url日志上报放到另一个异步任务里跟跳转互不影响问题瞬间消失。H5支付这类强依赖浏览器唤起第三方App的场景用户点击手势的有效时间窗口非常短。任何在跳转之前的异步等待、弹窗、倒计时都可能让这个窗口失效最终表现就是用户明明点了按钮页面却没反应。以后再排查“点了没反应”的问题先看看跳转前有没有异步操作把这个问题排除掉再去看配置域名能省下很多排查时间。
返回列表