uniapp 支付宝小程序传入 orderStr 无法唤起芝麻免押
结论
问题通常不在 tradeNo 支付链路,而是调用方式不匹配。
alipay.fund.auth.order.app.freeze 返回的是资金预授权 orderStr,不是 tradeNo。在支付宝小程序中,不能把它当作普通交易号传给 uni.requestPayment。小程序端应直接调用 my.tradePay,通过 orderStr 字段传入服务端生成的完整字符串。
原因
普通小程序支付的流程通常是:
服务端创建交易 → 返回 tradeNo → my.tradePay({ tradeNO })
资金预授权的流程则是:
服务端调用 alipay.fund.auth.order.app.freeze
→ 返回已经签名的 orderStr
→ my.tradePay({ orderStr })
这两种流程最终都可能通过 my.tradePay 唤起支付宝收银台,但使用的参数不同,不能混用:
// 普通支付
my.tradePay({
tradeNO: tradeNo
})
// 资金预授权
my.tradePay({
orderStr: orderStr
})
uni.requestPayment 对支付宝小程序的封装主要用于普通支付。不同 uni-app 版本对资金预授权 orderStr 的透传支持可能不一致。传入后,orderStr 可能被当作普通支付的订单信息或交易号处理,导致授权页面无法正常拉起。
返回码 6001 通常表示客户端流程被取消或中断。它只能说明本次授权没有完成,不能据此认定服务端签名有误。
正确调用方式
服务端调用:
alipay.fund.auth.order.app.freeze
然后将支付宝 SDK 生成的完整 orderStr 原样返回给小程序。支付宝小程序前端直接调用原生 API:
// #ifdef MP-ALIPAY
my.tradePay({
orderStr: orderStr,
success: (res) => {
console.log('资金预授权结果:', res)
if (res.resultCode === '9000') {
console.log('预授权成功')
return
}
if (res.resultCode === '6001') {
console.log('用户取消或授权流程被中断')
return
}
console.warn('预授权未成功:', res.resultCode, res.memo)
},
fail: (err) => {
console.error('my.tradePay 调用失败:', err)
}
})
// #endif
如果项目还要兼容其他平台,可以单独封装:
export function invokeAlipayAuth(orderStr) {
return new Promise((resolve, reject) => {
// #ifdef MP-ALIPAY
my.tradePay({
orderStr,
success: resolve,
fail: reject
})
// #endif
// #ifndef MP-ALIPAY
reject(new Error('当前平台不支持支付宝资金预授权'))
// #endif
})
}
调用方式如下:
const { orderStr } = await createAuthOrder()
try {
const result = await invokeAlipayAuth(orderStr)
if (result.resultCode === '9000') {
// 不要只依赖客户端结果更新业务状态,
// 应继续请求服务端查询授权结果。
}
} catch (error) {
console.error('唤起芝麻免押失败:', error)
}
服务端需要检查的内容
1. 返回完整的 orderStr
服务端应返回支付宝 SDK 生成的原始字符串:
AlipayFundAuthOrderAppFreezeRequest request =
new AlipayFundAuthOrderAppFreezeRequest();
request.setBizContent(bizContent);
AlipayFundAuthOrderAppFreezeResponse response =
alipayClient.sdkExecute(request);
String orderStr = response.getBody();
具体写法以项目实际使用的支付宝 Java SDK 方法定义为准。重点是取得客户端唤起所需的完整签名参数,不要只返回其中某个业务字段,也不要自行拼接 URL。
2. 不要重复编码或解码
示例中的 biz_content、sign 等字段已经完成编码,前端收到后直接传递即可:
my.tradePay({
orderStr: response.data.orderStr
})
不要再次执行:
encodeURIComponent(orderStr)
decodeURIComponent(orderStr)
也不要先把字符串拆成对象再重新拼接,否则 %2B、%2F、%3D、空格和 + 等内容可能发生变化,导致验签失败。
通过 JSON 返回时,建议将其保留为普通字符串:
{
"orderStr": "alipay_root_cert_sn=...&app_id=...&sign=..."
}
3. 确认应用和产品配置一致
还要核对以下配置:
- 生成
orderStr的app_id是否属于当前支付宝小程序应用。 - 应用是否已开通对应的资金预授权、芝麻免押产品能力。
serviceId是否属于当前应用和当前环境。category是否与签约的业务类目一致。- 应用证书、支付宝根证书和支付宝公钥证书是否匹配。
- 是否混用了沙箱与正式环境的应用、网关、证书或业务配置。
普通 tradeNo 支付成功,只能证明普通支付链路可用,并不能说明资金预授权产品、serviceId 和签约关系也已正确配置。
结果处理注意事项
客户端返回 9000 后,不要直接认定业务授权已经最终成功。服务端还应查询资金授权订单状态,或者以支付宝异步通知为准,再更新免押订单状态。
如果改用 my.tradePay({ orderStr }) 后仍然返回 6001,应记录完整的客户端返回对象,并检查以下内容:
- 服务端返回的字符串是否与 SDK 生成结果完全一致。
- 前端传入的是否为
orderStr,而不是tradeNO或orderInfo。 - 是否对
orderStr做过二次编码、解码或字符串替换。 app_id、serviceId、类目和产品签约关系是否一致。- 当前支付宝客户端和小程序基础库是否支持所使用的资金预授权能力。
这个场景的处理重点是保留后端生成的原始 orderStr,并在支付宝小程序中绕过普通支付封装,直接调用 my.tradePay({ orderStr }) 唤起资金预授权流程。
备注:内容仅供参考。
