上周帮一个做烘焙工作室的朋友把小程序商城收尾,别的都顺,就卡在支付上耗掉整整两天。其实微信支付文档写得不算差,但真正落到自己项目里,总会有些文档没明说、只有踩过才知道的地方。这篇就把我这次和小程序支付对接时踩的坑整理出来,给同样在做的朋友省点时间。
先把前置条件捋清楚
很多人一上来就写代码,结果报错连原因都看不懂。小程序要能发起支付,至少得先满足这几件事:
- 小程序必须是已认证的企业主体,个人主体开不了微信支付;
- 在微信支付商户平台完成商户号申请,并和小程序 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 完全两套逻辑,别混着抄旧代码。
坑三:回调必须幂等
支付回调这件事,微信会重试,网络也可能重复投递。我第一次写回调处理时没做幂等,测试环境里同一笔订单被处理了两次,库存扣成负数,闹了个笑话。
处理回调的正确姿势:
- 验签,确认请求确实来自微信;
- 解密
resource字段拿到订单信息; - 用订单号查本地记录,如果已经是「已支付」状态,直接返回成功,不重复处理;
- 处理业务逻辑(改订单状态、扣库存、发通知),最好放在事务里;
- 返回
{code: 'SUCCESS'},否则微信会一直重试。
另外回调处理要快,别在里面做发短信、调第三方接口这类耗时操作,容易超时导致微信重复回调。可以先把状态落库,异步再做后续动作。
坑四:前端只认 success,不认「已支付」
wx.requestPayment 的 success 回调只代表用户完成了支付动作,不代表钱一定到账。真正的到账依据是服务端的回调。我见过有项目前端 success 后直接跳「支付成功」页并解锁权益,结果用户支付中途取消或者回调延迟,就出现白嫖或者重复付款的争议。
稳妥的做法是:前端 success 后轮询后端订单状态,或者跳到一个「支付结果确认中」的页面,等后端确认后再展示结果。用户体验上多等一两秒,但逻辑上干净很多。
坑五:退款和账单别等出事才想
支付对接不只是收款。上线前最好把退款接口也跑通一遍,尤其是部分退款和退款回调。我朋友那个店后来遇到顾客要退单,才发现退款回调没配,只能手动在商户平台操作,很被动。
还有对账。每天拉一次交易账单,和本地订单比对,能及早发现掉单、重复支付这类问题。这件事前期花半小时写个脚本,后期能省掉大量扯皮。
小结
小程序支付对接本身不算复杂,难的是那些文档里一笔带过、实际却会卡住你的细节:签名规则、参数结构、回调幂等、结果确认、退款对账。把这五块提前想清楚,基本就能一次跑通。真遇到报错,先看返回的 err_code 和 err_code_des,再对照签名串逐字段核对,大多数问题都能自己定位。

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