小程序支付流程是怎样的?wx.requestPayment 前后端各负责什么?
小程序把订单号交给服务端,服务端调用微信支付的 JSAPI/小程序下单接口拿到 prepay_id,生成签名参数返回给小程序;小程序用这些参数调用 wx.requestPayment 拉起收银台。支付成功与否不能看前端回调,要以服务端收到的支付成功回调通知和查询订单接口结果为准。
简答
用户在小程序里点“立即支付”,小程序先把业务订单号发给自己的服务端。服务端调用微信支付的 JSAPI/小程序下单接口,拿到预支付交易会话标识
prepay_id,再按微信支付的签名规则算好签名,把timeStamp、nonceStr、package、signType、paySign返回给小程序。小程序用这几个参数调用wx.requestPayment,拉起微信收银台。金额、商品、签名都由服务端决定,是因为前端的东西用户都能改。小程序只负责拉起支付和展示结果,商户密钥和下单接口都不应该出现在前端。
用户付完或取消后回到小程序,
wx.requestPayment的回调会告诉你结果,比如requestPayment:fail cancel表示用户取消。前端拿到回调后,应该去问自己的服务端订单状态,再决定显示什么。最要注意的是,微信支付文档明确说前端回调不保证绝对可靠,订单状态要以服务端的支付成功回调通知和查询订单接口为准。服务端收到回调要先验签,回调可能重复发送,处理逻辑要能重入。
整个流程分几步
微信支付的开发指引把小程序支付分成下单、调起支付、用户支付三步,后面再加对账和退款。前后端的分工如下:
- 小程序请求自己的服务端创建支付。服务端按业务订单查出金额,调用
POST /v3/pay/transactions/jsapi下单,传入appid、mchid、商品描述、商户订单号out_trade_no、接收通知的notify_url、以分为单位的金额,以及支付者的openid。 - 下单成功返回
prepay_id。服务端用appId、timeStamp、nonceStr、package计算签名paySign,连同这些字段返回给小程序。 - 小程序调用
wx.requestPayment拉起收银台,用户完成支付或取消后回到小程序页面,收到回调。 - 用户支付成功后,微信支付向
notify_url发送支付成功回调通知;小程序收到前端回调后,服务端调用查询订单接口确认状态。
openid 来自登录流程,服务端用 wx.login 的 code 换到它,过程见《小程序登录流程是怎样的?wx.login、code2Session 和自定义登录态各做什么?》。
小程序这边要写什么
async function pay(orderNo) {
const params = await request({ url: '/api/pay/prepare', data: { orderNo } })
try {
await wx.requestPayment({
timeStamp: params.timeStamp,
nonceStr: params.nonceStr,
package: params.package,
signType: 'RSA',
paySign: params.paySign,
})
} catch (err) {
if (err.errMsg === 'requestPayment:fail cancel') {
// 用户取消,订单保持未支付,可以让用户稍后重新支付
}
}
// 不管回调说成功还是失败,都以服务端查到的状态为准
return request({ url: '/api/orders/status', data: { orderNo } })
}
wx.requestPayment 支持 Promise 风格调用,所以可以 await。package 的格式是 prepay_id=***。signType 在小程序 API 文档里默认是 MD5,但 MD5 和 HMAC-SHA256 只适用于 v2 接口,RSA 只适用于 v3 接口,而微信支付 v3 文档要求这里只支持 RSA,所以用 v3 下单时要显式传 RSA。timeStamp 是秒级时间戳,部分系统取到的是毫秒,要转成 10 位秒数。
微信支付文档还提醒两点。一是签名必须用实际调起支付的小程序 appid,并且和下单时传入的一致,微信支付会校验。二是下单如果由前端按钮触发,按钮要做防抖,防止用户重复点击造成重复支付。
为什么不能信前端回调
前端回调是用户回到小程序时触发的。用户支付成功后可能马上杀掉了小程序,网络也可能刚好断开,回调就收不到;反过来,前端代码运行在用户设备上,返回的结果也可以被伪造。所以微信支付文档写明,前端回调不保证绝对可靠,不能只依赖它判断订单支付状态。
服务端有两个可信的来源。第一个是支付成功回调通知:用户成功支付后,微信支付用 POST 向下单时的 notify_url 发送通知,业务数据经过 AES-256-GCM 加密,要用 APIv3 密钥解密。第二个是查询订单接口:订单未支付时只能用商户订单号查询,返回的 trade_state 为 SUCCESS 才表示支付成功,NOTPAY 是未支付,CLOSED 是已关闭。
服务端处理回调要注意什么
收到回调后先验签。微信支付用请求头里的 Wechatpay-Timestamp、Wechatpay-Nonce 和请求体构造验签串,用 Wechatpay-Serial 对应的平台证书或微信支付公钥验证 Wechatpay-Signature,确认通知确实来自微信支付。文档还说,微信支付偶尔会发带错误签名的探测请求,检查商户有没有认真验签。
验签要在 5 秒内完成并应答:通过返回 200 或 204,不通过返回 4XX 或 5XX。文档建议应答后再异步处理更新订单这类业务,避免处理太久导致应答超时。应答失败或超时,微信支付会按递增的间隔重发,最多 15 次。即使应答成功,也可能因为网络原因收到重复通知,所以更新订单要做成幂等的,比如只在订单还是未支付时才改成已支付。
文档也说,商户系统不能只依赖回调通知,要结合查询接口。没收到回调时,服务端主动查单补上状态。
常见追问
prepay_id 能一直用吗?
不能。
prepay_id有效期 2 小时,过期后要用原下单参数重新调用下单接口,换一个新的。订单本身如果没传支付结束时间time_expire,默认 7 天内可以支付,超过就无法再支付。
用户取消支付后,订单怎么处理?
取消后订单还是未支付状态,用户可以重新发起支付。不再需要支付的订单,比如超时或用户取消了业务订单,服务端可以先查单确认仍未支付,再调用关闭订单接口,关单后按失败终态处理。
云开发能省掉哪些步骤?
小程序文档说明,使用云开发时,
wx.requestPayment需要的参数可以通过云开发微信支付统一下单接口免鉴权获取,调用微信支付服务端接口也可以免证书、免签名,并能接收异步支付结果回调。支付结果以服务端为准的原则不变。