ARTICLE DETAIL

资讯详情

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

支付宝JSAPI支付与手机网站支付区别:H5接入避坑实战

支付宝JSAPI支付与手机网站支付区别:H5接入避坑实战 接到需求的时候业务方嘴上很轻松“就在官网H5页面里加个支付宝支付用JSAPI就行不用做小程序。”我当场愣了一下。支付宝的JSAPI支付官方文档写得很明白它是给支付宝小程序用的支付能力没有小程序容器压根唤不起来。后来真正跑完一圈才发现大家口中的“某付宝JSAPI转0无需小程序”其实被揉进了好几件事有人想借JSAPI的名字在H5里唤起支付宝有人想把订单金额转成0元方便内测还有人压根分不清手机网站支付和小程序支付的区别。这篇我把从方案选型到落地的完整过程写下来给同样被“JSAPI”绕晕的人一个参考。1. 支付宝JSAPI支付和“网页里弹支付”完全是两码事1.1 支付产品选型对照表先看一张对照表。这张表是我每次给项目做支付选型时都会先列出来的能避免绝大多数名词混淆。支付产品典型使用场景是否需要小程序入口形态JSAPI支付小程序支付支付宝小程序内购物、充值、会员开通必须要有且用户要在小程序环境里小程序内调起收银台手机网站支付手机浏览器H5页面、公众号外链、短信落地页不需要浏览器跳转支付宝收银台电脑网站支付PC官网收银台、桌面端网页不需要浏览器跳转支付宝扫码/登录支付APP支付原生安卓/iOS App不需要唤起支付宝APP收银台当面付线下扫码、门店POS不需要支付宝APP扫码或付款码我把“是否需要小程序”单独列一列就是想说明一个事实支付宝里真正不需要小程序的网页支付产品叫手机网站支付不叫JSAPI。JSAPI支付在官方文档里的定位非常明确它对应的是支付宝小程序环境后端下单后由前端小程序代码调起支付收银台。很多人一搜索“JSAPI”脑子里浮现的第一反应是微信支付里的JSAPI——公众号H5页面通过JSSDK调起微信支付。这个印象太深了以至于到了支付宝这里也默认JSAPI就是H5通用支付。这是最大的误区源头。1.2 为什么跨平台开发者老把微信JSAPI的印象套到支付宝上微信支付的产品体系里JSAPI支付确实是用于“服务号里面的网页支付”的所以H5开发者对“JSAPI”这个词很熟悉。但支付宝的产品命名体系不一样支付宝的JSAPI支付就是指“支付宝小程序支付”和微信的JSAPI只是撞了名。如果你以前做过微信支付再来接支付宝一定不要想当然。微信的公众号H5支付、微信的JSAPI支付、支付宝的手机网站支付、支付宝的小程序JSAPI支付四者之间没有一一对应关系。我见过不止一个团队把支付宝JSAPI支付的后端接口当成H5支付接入下单接口都调通了结果前端提示“请在支付宝小程序内打开”当场卡住。1.3 服务端下单与前端唤起是两套逻辑简单拆一下支付宝JSAPI支付的完整链路是这样后端调用下单接口生成预支付订单拿到一个支付参数串前端在小程序代码里调用支付宝提供的my.tradePay把支付参数串传进去支付宝在小程序内弹出收银台用户完成支付支付结果通过小程序回调、服务端异步通知两条通道返回。手机网站支付的链路则是后端调用手机网站支付接口支付宝返回一段HTML表单或者一个跳转链接直接把用户浏览器导向支付宝收银台用户支付完成后跳回同步返回URL同时服务端收到异步通知。看到没有两者不只是接口名不同整个产品路径都不同。所谓“无需小程序”指的是手机网站支付这条路它根本不依赖小程序环境用户在网页里点一下支付按钮就行。而“某付宝JSAPI转0无需小程序”如果要落地成一个正常业务真正要做的是“改用手机网站支付”而不是去改造JSAPI支付。2. 真·无需小程序的支付接入手机网站支付完整跑通既然脱离了小程序那H5场景里最标准、最不容易出错的方式就是接支付宝“手机网站支付”。下面按我实际操作过的顺序拆一遍。2.1 开通手机网站支付的前置条件先确认你的支付宝账号完成了企业实名认证个人账号没法签约支付产品。然后在支付宝开放平台创建应用添加“手机网站支付”功能提交签约。签约审核一般很快但有些类目会要求补充资质建议提前把营业执照、网站备案信息准备好。这里要特别说一句如果只是联调测试不用等签约通过直接用沙箱环境就行但生产环境一定以签约状态为准很多人在这一步踩坑——沙箱里跑通了切到正式环境却发现接口报“产品未开通”。密钥方面现在统一用RSA2。在开放平台上生成应用私钥、应用公钥把应用公钥上传获得支付宝公钥。私钥一定放在服务端别写进前端代码里。证书模式比公钥模式更安全但流程稍复杂日常项目公钥模式够用。2.2 下单接口和最小代码示例手机网站支付的下单接口是alipay.trade.wap.pay。我一般用Java的官方SDK逻辑比较少核心就是把AlipayTradeWapPayRequest的bizContent填好然后拿到支付宝返回的表单直接输出到页面。AlipayTradeWapPayRequest request new AlipayTradeWapPayRequest(); request.setNotifyUrl(https://api.example.com/pay/notify); request.setReturnUrl(https://www.example.com/order/result); request.setBizContent({ \out_trade_no\:\2025022012350001\, \total_amount\:\0.01\, \subject\:\联调测试商品\, \product_code\:\QUICK_WAP_WAY\ }); String form alipayClient.pageExecute(request).getBody(); // 直接把form输出到HTTP响应即可 response.setContentType(text/html;charsetutf-8); response.getWriter().write(form);这段代码里几个参数要解释清楚out_trade_no商户订单号必须唯一。我习惯用日期业务前缀流水别用时间戳直接怼很容易重复。total_amount支付金额单位是元字符串格式最多两位小数。subject商品标题会展示在用户的支付宝账单里。product_code手机网站支付固定传QUICK_WAP_WAY这个不能改。setNotifyUrl异步通知地址支付成功后支付宝服务器会往这里发POST请求。setReturnUrl同步跳转地址用户支付完成后浏览器会跳到这里。SDK的pageExecute方法返回的是自动提交的HTML表单这样实现跳转比自己去搞302重定向要稳尤其在浏览器各种安全策略下不容易被拦截。2.3 前端跳转细节如果项目是服务端渲染的页面直接把表单输出去就行。如果是前后端分离的SPA可以让前端请求一个下单接口后端把这段form字符串返回给前端前端再用一个隐藏的form节点submit出去。这里有个容易踩的坑不要用window.open去打开下单接口返回的URL很多浏览器会拦截新窗口用户看着页面毫无反应。quit_url参数需要单独提一下。它不是必填但建议在产品要求不那么严格的时候加上作用是用户在中途退出收银台时页面会跳回这个地址。不加的话用户退出后往往会卡在一个空白中间页体验很差。我一般会在下单参数里加quit_url指向订单中心页这样用户的路径是闭环的。2.4 异步通知验签和幂等支付完成后的异步通知是整个链路里最需要认真对待的一环。支付宝会向notify_url发送一个POST请求参数里包含订单号、交易号、实收金额、trade_status等但参数可能是伪造的所以拿到通知后第一步必须验签。SDK提供了AlipaySignature.rsaCheckV1方法传入支付宝的参数Map、支付宝公钥、字符集和签名类型返回true才继续处理。boolean signVerified AlipaySignature.rsaCheckV1( params, alipayPublicKey, UTF-8, RSA2); if (!signVerified) { return failure; }验签通过之后还要做业务校验检查out_trade_no对应的订单是否存在检查total_amount是否和本地订单金额一致检查trade_status是否为TRADE_SUCCESS或TRADE_FINISHED防止重复通知检查订单状态是否已更新。全部通过后更新订单状态最后向支付宝返回一个文本success注意不是JSON也不是ok就五个英文字母。只要返回的不是success支付宝会按照间隔策略反复重试通知时间可能拉得很长。幂等处理一定要做我见过上线第一天因为重复通知导致订单流水被插了两条的生产事故。3. “转0”到底转的是什么0元订单、营销活动与金额校验标题里的“转0”一定会有人好奇这里专门说一下。我理解的“转0”大多数情况下是指支付金额为0元的场景以及一些测试场景里想“把订单金额变成0元跑通流程”的操作。3.1 哪些合规业务会用到“0元”正常的业务里0元订单是真实存在的但基本不会走支付网关。比如营销活动里用户领了一张全额抵扣券下单后应支付金额是0元。这时候正确的做法是后端直接把订单标记为已支付或者待发货不给用户调支付宝收银台。再比如测试环境里大家不想花真钱所以喜欢把金额设置成0.01元来做全链路联调而不是真的设成0元。还有一种更常见的需求是想“校验0元单能不能调起收银台”我劝你别踩这个坑。支付宝对交易金额有硬性校验total_amount必须大于0单位最大位数是两位小数传0元或者空字符串都会被网关直接拒绝并返回金额范围错误。3.2 支付宝对金额的硬性约束从支付宝开放平台的角度看一笔支付交易的核心要素是金额、收款方、付款方、订单号。商家在后台创建订单时金额是入参里必须有的字段而且系统会在网关侧做二次校验。即使你手动构造一个请求把total_amount改成0也会报错把签名里的金额和业务参数里的金额改得不一致又会报签名错误。所以在正常的支付接口里不存在“把金额转为0然后走支付成功流程”的合法通道。如果有人声称能“转0”要么是诱导你走营销工具/代金券体系要么就是绕过了正常支付链路后者是明确要规避的方向。正规项目里老老实实做金额一致性校验比研究任何“转0技巧”都实际。3.3 回调金额校验服务端才能决定钱数这里要展开一个特别重要的工程习惯金额永远以服务端为准。我见过一些刚入行的同事把前端传来的total_amount直接塞进后端下单接口。这个做法非常危险。等于用户随便改一下请求参数就能以任意金额发起支付。虽然支付宝收银台展示的金额看起来是他改的那个数但最后真正扣款时如果产品逻辑有漏洞就会造成资损。正确做法是用户在前端只提交“商品ID/订单ID”和数量后端根据业务数据计算不可篡改的应付金额后端保存订单快照再用这个快照里的金额去调支付宝异步通知回来后把支付宝回调里的金额和本地订单金额做比对不一致就告警并挂起人工审核。只要做到这四步前端把它改成0元、1分钱、负数影响都为零。因为后端每次都拿自己的订单数据去生成新的支付请求回调也只认自己存过的金额。3.4 优惠后应付0元怎么处理如果业务上确实存在优惠后应付0元我的处理方案是这样的下单时先判断应付金额是否大于0如果大于0走支付宝收银台如果等于0则在服务端直接创建订单状态置为“已支付/已完成”并生成支付流水记录不调用任何支付宝接口。如果要给用户一个“确认下单”的动作就做一个普通的前端确认弹窗不需要支付收银台。这种做法完全合规而且用户体验反而更好用户不用跳去支付宝转一圈再回来。唯一要注意的是0元订单也要有订单号、操作日志、风控记录方便后续对账。4. 从“小程序支付转H5支付”掉进去的坑在把既有项目从支付宝小程序支付迁到H5支付的过程中我碰到了不少问题很多都很有代表性。4.1 工具卡死和依赖冲突我一个老项目里本来已经有小程序支付模块想着用官方提供的“智能转换/兼容辅助”工具把代码从支付宝小程序支付改成H5支付结果工具在解析项目依赖时直接卡死CPU飙满几十分钟都没反应。后来用进程分析才发现项目里同时引用了小程序SDK的支付依赖和手机网站支付的SDK两套SDK有同名类打包工具在解析时互相覆盖导致死循环。解决办法很直接不要想着把一个小程序支付页面直接“转换”成H5支付页面这两套支付在代码结构上是两套体系。老老实实新建一个干净的H5支付模块只引入alipay-sdk-java或对应语言的SDK把下单、跳转、回调单独封装。卡死的工具问题本质上不是工具不行而是老项目的依赖环境本身就有问题。4.2 微信浏览器里的白屏问题H5支付页面如果在微信浏览器里打开点击“支付宝支付”按钮后有一定概率打开的是空白页或者提示“已停止访问”。原因是微信会拦截支付宝的跳转scheme不允许在当前WebView内直接唤起支付宝APP。正常做法是检测到当前浏览器是微信环境时在页面里给出提示让用户点击右上角“在浏览器中打开”然后再跳支付宝收银台。有些团队会和支付宝申请“微信内H5支付”的白名单能力申请通过后可以在微信内无缝拉起支付宝收银台但不是默认开通的需要额外签约和配置。我的建议是如果主要流量在微信内优先接入微信支付而不是硬在微信里做支付宝H5支付。4.3 APP内WebView唤起支付宝在原生APP内置WebView里调起手机网站支付比移动浏览器要麻烦。安卓端需要在shouldOverrideUrlLoading里拦截URL如果URL以alipays://或alipay://开头就启动一个Intent跳转到支付宝APP。不做这步页面会一直卡在收银台加载中。iOS端相对简单但现在支付宝也要求通过Universal Link等方式处理需要App在工程里配置关联域名。我建议把这块处理逻辑做成一个公用的WebView工具前后端同事共用不要在每个页面里各自写一遍。否则会出现安卓可以支付、iOS白屏的问题排查起来非常痛苦。4.4 沙箱与正式环境混用有一次联调时我拿正式环境的APPID去请求了沙箱网关结果接口返回“应用不存在”。反过来有人拿沙箱的密钥配置去跑生产日志报“签名验证失败”。我把最容易混用的东西列个表环境网关地址APPID密钥体系测试账号沙箱环境openapi.alipaydev.com沙箱应用APPID沙箱密钥/沙箱支付宝公钥虚拟买家账号正式环境openapi.alipay.com正式应用APPID正式密钥/正式支付宝公钥真实支付宝账号看起来很简单但一旦配置文件里多个环境共用很容易漏改其中一个。我习惯把环境和密钥配置做成独立profile启动时强制校验当前环境参数和网关域名是否匹配不匹配直接启动失败从源头杜绝。4.5 同步/异步地址的域名规则支付宝对return_url和notify_url有要求必须是公网可访问的HTTPS地址不能是localhost也不能是带查询参数的地址同时域名主体需要和开放平台配置一致。开发时如果想用内网穿透工具临时联调要确保工具生成的HTTPS域名没被支付宝拉黑并且后台配置好回调地址白名单。这里有个小经验异步通知和同步跳转的地址不要直接写在业务方法里而是做成配置项。每次环境切换时确保域名一起切少配一个就收不到回调。5. 选型建议什么时候坚持“无需小程序”什么时候老实做小程序5.1 场景选型速查表在一次需求里到底该用哪种支付方式我一般是按这张表来对你的页面/环境推荐支付方案理由支付宝小程序内支付宝JSAPI支付官方指定产品体验最顺手机浏览器H5支付宝手机网站支付无需小程序跳转收银台流程简单微信公众号内用户强依赖微信优先微信JSAPI支付若必须支持支付宝提示浏览器打开微信内直接调支付宝容易被拦截PC官网支付宝电脑网站支付扫码/登录支付都有人用原生App支付宝APP支付直接唤起支付宝客户端原生体验最好营销活动0元单不走支付接口后端直接锁单支付网关不支持0元合规做法是内部成单5.2 我的几条经验如果说有什么最想提醒的就是不要被“无需小程序”这种话带偏。如果你连自己页面运行在什么容器里都没搞清楚选型就会出错。小程序页面就是小程序支付H5页面就是手机网站支付App页面就是App支付它们不能说互相“转换”只能按产品形态重新接入。第二个经验是支付这种模块尽量不要自己从头造轮子尽量使用官方SDK的最新稳定版。网上有些老博客教的“拼接参数原生RSA签名”虽然原理没问题但很容易在字符集、URL编码、空值处理上出错。官方SDK把这些都封装好了升级也及时省下的是调试成本。第三个经验是先做回调验签再做页面接入。很多人上来就调下单接口看到表单跳出来就以为成功了结果异步通知没验签、金额没核对直到生产环境被用户薅了一次羊毛才发现问题。支付流程的最后一公里永远是后端对账和风控。6. 最后分享几个让联调事半功倍的小习惯回到开头那个需求我最终的落地其实很朴素H5官网接的是支付宝手机网站支付联调金额用的是0.01元回调验签和金额一致性校验放在了最前面。所谓“某付宝JSAPI转0无需小程序”在我这里落成了一句大白话小程序支付和H5支付是两条路0元单不走支付网关服务端校验不能偷懒。有几个小习惯我后来一直保留着。第一沙箱环境一定先跑通同步跳转、异步通知、重复通知、验签失败这四个用例再上生产能省一大半线上问题。第二日志里完整打印支付宝回包和本地请求参数验签失败时先看号是不是被解析成了空格这个坑非常隐蔽。第三数据库里金额统一用“分”存储展示层再转成“元”避免浮点误差和支付宝交互时再转成字符串两位小数。第四每次发布支付相关代码都要在灰度环境下一笔真实小额支付确认回调链路通了才放量。支付这个东西看着接口就几个参数真正考验人的是边界场景。把名词搞清楚把金额关系守好把回调验签做到位你就算不开发小程序也能稳稳接住支付宝支付。
返回列表