PAYATHON 2026

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)

它们都可能改变已经签名的内容。

临时验证方法

如果暂时无法修改后端,可以在前端调用支付前把 &amp; 替换成 &,先验证问题是否由 HTML 转义引起:

const orderInfo = response.data.orderInfo.replace(/&amp;/g, '&')

uni.requestPayment({
  provider: 'alipay',
  orderInfo,
  success(res) {
    console.log(res)
  },
  fail(err) {
    console.error(err)
  }
})

如果替换后可以正常拉起支付,基本能确定问题出在传输过程中的 HTML 转义。

这种处理更适合临时排查,正式修复仍应放在后端。如果字符串中出现了 &amp;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 中的用户输入,不应该处理支付宝签名结果。可以用抓包工具直接查看接口的原始响应,确认 &amp; 是后端返回时就已经存在,还是前端处理后才出现。

注意事项

不要把完整的支付宝私钥、完整签名或可支付的订单字符串写入公开日志。示例中的 app_id 如果属于真实应用,在公开场景中也建议脱敏。

排查时,应以服务端最终生成的字节内容和实际传给 uni.requestPayment 的字符串为准,不要以页面渲染后的文本为准。签名后的订单字符串只要在传输过程中发生变化,即使肉眼看起来差别不大,也可能导致支付失败。

备注:内容仅供参考。