uniapp打包后支付宝支付报错62009是否与&转义有关
结论
如果前端收到的订单字符串里确实含有字面量 &,支付宝支付失败很可能就是它引起的。
支付宝 App 支付使用的 orderStr 是签名后的参数字符串,各参数必须用真正的 & 分隔。& 只是 HTML 实体,并不是支付宝协议认可的参数分隔符。签名完成后,如果 & 被替换成 &,原始订单字符串就发生了变化,支付宝可能无法正确解析参数,或者验签失败。
但要先确认字符串是否真的被转义。有些调试面板会把 & 显示成 &,实际值并没有变化。因此,不能只看页面上的显示结果,要检查调用支付接口前的字符串本身。
如何确认是否真的发生了转义
在调用 uni.requestPayment 前检查:
console.log('orderInfo:', orderInfo)
console.log('包含 &:', orderInfo.includes('&'))
console.log('包含真实 &:',
orderInfo.includes('&')
)
console.log('字符串长度:', orderInfo.length)
console.log('JSON形式:', JSON.stringify(orderInfo))
如果这个表达式返回 true,说明字符串中确实含有 HTML 实体:
orderInfo.includes('&')
正常的订单字符串应该类似:
alipay_sdk=alipay-sdk-php-20200415&app_id=2021003124676910&biz_content=...&charset=utf-8&method=alipay.trade.app.pay&sign_type=RSA2&sign=...
而不应该是:
alipay_sdk=alipay-sdk-php-20200415&app_id=2021003124676910&biz_content=...
推荐解决方法
最好从后端去掉 HTML 转义,让支付宝 SDK 生成的订单字符串保持原样,并通过 JSON 返回。不要对它调用 htmlspecialchars()、htmlentities(),也不要让它经过用于 HTML 模板的转义流程。
PHP 可以直接返回 JSON:
<?php
$orderString = $aop->sdkExecute($request);
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'code' => 0,
'orderInfo' => $orderString,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
不要这样处理:
$orderString = htmlspecialchars($orderString);
也不要手动拼接 JSON:
echo '{"orderInfo":"' . $orderString . '"}';
手动拼接可能带来转义、引号或编码问题,这部分应交给 json_encode() 处理。
前端收到结果后,直接把订单字符串传给支付宝支付接口:
uni.requestPayment({
provider: 'alipay',
orderInfo: response.data.orderInfo,
success(res) {
console.log('支付调用成功', res)
},
fail(err) {
console.error('支付调用失败', err)
}
})
不要再对订单字符串执行这些操作:
encodeURIComponent(orderInfo)
decodeURIComponent(orderInfo)
JSON.stringify(orderInfo)
它们都可能改变已经签名的内容。
临时验证方法
如果暂时无法修改后端,可以在前端调用支付前把 & 替换成 &,先验证问题是否由 HTML 转义引起:
const orderInfo = response.data.orderInfo.replace(/&/g, '&')
uni.requestPayment({
provider: 'alipay',
orderInfo,
success(res) {
console.log(res)
},
fail(err) {
console.error(err)
}
})
如果替换后可以正常拉起支付,基本能确定问题出在传输过程中的 HTML 转义。
这种处理更适合临时排查,正式修复仍应放在后端。如果字符串中出现了 &amp;,说明它可能经过了多次转义,只替换一次未必能恢复原始内容。
仍然报错时需要排查的项目
在 uni-app 的支付调用中,62009 可能只是上层返回的笼统错误,不能仅凭这个编号认定问题一定是 HTML 转义。如果传入的已经是原始订单字符串,还要继续检查以下项目。
1. 订单字符串是否被二次编码
重点检查:
- 是否对整个
orderInfo再次调用了encodeURIComponent()。 - 是否先把订单字符串放入表单,又经过了 URL 编码。
- 是否经过富文本、模板引擎或 HTML 属性传递。
- 是否将字符串序列化了两次。
- 是否出现换行、截断或首尾空格变化。
biz_content 和 sign 的编码由服务端 SDK 处理,前端通常只需原样传递整个订单字符串。
2. 签名后是否修改了任何参数
完成签名后,下面这些变化都可能导致验签失败:
- 修改
biz_content。 - 改变金额格式。
- 替换空格、
+、%2B或%20。 - 重新排列参数。
- 删除空参数。
- 改变字符编码。
- 对
sign再次进行 URL 编码或解码。
比较可靠的做法是分别记录服务端最终生成的字符串,以及前端调用支付接口前的字符串,然后对比两者的长度和哈希值。
服务端:
error_log('orderInfo length=' . strlen($orderString));
error_log('orderInfo sha256=' . hash('sha256', $orderString));
前端也可以计算哈希后进行对比。如果不方便,至少要比较完整字符串和字符串长度。日志可能包含订单号、签名等敏感数据,应限制访问权限,并在排查结束后及时清理。
3. RSA2 配置是否一致
需要确认:
- 服务端用于签名的是支付宝应用私钥,而不是支付宝公钥。
sign_type为RSA2。- 支付宝开放平台配置的应用公钥与当前私钥匹配。
- 正式环境和沙箱环境没有混用。
app_id与密钥所属应用一致。- PHP 运行环境能够正确读取完整私钥,私钥没有丢失换行或头尾内容。
调试工具可以完成支付,只能证明当时生成的那份订单数据有效,并不能证明线上请求所用的应用、密钥和网关配置完全相同。
4. 打包后的支付宝支付模块是否正确配置
如果只有打包后的 App 出现问题,还要检查:
- manifest 中是否启用了支付宝支付模块。
- 修改支付配置后,当前安装包是否重新打包。
- 当前使用的是自定义基座,还是正式云打包的包体。
- Android 和 iOS 是否都会失败,还是只有一个平台失败。
- 包名、签名证书及线上发布配置是否符合当前支付接入要求。
如果支付宝客户端都无法正常拉起,应优先检查原生支付模块和打包配置。如果客户端能够拉起,但提示订单错误,则优先检查订单字符串、签名和应用配置。
5. 接口响应是否经过了额外处理
线上环境可能比本地多经过一些处理环节,例如:
- CDN 或网关。
- PHP 模板输出。
- Web Application Firewall。
- 统一响应封装。
- 前端网络库的响应转换器。
- 服务端框架的 XSS 过滤器。
XSS 过滤通常用于展示到 HTML 中的用户输入,不应该处理支付宝签名结果。可以用抓包工具直接查看接口的原始响应,确认 & 是后端返回时就已经存在,还是前端处理后才出现。
注意事项
不要把完整的支付宝私钥、完整签名或可支付的订单字符串写入公开日志。示例中的 app_id 如果属于真实应用,在公开场景中也建议脱敏。
排查时,应以服务端最终生成的字节内容和实际传给 uni.requestPayment 的字符串为准,不要以页面渲染后的文本为准。签名后的订单字符串只要在传输过程中发生变化,即使肉眼看起来差别不大,也可能导致支付失败。
备注:内容仅供参考。
