4-5 封装JSAPI调起支付接口&调试小程序支付
从 prepay_id 到调起支付
上一步获取到了 prepay_id,但小程序前端不能直接使用它。需要将 prepay_id 与其他参数一起进行签名,组装成 wx.requestPayment 所需的参数格式。
后端:getWxOrderInfo 方法
在 PayService 中添加方法,将 prepay_id 转换为小程序可用的支付参数:
/**
* 生成小程序调起支付所需的签名参数
* @param prepayId 预付单 ID
* @returns wx.requestPayment 所需参数
*/
getWxOrderInfo(prepayId: string) {
const appId = this.configService.get<string>('WECHAT_APPID');
const timeStamp = Math.floor(Date.now() / 1000).toString();
const nonceStr = randomstring.generate(32);
// 签名串格式:appId\ntimeStamp\nnonceStr\npackage\n
const message = `${appId}\n${timeStamp}\n${nonceStr}\nprepay_id=${prepayId}\n`;
// 使用商户私钥签名
const paySign = this.sign(message);
return {
appId,
timeStamp,
nonceStr,
package: `prepay_id=${prepayId}`,
signType: 'RSA',
paySign,
};
}
typescript
改造 wechatJsPay 返回支付参数
async wechatJsPay(params: WxPayParams) {
try {
const urlPath = '/v3/pay/transactions/jsapi';
const wxParams: WechatPayOrder = {
...params,
appid: this.configService.get<string>('WECHAT_APPID'),
mchid: this.configService.get<string>('WECHAT_MCHID'),
notify_url: this.configService.get<string>('WECHAT_NOTIFY_PAY'),
out_trade_no: this.getTradeNumber(),
time_expire: dayjs().add(30, 'minute').format(),
};
const body = JSON.stringify(wxParams);
const headers = this.getSignHeaders('POST', urlPath, body);
const result = await this.axiosInstance.post(
`https://api.mch.weixin.qq.com${urlPath}`,
wxParams,
{ headers },
);
// 获取 prepay_id 后,生成小程序支付参数
const prepayId = result.data.prepay_id;
return this.getWxOrderInfo(prepayId);
} catch (error) {
this.logger.error('微信JSAPI下单失败', error.message);
throw error;
}
}
typescript
前端:小程序调起支付
微信原生小程序
wx.requestPayment({
timeStamp: '', // 后端返回的 timeStamp
nonceStr: '', // 后端返回的 nonceStr
package: '', // 后端返回的 prepay_id=wx20141027...
signType: 'RSA', // 固定值
paySign: '', // 后端返回的签名
success(res) {
console.log('支付成功', res);
},
fail(err) {
console.error('支付失败', err);
},
});
javascript
uni-app 项目
在 uni-app 中使用 uni.requestPayment,需要注意参数格式:
// 封装支付请求方法
async function handlePay(orderData: CreateWechatOrderDto) {
try {
// 1. 调用后端接口获取支付参数
const res = await uni.request({
url: '/api/v1/pay/order',
method: 'POST',
data: orderData,
});
const orderInfo = res.data;
// 2. 调起支付
await uni.requestPayment({
provider: 'wxpay', // 支付提供商
orderInfo: orderInfo, // 后端返回的完整支付参数
// uni-app 会自动从 orderInfo 中提取所需字段
});
// 3. 支付成功
uni.showToast({ title: '支付成功', icon: 'success' });
} catch (error) {
console.error('支付失败:', error);
uni.showToast({ title: '支付失败', icon: 'none' });
}
}
typescript
uni.requestPayment 与 wx.requestPayment 的区别
| 对比项 | wx.requestPayment | uni.requestPayment |
|---|---|---|
| 平台 | 仅微信小程序 | 跨平台(微信、支付宝等) |
| provider | 不需要 | 必须指定(如 wxpay) |
| 参数结构 | 逐个传入字段 | 包装在 orderInfo 中 |
| orderInfo | 无此参数 | 传入后端返回的完整对象 |
支付参数签名串对比
支付调起签名与 API 签名的区别:
| 对比项 | API 请求签名 | 支付调起签名 |
|---|---|---|
| 签名串格式 | 方法\nURL\n时间戳\n随机串\n请求体\n | appId\n时间戳\n随机串\nprepay_id\n |
| 签名算法 | RSA-SHA256 | RSA-SHA256 |
| 使用场景 | 后端请求微信 API | 小程序调起支付 |
| 结果存放 | Authorization 头 | paySign 字段 |
完整支付流程验证
测试步骤
- 在 Bruno/Postman 中发送下单请求:
POST /api/v1/pay/order
{
"description": "测试商品-1分钱",
"amount": {
"total": 1
},
"payer": {
"openid": "user_openid_here"
}
}
json
- 后端返回支付参数:
{
"appId": "wx1234567890",
"timeStamp": "1684401015",
"nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
"package": "prepay_id=wx201410272009395542657a690389285100",
"signType": "RSA",
"paySign": "base64EncodedSignature=="
}
json
- 小程序使用返回参数调起支付,用户完成支付后微信弹出支付成功界面
测试金额
测试阶段建议使用 1 分钱(amount.total = 1),避免产生较大金额的实际扣款。
参考资源
- wx.requestPayment - 小程序支付 API
- uni.requestPayment - uni-app 支付 API
- 微信支付调起支付 - 签名规则
↑