上周帮邯郸丛台区一个做社区团购的客户排查线上问题,用户下单后钱扣了,订单却显示未支付。翻日志发现是支付回调没做幂等,微信重复通知把订单状态改乱了。这种问题在本地商超、餐饮、教育类小程序里特别常见——支付功能看着简单,真接起来坑不少。这篇就把我在邯郸做小程序开发这些年踩过的支付对接经验整理一下,尽量说人话。

一、先搞清楚你要接哪种支付
很多邯郸的老板一上来就说“我要接微信支付”,其实得先分清场景:
- JSAPI 支付:用户在微信里打开小程序直接付款,最常见,商城、点餐、缴费都用它。
- 小程序支付:严格说就是 JSAPI 在小程序端的形态,调
wx.requestPayment唤起收银台。 - Native 扫码支付:PC 网站或线下大屏用,生成二维码让用户扫。
- H5 支付:微信外浏览器里用,邯郸不少做本地生活导航的会用到。
选错类型,后面接口对不上,白折腾。做邯郸app开发时如果 App 里嵌了小程序,也要注意支付渠道别混用。
二、商户号和小程序要“绑对”
这一步是新手最容易卡住的地方。你需要:
- 在微信支付商户平台注册企业商户号(个体户也能申请,但资料要齐)。
- 把小程序 AppID 和商户号做关联,在商户平台「产品中心-APPID 授权管理」里操作。
- 下载 API 证书,配置 APIv3 密钥。
我见过邯郸永年一位做紧固件批发的客户,小程序是找别人做的,商户号是自己注册的,两边没关联,调支付一直报“商户号与 AppID 不匹配”。折腾两天才发现是授权没做。所以接手项目第一件事,我都会先核对这两者的绑定关系。
三、下单接口:金额单位别搞错
微信支付金额单位是分,不是元。前端传 9.9 元,后端要乘 100 变成 990。这个错在本地餐饮小程序里我至少见过五次,用户付了 9.9 结果扣了 990,退款加道歉,口碑直接崩。
// 后端下单示例(伪代码,思路为主)
const order = await wxpay.createOrder({
appid: APPID,
mchid: MCHID,
description: '邯郸知码客-社区团购订单',
out_trade_no: orderNo, // 自己生成的唯一单号
notify_url: 'https://yourdomain.com/api/pay/notify',
amount: { total: Math.round(price * 100), currency: 'CNY' },
payer: { openid: userOpenid }
});
return order.prepay_id; // 返回给前端唤起支付
注意 out_trade_no 必须全局唯一,重复下单会报错。建议用“业务前缀+时间戳+随机数”。
四、前端唤起支付
wx.requestPayment({
timeStamp: res.timeStamp,
nonceStr: res.nonceStr,
package: res.package,
signType: 'RSA',
paySign: res.paySign,
success: () => {
// 注意:这里只代表唤起成功,不代表钱到账
wx.redirectTo({ url: '/pages/order/result?no=' + orderNo });
},
fail: (err) => {
if (err.errMsg.indexOf('cancel') === -1) {
wx.showToast({ title: '支付失败,请重试', icon: 'none' });
}
}
});
划重点:success 回调不等于支付成功。用户可能付了但网络断了,也可能没付就返回。真正的到账依据是服务端的支付回调通知。
五、回调处理:三个必须做的动作
1. 验签
APIv3 用平台证书验签,别图省事跳过。跳过验签等于把订单状态交给任何人改。
2. 幂等
微信会重复通知,同一笔单可能来好几次。处理前先查订单状态,已支付就直接返回成功,别再改数据。开头说的丛台区那个客户就是栽在这。
3. 主动查单兜底
回调可能因为网络、服务器重启丢失。建议加个定时任务,对“待支付超过 5 分钟”的订单主动查一次支付状态。邯郸有些本地商户服务器配置一般,回调丢失概率不低,这层兜底很值。
// 回调处理核心逻辑
async function handleNotify(body) {
const data = decryptResource(body.resource);
const order = await db.findOrder(data.out_trade_no);
if (order.status === 'PAID') return { code: 'SUCCESS' }; // 幂等
if (data.trade_state === 'SUCCESS') {
await db.updateOrder(order.id, { status: 'PAID', transactionId: data.transaction_id });
await notifyMerchant(order); // 通知商家
}
return { code: 'SUCCESS' };
}
六、退款和分账也别忽略
本地做教培、健身的小程序经常要退款。退款接口同样要传商户退款单号,也要做幂等。如果是平台型业务(比如邯郸本地多个商家入驻的商城),还要考虑分账,这个要在下单时就标记 profit_sharing,事后补不了。
七、几个本地项目里总结的小经验
- 测试阶段用 1 分钱跑通全流程,别用真实大额。
- 回调地址必须 HTTPS,且公网可访问,本地调试用内网穿透。
- 把支付日志单独存一份,出问题好对账。邯郸不少客户月底对账时才发现差异,有日志能省大量时间。
- 小程序后台的「支付设置」和商户平台的配置要同时检查,两边任一没配好都不通。
支付对接这事,代码量不大,但细节多。我在邯郸做小程序和 APP 开发这些年,见过太多因为回调、金额、绑定这些小问题返工的案例。与其上线后救火,不如接的时候就把幂等、验签、查单兜底这几件事做扎实。知码客这边接本地项目,支付模块基本都是按这套流程走,稳一点,睡得踏实。

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