专注小程序 / APP 开发,用代码改变生活

获取方案
全国分站 主站首页 北京分站 石家庄分站 唐山分站 保定分站 廊坊分站 沧州分站 郑州分站 邯郸分站

小程序支付对接,我踩过的五个坑

小程序开发 2026-10-30 0 阅读 0 点赞 原创

上周帮一个做烘焙工作室的朋友把小程序商城收尾,别的都顺,就卡在支付上耗掉整整两天。其实微信支付文档写得不算差,但真正落到自己项目里,总会有些文档没明说、只有踩过才知道的地方。这篇就把我这次和小程序支付对接时踩的坑整理出来,给同样在做的朋友省点时间。

先把前置条件捋清楚

很多人一上来就写代码,结果报错连原因都看不懂。小程序要能发起支付,至少得先满足这几件事:

  • 小程序必须是已认证的企业主体,个人主体开不了微信支付;
  • 在微信支付商户平台完成商户号申请,并和小程序 appid 完成关联;
  • 服务器配置好支付回调域名,必须是 https,且不能带端口和路径参数;
  • 后端拿到商户 API 密钥,妥善保存,绝不能下发到前端。

这几步里最容易翻车的是「关联」和「回调域名」。商户号和小程序没关联,调统一下单会直接报「appid 与 mch_id 不匹配」,这个错误信息还算友好;回调域名配错,则是支付成功了但订单一直显示未支付,查起来更费劲。

坑一:签名总对不上

第一次调统一下单,返回「签名错误」。我把参数翻来覆去核对,最后发现问题出在签名串的拼接顺序和空值处理上。微信支付的签名规则是:参数按 key 的 ASCII 码从小到大排序,值为空的参数不参与签名,最后拼上 key 做 MD5 或 HMAC-SHA256。

这里有个细节:值为空的参数要整个跳过,而不是拼成 key=。我一开始把空字符串也拼进去了,怎么算都不对。另外金额单位是分,别手滑传成元,否则要么报错要么多收钱。

function buildSign(params, key) {
  const keys = Object.keys(params)
    .filter(k => params[k] !== '' && params[k] !== undefined && params[k] !== null)
    .sort();
  const str = keys.map(k => `${k}=${params[k]}`).join('&') + `&key=${key}`;
  return crypto.createHash('md5').update(str, 'utf8').digest('hex').toUpperCase();
}

建议把签名函数单独抽出来,写几个用例测一下,后面排查问题时能省很多事。

坑二:统一下单和调起支付的参数不是一回事

后端调统一下单接口拿到的是 prepay_id,前端 wx.requestPayment 需要的却是 timeStamp、nonceStr、package、signType、paySign 这几个字段。新手常犯的错是把统一下单的返回直接丢给前端,结果报「参数错误」。

正确做法是后端用 prepay_id 重新按小程序调起支付的规则生成一次签名,返回给前端。而且 package 的值格式是 prepay_id=xxx,别只传 id。

const payParams = {
  timeStamp: Math.floor(Date.now() / 1000).toString(),
  nonceStr: randomStr(32),
  package: `prepay_id=${prepayId}`,
  signType: 'RSA'
};
payParams.paySign = buildSignV3(payParams, privateKey);
// 返回给小程序,前端直接 wx.requestPayment(payParams)

现在新商户基本都是 APIv3 了,签名用 RSA,和 v2 的 MD5 完全两套逻辑,别混着抄旧代码。

坑三:回调必须幂等

支付回调这件事,微信会重试,网络也可能重复投递。我第一次写回调处理时没做幂等,测试环境里同一笔订单被处理了两次,库存扣成负数,闹了个笑话。

处理回调的正确姿势:

  1. 验签,确认请求确实来自微信;
  2. 解密 resource 字段拿到订单信息;
  3. 用订单号查本地记录,如果已经是「已支付」状态,直接返回成功,不重复处理;
  4. 处理业务逻辑(改订单状态、扣库存、发通知),最好放在事务里;
  5. 返回 {code: 'SUCCESS'},否则微信会一直重试。

另外回调处理要快,别在里面做发短信、调第三方接口这类耗时操作,容易超时导致微信重复回调。可以先把状态落库,异步再做后续动作。

坑四:前端只认 success,不认「已支付」

wx.requestPayment 的 success 回调只代表用户完成了支付动作,不代表钱一定到账。真正的到账依据是服务端的回调。我见过有项目前端 success 后直接跳「支付成功」页并解锁权益,结果用户支付中途取消或者回调延迟,就出现白嫖或者重复付款的争议。

稳妥的做法是:前端 success 后轮询后端订单状态,或者跳到一个「支付结果确认中」的页面,等后端确认后再展示结果。用户体验上多等一两秒,但逻辑上干净很多。

坑五:退款和账单别等出事才想

支付对接不只是收款。上线前最好把退款接口也跑通一遍,尤其是部分退款和退款回调。我朋友那个店后来遇到顾客要退单,才发现退款回调没配,只能手动在商户平台操作,很被动。

还有对账。每天拉一次交易账单,和本地订单比对,能及早发现掉单、重复支付这类问题。这件事前期花半小时写个脚本,后期能省掉大量扯皮。

小结

小程序支付对接本身不算复杂,难的是那些文档里一笔带过、实际却会卡住你的细节:签名规则、参数结构、回调幂等、结果确认、退款对账。把这五块提前想清楚,基本就能一次跑通。真遇到报错,先看返回的 err_code 和 err_code_des,再对照签名串逐字段核对,大多数问题都能自己定位。

小程序接口开发:别急着写代码小程序开发文档:别等联调才翻

评论 (0)

还没有评论,快来抢沙发吧~

好想法,值得被认真交付

从小程序、APP 到全栈网站,一站式把想法变成可落地的产品

微信咨询

微信扫码,直接沟通需求