3、小程序端调起支付:wx.requestPayment接口、参数组装、支付回调处理、支付成功/失败页面
好,到了这一步,其实才是前端同学最熟悉的战场。后端把预支付订单搞定了,签名也验过了,接下来就该我们小程序端上场了。说白了,就是调起那个微信支付的弹窗,让用户输密码或者刷脸。
嗯,这里要注意。很多新手容易犯一个错:以为拿到 prepay_id 就能直接调支付了。其实还差一步——你得把参数组装成 wx.requestPayment 能认的格式。我刚开始做的时候也踩过这个坑,直接拿后端返回的 prepay_id 往里塞,结果弹窗死活不出来。
3.1 wx.requestPayment 接口长什么样
这个接口是微信官方提供的,专门用来调起支付弹窗。它的参数不多,但每个都很关键。我个人习惯是先列出来,再一个个讲清楚。
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| timeStamp | String | 是 | 时间戳,从 1970 年 1 月 1 日 00:00:00 至今的秒数 |
| nonceStr | String | 是 | 随机字符串,长度不超过 32 位 |
| package | String | 是 | 订单详情扩展字符串,格式为 prepay_id=*** |
| signType | String | 是 | 签名算法,目前固定为 RSA 或 MD5 |
| paySign | String | 是 | 签名,由后端生成,前端直接使用 |
| success | Function | 否 | 支付成功的回调 |
| fail | Function | 否 | 支付失败的回调 |
| complete | Function | 否 | 无论成功失败都会触发 |
你想想看,这些参数里,timeStamp、nonceStr、package、signType、paySign 这五个,其实都是后端算好签名之后返回给你的。前端只需要原样传进去就行。但有一个坑——timeStamp 必须是字符串。我记得有一次后端返回的是数字,我直接用了,结果在 Android 上没问题,iOS 上直接报错。后来排查了半天才发现是类型问题。
String() 转一下。
3.2 参数组装:前端到底要做什么
很多人觉得参数组装就是后端给啥我传啥。其实没那么简单。你想想看,后端返回的数据结构可能跟你接口要求的不一样。比如后端可能返回一个对象,里面包含了所有字段,但字段名可能叫 pay_info 或者 wx_pay_params。你需要把它拆开,重新组装。
我个人习惯是在前端写一个专门的函数来做这件事。这样万一后端改了字段名,我只需要改一个地方。
// 组装支付参数
function assemblePayParams(backendData) {
// 后端返回的数据结构示例:
// {
// prepay_id: 'wx1234567890',
// time_stamp: '1712345678',
// nonce_str: 'abc123',
// sign: 'xxxxxx',
// sign_type: 'RSA'
// }
return {
timeStamp: String(backendData.time_stamp), // 注意转字符串
nonceStr: backendData.nonce_str,
package: 'prepay_id=' + backendData.prepay_id,
signType: backendData.sign_type,
paySign: backendData.sign
};
}
嗯,这里有个细节。package 字段的格式是固定的,必须是 prepay_id=xxx。我曾经见过有人直接传了 prepay_id 的值,忘了加前缀,结果调起支付失败。这种低级错误排查起来特别浪费时间。
3.3 支付回调处理:成功不等于真的成功
调起支付之后,用户会看到微信的支付弹窗。输入密码或者刷脸之后,会触发 success 回调。但这里有个非常重要的概念——前端的 success 回调不代表支付真的成功了。
为什么会这样?因为微信的 success 回调只代表「用户完成了支付操作」,不代表「微信服务器确认了这笔交易」。说白了,用户可能输入了密码,但网络延迟导致微信服务器没收到确认。或者用户刷脸成功了,但后台系统还没更新订单状态。
所以,我建议的做法是:前端只做 UI 反馈,真正的支付结果以后端通知为准。
那前端到底该怎么做?我一般这样处理:
wx.requestPayment({
...payParams,
success(res) {
// 支付操作完成,但结果不确定
// 先跳转到支付中页面,显示「支付处理中...」
wx.navigateTo({
url: '/pages/pay-processing/pay-processing'
});
// 然后主动查询订单状态
checkOrderStatus(orderId);
},
fail(err) {
// 用户取消支付或支付失败
console.error('支付失败', err);
wx.navigateTo({
url: '/pages/pay-fail/pay-fail?reason=' + encodeURIComponent(err.errMsg)
});
},
complete() {
// 无论成功失败,都执行
// 可以用来关闭加载动画
}
});
我曾经遇到过一个真实案例。用户支付成功了,前端也跳到了成功页,但后端因为网络波动没收到微信的回调。结果用户以为支付成功了,实际上订单状态还是「未支付」。后来我们加了一个主动查询的机制,前端在 success 回调后每隔 3 秒查一次订单状态,直到确认结果为止。
3.4 支付成功/失败页面:用户体验的最后一公里
支付结果页面虽然看起来简单,但做不好很影响用户体验。我个人习惯是设计三个页面:
- 支付中页面:显示加载动画,告诉用户「正在确认支付结果」
- 支付成功页面:显示绿色对勾,展示订单金额和商品信息,提供「查看订单」按钮
- 支付失败页面:显示红色叉叉,展示失败原因,提供「重新支付」和「联系客服」按钮
这里有个小技巧。支付失败的原因有很多种,比如用户取消、余额不足、网络超时等。不同的原因应该展示不同的提示文案。我一般会根据 err.errMsg 来判断:
function getFailReason(errMsg) {
if (errMsg.includes('cancel')) {
return '您已取消支付';
} else if (errMsg.includes('balance')) {
return '余额不足,请更换支付方式';
} else if (errMsg.includes('timeout')) {
return '支付超时,请重试';
} else {
return '支付失败,请稍后重试';
}
}
嗯,这里要注意。微信的 errMsg 在不同版本上可能不一样。我建议不要完全依赖字符串匹配,最好后端也返回一个错误码,前端根据错误码来展示文案。
3.5 完整流程串起来
好了,我们把整个流程串起来看看。从用户点击「立即支付」开始,到看到支付结果页面结束:
- 用户点击支付按钮,前端显示加载动画
- 前端调用后端接口,获取预支付参数(prepay_id 等)
- 前端组装参数,调用 wx.requestPayment
- 微信弹出支付弹窗,用户输入密码或刷脸
- 触发 success 回调,前端跳转到支付中页面,同时主动查询订单状态
- 后端确认支付成功,前端收到通知,跳转到支付成功页面
- 如果支付失败或用户取消,跳转到支付失败页面
这个流程看起来简单,但每一步都有坑。我建议你在开发的时候,把每一步的日志都打出来。特别是 wx.requestPayment 的 success 和 fail 回调,里面的 errMsg 信息非常关键。
最后说一句。支付功能是跟钱打交道的,容不得半点马虎。我见过太多因为前端处理不当导致的支付问题。记住一句话:前端只做展示和引导,真正的支付确认交给后端。这样即使出了问题,也能保证数据的一致性。