Skip to content

微信小程序支付,从关联商户号到收到第一笔钱 ​

小程序能下单了,点付款却弹一句 appid 和 mch_id 不匹配。你打开商户平台看了三遍,AppID 明明填了。

或者更气人的:钱付出去了,微信扣了,订单还挂在「待支付」。

踩错的代价 ​

小程序支付这条链路,真正卡人的地方不在代码,在两个后台之间。

三种死法,一种比一种晚发现:

第一种,当场调不起。 用户点付款直接弹错。这种最好——它当场告诉你坏了,你还有机会在上线前修。

第二种,能付但订单不变。 用户钱付了,你服务器没收到通知。用户以为没付成功,要么再付一次,要么来找你退款。这种要等到有人投诉才发现,而在那之前你一直觉得自己配得挺好。

第三种,审核被卡。 提审时带着充值入口,被要求补资质,一轮一周。等你补完,这个月过去一半了。

至于四样凭据(APIv3 密钥、API 证书、证书序列号、微信支付公钥)怎么配、别配混,那是另一篇的事,这里不重复。这篇只讲小程序特有的那几步。

正确步骤 ​

一、关联商户号和小程序:这事得两边都点 ​

这是最高频的坑,因为它是双向的,而一半人只做了前一半。

小程序和商户号是两个独立账号,默认互不认识。你得给它们互相介绍一下:

  1. 微信支付商户平台 →「产品中心」→「AppID 账号管理」→ 关联 AppID,填小程序 AppID
  2. 然后回到小程序后台(mp.weixin.qq.com)→「微信支付」→ 找到这个商户号 → 确认关联

第 2 步不做,商户平台那边就一直挂着「待确认」。而报错是 appid 和 mch_id 不匹配——它不会说「你还差一步确认」,它就说不匹配。于是你回商户平台反复检查那个 AppID 有没有填错,检查到怀疑人生。

AppID 没错。是另一边没点。

主体不一致的情况另说,那个要多走一道确认流程,坑更深,单独一篇讲。

二、支付授权目录:小程序不用配,而我配了一下午 ​

商户平台的「开发配置」里躺着「支付授权目录」「JSAPI 支付授权目录」。

我第一次接的时候看见就顺手配了。填域名、填路径、改了四五版、清缓存、重新提交,就是不生效。当时的想法是「肯定是我路径写错了」,于是又改了三版。

后来才搞明白:小程序下单根本不看这个目录。

支付授权目录是给公众号内网页的 JSAPI 支付用的。
(注意跟微信支付的「H5 支付」不是一回事——那个是微信外的手机浏览器,另一条产品线。)

小程序只要 AppID 关联对了就能调起,跟这个目录一毛钱关系没有。

我在一个不需要配的地方,认认真真配了一下午。

做小程序支付,这一步直接跳过。

三、回调地址:三个硬要求,缺一个就是第二种死法 ​

回调地址(notify_url)不通,就是前面说的「付了钱订单不变」。

  1. 必须 https,http 不收
  2. 域名必须已备案、公网能访问
  3. 不能带参数,问号后面那串去掉

判断方法很简单:下单能成功,说明凭据全对;订单不变,九成就是这里。 别再去翻密钥了。

第四个要求没写在文档里:这个地址是每个端一条,不是全局一条。

我数了一个真实项目的回调入口,一个 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 个字段给前端,前端直接透传:

js
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. 收银台调得起、能付成功
  2. 回调收到了,订单从待支付变成已支付 ← 最关键
  3. 商户平台后台查得到这笔,金额对得上

只在前端看到「支付成功」不算数。那可能只是微信那边成功了,你的订单还在原地。

六、提审前先把充值入口关掉 ​

小程序审核期间,如果带着充值功能,可能被要求补充资质。一轮一周。

建议提审前先关掉入口,过审再开。

这条不在任何官方文档里,是被拒过才知道的。

关的时候注意别关错层。 我看的那个项目里,微信支付的总开关
是一个配置项,而它在整个后端只有 1 处被读——就在下单服务里,
下单之前判一次。

只有 1 处读,意味着关掉它是服务端彻底不给下单,
不是「前端把按钮藏起来」。

审核要的是后者:入口看不见。前者会让已经在用的用户直接付不了款。

提审关的是入口,不是能力。 这两件事在代码里通常不是一个开关,
动手前先确认你关的是哪个。

常见报错对照表 ​

报错真正的原因解法
appid 和 mch_id 不匹配关联只做了商户平台那一半,没回小程序后台确认两个后台各点一次,确认状态不是「待确认」
调起支付时「支付验证签名失败」signType 还写着 MD5,或签名串顺序/换行错了v3 固定 RSA;签名串按 AppID/时间戳/随机串/package 四行,每行都要换行符
requestPayment:fail 参数错误package 少了 prepay_id= 前缀,或 timeStamp 传了数字补前缀;时间戳转成字符串
下单成功,订单一直待支付回调地址非 https/未备案/带参数/端口被拦换成 https 备案域名的干净地址,服务器放行
签名失败,但密钥证书都对paySign 里填的是商户号,不是小程序 AppID换成小程序 AppID
金额差 100 倍金额单位是分不是元传整数分
提审被驳回,要求补充资质提审时带着充值功能先关掉充值入口,过审再开

这条链路上的其他几篇

  • 《微信支付 v3 的证书和密钥,四类东西别再配混了》

签名-B

大粽子