微信小程序支付,从关联商户号到收到第一笔钱
小程序能下单了,点付款却弹一句 appid 和 mch_id 不匹配。你打开商户平台看了三遍,AppID 明明填了。
或者更气人的:钱付出去了,微信扣了,订单还挂在「待支付」。
踩错的代价
小程序支付这条链路,真正卡人的地方不在代码,在两个后台之间。
三种死法,一种比一种晚发现:
第一种,当场调不起。 用户点付款直接弹错。这种最好——它当场告诉你坏了,你还有机会在上线前修。
第二种,能付但订单不变。 用户钱付了,你服务器没收到通知。用户以为没付成功,要么再付一次,要么来找你退款。这种要等到有人投诉才发现,而在那之前你一直觉得自己配得挺好。
第三种,审核被卡。 提审时带着充值入口,被要求补资质,一轮一周。等你补完,这个月过去一半了。
至于四样凭据(APIv3 密钥、API 证书、证书序列号、微信支付公钥)怎么配、别配混,那是另一篇的事,这里不重复。这篇只讲小程序特有的那几步。
正确步骤
一、关联商户号和小程序:这事得两边都点
这是最高频的坑,因为它是双向的,而一半人只做了前一半。
小程序和商户号是两个独立账号,默认互不认识。你得给它们互相介绍一下:
- 微信支付商户平台 →「产品中心」→「AppID 账号管理」→ 关联 AppID,填小程序 AppID
- 然后回到小程序后台(mp.weixin.qq.com)→「微信支付」→ 找到这个商户号 → 确认关联
第 2 步不做,商户平台那边就一直挂着「待确认」。而报错是 appid 和 mch_id 不匹配——它不会说「你还差一步确认」,它就说不匹配。于是你回商户平台反复检查那个 AppID 有没有填错,检查到怀疑人生。
AppID 没错。是另一边没点。
主体不一致的情况另说,那个要多走一道确认流程,坑更深,单独一篇讲。
二、支付授权目录:小程序不用配,而我配了一下午
商户平台的「开发配置」里躺着「支付授权目录」「JSAPI 支付授权目录」。
我第一次接的时候看见就顺手配了。填域名、填路径、改了四五版、清缓存、重新提交,就是不生效。当时的想法是「肯定是我路径写错了」,于是又改了三版。
后来才搞明白:小程序下单根本不看这个目录。
支付授权目录是给公众号内网页的 JSAPI 支付用的。
(注意跟微信支付的「H5 支付」不是一回事——那个是微信外的手机浏览器,另一条产品线。)
小程序只要 AppID 关联对了就能调起,跟这个目录一毛钱关系没有。
我在一个不需要配的地方,认认真真配了一下午。
做小程序支付,这一步直接跳过。
三、回调地址:三个硬要求,缺一个就是第二种死法
回调地址(notify_url)不通,就是前面说的「付了钱订单不变」。
- 必须 https,http 不收
- 域名必须已备案、公网能访问
- 不能带参数,问号后面那串去掉
判断方法很简单:下单能成功,说明凭据全对;订单不变,九成就是这里。 别再去翻密钥了。
第四个要求没写在文档里:这个地址是每个端一条,不是全局一条。
我数了一个真实项目的回调入口,一个 111 行的 Controller,7 条路由:
支付 /wechat/ma 小程序
/wechat/mp 公众号
/wechat/app App
/alipay 支付宝
退款 /wechat/refund/ma
/wechat/refund/mp
/wechat/refund/app微信这边 6 条:三个端 × 支付和退款两件事。
小程序那条是 /wechat/ma,公众号那条是 /wechat/mp——差两个字母。
填串了会怎样:小程序付的款,通知发到了公众号那个处理器上。
那个处理器拿公众号的 AppID 去验签,验不过,返回失败。
微信认为你没收到,按它的重试节奏一遍遍推。
而你这边的日志里是有请求的——所以你会以为回调通了,
转头去查订单更新逻辑。这是这条链路上最费时间的一种错。
顺带一个能一眼看出协议差异的细节:那三个微信回调的返回类型是ResponseEntity<String>,支付宝那条是 String。
差别在于微信要你用 HTTP 状态码表态(成功就是 200 或 204 加空 body),
支付宝只看你 body 里是不是 success。
一个用状态码,一个用报文,抄错一边就是无限重推。
四、前端调起支付
后端拿到 prepay_id 之后,组装 5 个字段给前端,前端直接透传:
wx.requestPayment({
// 秒级时间戳,必须是字符串
timeStamp: res.timeStamp,
nonceStr: res.nonceStr,
// 必须带前缀:prepay_id=wx2026...
package: res.package,
// v3 固定 RSA,不是 MD5
signType: 'RSA',
paySign: res.paySign,
success() {
// 别在这里把订单改成已支付
},
fail(e) {
console.error(e.errMsg)
}
})三个高频翻车点:
signType 从 v2 代码里抄过来忘了改。 v2 是 MD5 或 HMAC-SHA256,v3 固定 RSA。网上能搜到的老代码大半是 v2 的,照抄必挂。
package 要带 prepay_id= 前缀,只传那串 id 调不起来。
timeStamp 传字符串,传数字有些机型会失败——而且是「有些」,所以你自己那台大概率测不出来。
paySign 的签名串是固定四行,顺序不能换,每行末尾都要有换行符:
小程序AppID\n
时间戳\n
随机字符串\n
package\n用商户 API 私钥做 SHA256-RSA 签名再 Base64。这里用的是小程序 AppID,不是商户号——两个都是一串字符,填反了报的还是「签名失败」,你能查上半天。
顺带说一下这一步在后端有多重。
我数了那个项目:拼装这 5 个字段的配置类只有 45 行,
而它前面那个下单服务是 1835 行。
差距不在拼参数,在判断你是哪个端。
那套系统的支付渠道常量有 12 个,小程序只是其中 1 个(值是 mini)。
下单接口是同一个,但它要先认出你是这 12 个里的哪一个,
再决定组装成什么结构。
所以「小程序支付怎么接」这个问题,在后端不是一个独立功能,
是一个 switch 分支。 你接第二个端的时候会体会到——
前端要重写一遍,后端只是多一个 case。
最后那句注释单独展开说:success 里不要改订单状态。
它只代表用户在收银台点了确认,不代表钱到账。真正的状态以服务端收到异步回调为准。前端改状态,遇上用户支付到一半断网、或者付完立刻杀进程,你的订单数据就跟微信对不上了——而对账是月底才做的事,到时候你已经忘了这段代码。
五、验一笔真钱
配完别急着说完事。用真机走一笔小额支付,确认三件:
- 收银台调得起、能付成功
- 回调收到了,订单从待支付变成已支付 ← 最关键
- 商户平台后台查得到这笔,金额对得上
只在前端看到「支付成功」不算数。那可能只是微信那边成功了,你的订单还在原地。
六、提审前先把充值入口关掉
小程序审核期间,如果带着充值功能,可能被要求补充资质。一轮一周。
建议提审前先关掉入口,过审再开。
这条不在任何官方文档里,是被拒过才知道的。
关的时候注意别关错层。 我看的那个项目里,微信支付的总开关
是一个配置项,而它在整个后端只有 1 处被读——就在下单服务里,
下单之前判一次。
只有 1 处读,意味着关掉它是服务端彻底不给下单,
不是「前端把按钮藏起来」。
审核要的是后者:入口看不见。前者会让已经在用的用户直接付不了款。
提审关的是入口,不是能力。 这两件事在代码里通常不是一个开关,
动手前先确认你关的是哪个。
常见报错对照表
| 报错 | 真正的原因 | 解法 |
|---|---|---|
appid 和 mch_id 不匹配 | 关联只做了商户平台那一半,没回小程序后台确认 | 两个后台各点一次,确认状态不是「待确认」 |
| 调起支付时「支付验证签名失败」 | signType 还写着 MD5,或签名串顺序/换行错了 | v3 固定 RSA;签名串按 AppID/时间戳/随机串/package 四行,每行都要换行符 |
requestPayment:fail 参数错误 | package 少了 prepay_id= 前缀,或 timeStamp 传了数字 | 补前缀;时间戳转成字符串 |
| 下单成功,订单一直待支付 | 回调地址非 https/未备案/带参数/端口被拦 | 换成 https 备案域名的干净地址,服务器放行 |
| 签名失败,但密钥证书都对 | paySign 里填的是商户号,不是小程序 AppID | 换成小程序 AppID |
| 金额差 100 倍 | 金额单位是分不是元 | 传整数分 |
| 提审被驳回,要求补充资质 | 提审时带着充值功能 | 先关掉充值入口,过审再开 |
这条链路上的其他几篇
- 《微信支付 v3 的证书和密钥,四类东西别再配混了》
