ARTICLE DETAIL

资讯详情

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

小程序支付和公众号支付到底有何区别?场景、openid与避坑指南

小程序支付和公众号支付到底有何区别?场景、openid与避坑指南 说句实话直到现在还有不少刚接微信支付的朋友问我小程序支付和公众号支付到底有什么区别我听到最典型的说法是小程序支付就是微信里付钱公众号支付也是微信里付钱不都一样吗。这话对了一半——它们确实是同一套微信支付体系但落地场景、技术链路、用户感知乃至商户号绑定关系完全是两回事。当年我刚从公众号支付切到小程序支付的时候也天真以为只是换了个前端调用接口结果光一个 openid 的来源差异就折腾了我一下午后面还会细说。这篇文章我把这两条支付路径从用户侧到服务端完整拆开讲清楚适合三类人看刚接手微信支付开发的工程师、需要给产品/老板定接入方案的人以及想知道我到底该接哪个支付的独立开发者。看完你至少能回答三个问题它们底层是不是同一套接口、各自要准备什么、生产环境最容易因为什么翻车。1. 先把概念理清楚这不是两种接口而是两种入口场景很多开发者的误区在于以为小程序支付和公众号支付是两个不同的支付接口好像选了一个就不能用另一个。实际上它们共享的是同一条微信支付通道区别在于用户是从哪个微信生态场景发起的支付。说得直白点支付大脑是同一个只是小程序和公众号给大脑接了不同的神经。1.1 同一个支付大脑两条不同的神经通路微信支付的底层能力是统一下单V2 时代叫 unifiedorderV3 时代拆分成transactions/jsapi、transactions/h5、transactions/app等更细的接口。不管是小程序支付还是公众号支付服务端做的事都是同一个拿着订单信息向微信支付下单换回一个prepay_id。不一样的地方在哪里往下看对比维度公众号支付JSAPI 网页支付小程序支付用户所处的容器微信内置浏览器里打开的 H5 页面网页微信小程序运行环境原生页面获取 openid 的方式OAuth2 网页授权snsapi_basewx.login()拿 code再调code2Session前端拉起支付的 APIWeixinJSBridge.invoke(getBrandWCPayRequest, ...)wx.requestPayment(...)支付收银台呈现形式网页内嵌的微信支付收银台浮层小程序原生拉起的一整页收银台用户能否返回支付完通过 redirect 跳回商户页面支付结果通过小程序页面onShow感知适用的 appId公众号的 appid小程序的 appid是否需要绑定公众平台/开放平台里把公众号绑定到商户号小程序后台把小程序绑定到商户号这里最容易被忽略的点是** openid 的来源归属**。每一个微信用户在公众号AppID下有一个 openid在小程序AppID下又有另一个 openid两者完全不同——除非你把公众号和小程序绑到同一个开放平台账号下才可以通过 unionid 识别同一用户。所以你不能拿小程序支付返回的 openid 去请求公众号支付的 JSAPI 下单微信会直接甩你一句openid和appid不匹配。1.2 换个角度这不是支付能力的差别是载体场景的差别我习惯用停车场来打比方微信支付就像一个大型停车场车辆用户进场的入口可以是大门闸机公众号网页也可以是地下车库直梯小程序。停车场都是同一个但进场的凭证openid不一样指引牌支付方式也不一样。你拿着地下车库的钥匙去开大门的闸机门卫自然不放行。所以当产品经理问公众号支付和小程序支付功能上有什么区别吗的时候正确的回答是支付本身没有区别但用户怎么进到这个支付页面来这件事区别很大。小程序支付是用户已经在你的小程序里了点击支付按钮微信原生收银台弹出来公众号支付是用户先在微信里看网页网页里点付费微信收银台浮层盖在网页上。如果把这个差异理解透了后面的技术链路、参数、踩坑就都好理解了。2. 用户视角的真实差异一个在 App 内付一个在网页里付技术先放在一边我们先说人话你自己作为用户在小程序里付钱和在公众号文章/H5网页里付钱体验完全不一样。2.1 小程序支付的用户路径全程原生体验小程序支付的完整用户路径是用户点击微信底部的发现或者从聊天框进入你的小程序在小程序里挑选商品、下单到收银台页点击立即支付微信直接拉起一个全新的原生支付界面有商品信息、支付金额、付款方式用户可以用指纹/面容或者输密码支付完成微信收银台自动收起回到了小程序页面。整个过程里用户从头到尾感觉都在微信的 App 环境内部完成操作没有跳出感也没有浏览器地址栏这种干扰信息。微信对小程序支付的定义是应用内支付它和水滴筹捐款、拼多多下单、美团外卖付款是同一类东西。从支付成功率的角度说小程序支付明显优于公众号支付因为它的整个链路更短、误触更少、安全信任感更强。这里有个细节小程序支付完成后你要在小程序页面的onShow生命周期里主动查询订单状态因为从前端拿到的返回errMsg: requestPayment:ok只代表用户确实完成了收银台操作不代表服务端的支付结果已经落定。严谨的做法是等异步通知后面第五部分专门讲。2.2 公众号支付的用户路径网页里的隐藏式支付公众号支付微信内部的叫法是 JSAPI 支付也叫公众号网页支付的用户路径则是用户在微信里打开一篇公众号文章、一个菜单链接或者扫了公众号二维码网页加载可能是商城 H5、课程购买页、文章赞赏页用户点支付按钮的时候微信在网页顶部拉出一个收银台浮层你会看到熟悉的微信支付收银界面但它的背景还是你那个网页支付完成后收银台收起页面根据返回参数跳回你指定的 success 页面或者调用页面内的回调。这个体验在小屏幕手机上有时候会显得层上加层但它是公众号生态里唯一合法的支付方式。有人会问那能不能在微信里打开一个普通 H5 网页直接用微信支付答案是不行——如果你在浏览器比如 Safari里打开同一个网页微信支付按钮会直接报当前页面无法使用微信支付因为你离开了微信内置浏览器JSBridge 根本不存在。所以公众号支付的前提是用户必须停留在微信客户端内置浏览器里。这个限制是做 H5 电商的同学必须提前知悉的。2.3 体验差异带来的业务影响从业务角度这两种支付的场景选型一般是这样你的核心阵地是小程序商城/小程序内容付费→ 必须接小程序支付因为用户在原生环境里下单转化率高你有公众号粉丝想在图文/菜单里引导用户付费→ 接公众号支付让用户从文章直接跳 H5 购买你两边都要→ 同一个商户号可以同时绑定小程序和公众号两个 appid 的支付都接但需要注意两套 openid 体系下文讲绑定关系。很多团队会犯的错是做了小程序商城却把公众号图文里的支付按钮直接跳转到小程序里让用户支付——这没问题但你在公众号图文里只能用 URL Link 跳小程序不能图文内嵌一个网页收银台。反过来说如果你只有公众号没有小程序你就必须老老实实走公众号支付。所以不是你想用哪种就能用哪种而是你的业务入口决定了支付入口。3. 技术链路拆解下单接口、openid 来源和签名算法全对比这一部分是核心我尽量讲得细一点。两种支付的前端调用方式完全不同但后端下单的目标大体一致——都是生成prepay_id只是获取用户标识的方式、调起支付的 JS 方法、以及请求接口的差异需要注意分清。3.1 小程序支付wx.login换 openidwx.requestPayment拉起收银台小程序支付的技术链路大概是前端在小程序里调用wx.login()拿到一个临时凭证code服务端拿着code请求微信接口https://api.weixin.qq.com/sns/jscode2session带上小程序的appid和secret换回openid和session_key服务端拿到 openid 之后调微信支付下单接口V2 是https://api.mch.weixin.qq.com/pay/unifiedorderV3 是https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi传小程序的appid、mch_id、openid、订单号、金额、回调地址等得到prepay_id服务端用prepay_id生成支付参数timeStamp、nonceStr、packageprepay_idxxx、signType、paySign返回给前端前端调用wx.requestPayment({...})微信拉起原生收银台。伪代码前端小程序里// 小程序端wx.login 拿 code wx.login({ success: (res) { // 拿 res.code 去服务端换 openid 下单 wx.request({ url: https://api.example.com/pay, data: { code: res.code, orderId: ... }, success: (resp) { // 服务端返回的支付参数 const { timeStamp, nonceStr, package: pkg, signType, paySign } resp.data; wx.requestPayment({ timeStamp, nonceStr, package: pkg, signType, paySign, success: () { /* 用户支付完成但还需以服务端异步通知为准 */ }, fail: (err) { /* 用户取消或支付失败 */ } }); } }); } });伪代码Node.js 简化示意V2 统一下单const params { appid: wx1234567890abcdef, // 小程序 appid mch_id: 1600000000, out_trade_no: ORDER202501010001, total_fee: 1, // 单位是分填 0.01 元就是 1 body: 测试商品, openid: oABCDEF123456, // 必须是小程序 appid 下的 openid notify_url: https://api.example.com/wxpay/notify, trade_type: JSAPI // 加上 sign 之后 POST 到 unifiedorder };这里有一个经典操作细节total_fee单位是分整数不是元。我见过不少新手拿 0.01 提交然后支付失败原因是金额必须大于等于 1 分且必须是整数。3.2 公众号支付OAuth2 拿 openidWeixinJSBridge拉起收银台公众号支付的链路是前端用户在微信里打开 H5 页面页面先做一个 OAuth2 跳转类似于https://open.weixin.qq.com/connect/oauth2/authorize? appid公众号appid redirect_urihttps://api.example.com/oauth/callback response_typecode scopesnsapi_base statexxx#wechat_redirect微信服务器用户在微信里确认授权snsapi_base是静默授权用户无感知然后 302 跳回你的redirect_uri带上code服务端拿code请求https://api.weixin.qq.com/sns/oauth2/access_token用公众号的appid、secret换回openid和access_token服务端拿openid去调统一下单接口同样 V2 用unifiedorderV3 用/v3/pay/transactions/jsapi注意这里传的appid是公众号 appidopenid 也必须是公众号 openid前端在 H5 页面里用微信内置的 JSBridge 拉起收银台。伪代码前端 H5 页面内拉起支付// 在微信内置浏览器环境中 function onBridgeReady() { WeixinJSBridge.invoke(getBrandWCPayRequest, { appId: 公众号appid, timeStamp: 1700000000, nonceStr: 随机字符串, package: prepay_idxxx, signType: MD5, paySign: 签名值 }, (res) { if (res.err_msg get_brand_wcpay_request:ok) { // 支付完成但以服务端异步通知为准 } }); } if (typeof WeixinJSBridge undefined) { document.addEventListener(WeixinJSBridgeReady, onBridgeReady, false); } else { onBridgeReady(); }注意WeixinJSBridge只在微信内置浏览器里存在所以在普通浏览器里打开这个 H5 页面WeixinJSBridge是undefined支付按钮多半点了没反应或者直接报错。这个点经常被测试吐槽网页打不开支付——先确认是不是在非微信浏览器里测的。3.3 参数与接口路径对照我把两种支付在后端下单时最容易混淆的参数整理成一张表你对照着检查基本不会走偏参数/概念公众号支付小程序支付下单接口V2pay/unifiedorderpay/unifiedorder下单接口V3v3/pay/transactions/jsapiv3/pay/transactions/jsapi同一接口但 appid 不同请求里的appid公众号 appid小程序 appid前端获取 openidOAuth2 网页授权scopesnsapi_basewx.logincode →jscode2session前端拉起支付WeixinJSBridge.invoke(getBrandWCPayRequest)wx.requestPaymentopenid 归属公众号 appid 下的 openid小程序 appid 下的 openid能否互用 openid不能会报 openid 与 appid 不匹配不能支付回调通知统一走notify_url异步通知同一个notify_url异步通知机制相同业务载体微信内 H5/网页小程序原生环境这里最需要记住一个结论V3 的 JSAPI 下单接口虽然名称一样但传的 appid 不同、openid 的获取方式不同最终微信生成的 prepay_id 绑定的是对应 appid 下的用户身份。所以千万别把一套下单代码在两个场景里无脑复用openid 来源必须跟着容器走。3.4 签名的差异几乎一样但有一个坑位两种支付的签名规则本质上是一模一样的MD5 或 HMAC-SHA256V2 和 V3 签名规则有区别但这里不展开 V3 证书签名的细节。我主要提醒一个公众号支付特有的坑公众号支付前端拉起收银台的时候传给getBrandWCPayRequest的参数里有一个appId字段这个字段必须和下单时用的 appid 一致。如果你在小程序里做公众号支付技术上虽然不太会出现或者后端代码复制粘贴时漏改了 appid就会导致前端拉起支付报appid 不匹配。小程序支付的wx.requestPayment则相对简单因为小程序支付请求本来就是发生在小程序环境里的微信天然知道当前是哪个小程序不需要你反复传 appid。提示签名算不对的时候先别急着怀疑加密算法。90% 的情况是字段拼写错了比如把nonceStr写成nonce_str、大小写不一致或者参与签名的参数里有空值。4. 商户接入怎么选主体、绑定关系和适用场景技术讲完回到很多非技术负责人最关心的实际问题我到底应该申请什么、绑定什么、先做哪一个4.1 商户号与 AppID 的绑定关系先说一个常见的理解误区一个商户号不是只能服务一种支付场景的。同一个mch_id可以在公众平台绑定一个公众号也可以在小程序后台绑定一个小程序。所以如果你的业务既有公众号 H5 商城又有小程序商城完全可以共用一个商户号。绑定路径分别是公众号支付登录公众号后台 → 微信支付 → 关联商户号或者在商户平台里添加 AppID 绑定小程序支付登录小程序后台 → 支付设置 → 关联商户号或者在商户平台里添加 AppID 绑定。绑定之后该商户号才能使用对应 appid 发起支付。很多初次接入的人下单时报商户号和AppID不匹配十有八九是绑定关系没有配好。但要多说一句虽然共用商户号没问题但回调地址notify_url可以是同一个域名下的不同路径建议区分开比如/notify/mp和/notify/miniapp方便线上排查问题是哪条支付链路来的。4.2 主体要求与审核约束公众号和小程序都需要已完成微信认证的企业/个体户主体才能开通微信支付。个人主体的小程序和公众号无法开通微信支付除非你是个人小商户的特殊通道微信也有面向小微商户的方案但和个人主体不是一回事。审核上有一个容易翻车的点你那套支付场景必须和实际经营类目匹配。比如小程序是做线上培训的但你选了实物电商这个类目来开通支付可能会被驳回。提交商户资料的时候脑子里要对齐业务模式 类目 支付用途三者的一致性。提示如果是新注册的公众号/小程序建议先完成认证再去申请微信支付否则资质审核过程会到处卡壳。我第一次申请的时候没认证就想先测结果白白等了一个星期邮件来回。4.3 业务场景选型建议我按自己接过的项目经验给一个粗暴的选型建议方便你直接抄作业你的业务形态推荐支付方式理由主阵地是公众号靠文章/菜单带货公众号支付用户在图文/菜单里点开即付不脱离微信内浏览器主阵地是小程序做商城/预约/内容付费小程序支付原生环境转化高支付链路短用户信任感强公众号与小程序都有双链路接入共用商户号H5 商城走公众号支付小程序走小程序支付只有普通 H5且用户可能在微信外打开原生 H5 支付trade_typeMWEB注意微信内普通 H5 不能用 MWEB只能用 JSAPI微信外 H5 用 MWEB 跳转 App纯 App 内支付App 支付trade_typeAPP和公众号/小程序完全不同的另一条链路表格里最后两行是提醒你别把公众号支付和微信内 H5 支付完全画等号——公众号支付是特指微信内置浏览器里的 JSAPI 支付而 H5 支付MWEB是给微信外的浏览器用的。这是另一套知识别混淆。5. 我踩过的那些坑openid 不匹配、sign 校验失败、回调时序问题写这一部分其实比前面讲原理更肉疼因为全是真金白银的线上事故换来的。希望你看完能少走弯路。5.1 坑位一openid 串台拿小程序的 openid 去调公众号下单这个错误我在很早的时候犯过一次当时项目同时接了公众号支付和小程序支付后端封装了一个公共的下单函数参数里带了openid。结果因为函数复用的时候没有区分来源小程序支付传了公众号的 openid或者反过来微信直接返回错误码40003 错误描述openid 错误排查步骤建议这样走先确定你当前下单用的 appid 是哪个公众号还是小程序再看 openid 是从哪个接口换回来的——如果是jscode2session换的那就是小程序 openid如果是sns/oauth2/access_token换的那就是公众号 openid两者对不上就是串台。提示解决办法很简单——同一用户在小程序和公众号里的 openid 不同项目里存储用户标识时不要只存 openid存一份 unionid 或者把 openid 和 appid 一起存。我当时是建了一张用户账户表字段里同时放了mp_openid和miniapp_openid各存各的下单时按渠道取。5.2 坑位二paySign 计算不一致前端拉起收银台失败前端报错最常见的是支付验证签名失败。这个签名是后端算好返回给前端的一般不会随机出问题但以下两个原因我见得太多了参与签名的字段名和微信官方文档不一致公众号支付用的是timeStamp大写 S小程序支付也是timeStamp但很多人习惯在对象里写timestamp小写 s结果签名字符串拼出来不对支付参数里混入了多余字段签名规则是把参与签名的参数按字典序排列后拼成keyvaluekeyvalue最后接上商户 API 密钥。如果你把prepay_id也带上签名了必挂。正确的做法是写一小段与文档字段名完全一致的签名代码并打日志输出参与签名的原始字符串。我把当年的排查 Key Point 写出来参与签名参数V2 公众号支付/小程序支付通用示例 appIdxxxnonceStrxxxpackageprepay_idxxxsignTypeMD5timeStamp1700000000key商户密钥注意package参数的值是prepay_idxxx如果漏掉prepay_id前缀有些开发者手动拼的时候只放了 id签名必错。这个前缀错误超级隐蔽因为前端拉收银台的时候微信会去解析预支付 ID少一个前缀就是非法数据。5.3 坑位三前端回调ok不等于钱已到账这是新手最容易产生误解的地方小程序端wx.requestPayment的 success 回调返回了requestPayment:ok产品就来问用户是不是付款了。严格来说不是。这个ok只表示微信收银台流程走完了用户可能已经付款也可能正在处理中比如银行扣款延迟。真正用于系统入账的必须是服务端异步通知微信支付在收银台流程结束后会向你的notify_url发起一个异步通知以 V2 为例是 XML 报文含return_code、transaction_id、out_trade_no、total_fee等你需要在通知回调里校验签名校验total_fee是否和本地订单一致更新订单状态返回success给微信这里特指字符串successV2 返回 XML 里return_code![CDATA[SUCCESS]]/return_code。同时小程序前端应当在onShow里去查一次订单状态等异步通知落地后通过你自己的接口查询用这个来决定前端展示已支付还是待支付。简单说前端成功仅供参考服务端异步通知才是唯一准绳。5.4 坑位四沙箱环境和密钥混乱导致线上闪现签名错误微信支付尤其是 V2有沙箱环境很多同学先用沙箱调试然后切正式环境的时候忘了把key换回来结果线上支付全部签名失败。这个坑踩进去是全站性的非常狼狈。我个人的习惯是在后端建一个wxpayconfig持久化配置环境标识sandbox/live、API 密钥、商户号、appid 分别存好启动时打印一条当前支付环境: xxx切环境时先看日志再联调。提示还有一个隐蔽点——沙箱环境的appid和mch_id通常也是沙箱专用的别拿真实的 appid 配沙箱 key。如果实在分不清最简单的方式是真机小程序支付测试时直接用正式环境、只支付0.01 元一分的测试单比沙箱环境更接近真实链路也更省心。5.5 坑位五非微信浏览器打开公众号支付页面这个我前面提过但因为它太常见必须再列出来说一下。公众号支付的页面如果在 PC 浏览器、Safari、Chrome 里打开前端WeixinJSBridge根本不存在页面会白屏或者按钮无反应。处理方案是前端做环境检测function isWeChatBrowser() { return /MicroMessenger/i.test(window.navigator.userAgent); } if (!isWeChatBrowser()) { // 提示用户请使用微信扫码打开 }有些开发者想绕过这个限制让用户复制链接到微信里打开也能行但用户路径长了一截转化率会掉。如果你确实需要微信外浏览器也能微信支付那就得走原生 H5 支付MWEB那是另一个方案公众号支付的锅它不背。最后再说两句说个小经验如果你在一个项目里同时接两种支付前后端代码一定不要写在一个文件里硬混。我踩过的所有坑里90% 都是因为公众号支付和小程序支付参数太像了复制粘贴时漏改了一个字段然后排查一小时起步。建议把两种支付拆成两个独立模块各自封装getOpenid、createOrder、parseNotify三个方法互不引用同一份看起来差不多的逻辑。至于选哪个我上面已经给了建议表格。我的真实体会是如果业务从零起步、没有历史包袱优先做小程序支付——原生收银台的用户信任感和支付成功率都比公众号网页里那个浮层高不少。当然如果你的用户都在公众号文章里那也没得选老老实实把公众号支付做好。这两种支付的底层逻辑一通百通只要把 openid 归属和发起容器这两个核心差异刻进脑子里后面无论微信怎么升级接口你都不会慌。
返回列表