PAYATHON 2026

支付宝app支付参数被转义为&后无法拉起支付页面

支付阿杰

结论

是的,如果传给支付宝 App SDK 的支付参数中确实含有字面量 &,参数就无法正确拆分,支付页面也可能因此无法拉起。

支付宝 App 支付的订单字符串使用 & 分隔参数,例如:

app_id=xxx&method=alipay.trade.app.pay&sign=xxx

& 是 & 的 HTML 实体编码,只适合在 HTML 中展示,不能作为支付参数的分隔符。SDK 收到 &app_id=... 后,可能会把参数名解析为 amp;app_id,导致 app_id、method、sign_type 等必要参数缺失或异常。

但要先确认,& 是调试页面产生的显示效果,还是支付前字符串中的真实内容。浏览器或调试工具有时会为了安全展示,自动把 & 显示为 &,这种情况不会影响支付。

如何确认问题

不要只看网页源码或经过 HTML 渲染的日志。应在调用支付宝 SDK 前,直接检查原始字符串。

例如,在客户端调用支付接口前输出:

console.log(orderInfo);
console.log(orderInfo.includes('&'));

如果第二行输出 true,传入 SDK 的就是错误字符串。

正常内容应该是:

alipay_sdk=alipay-sdk-PHP-4.11.14.ALL&app_id=2021003120698011&biz_content=...

而不是:

alipay_sdk=alipay-sdk-PHP-4.11.14.ALL&app_id=2021003120698011&biz_content=...

解决步骤

1. 检查服务端返回的数据

订单字符串应通过 JSON 字段或纯文本接口直接返回,不要经过 HTML 模板输出。

建议返回 JSON:

<?php

$orderString = $alipayClient->sdkExecute($request);

header('Content-Type: application/json; charset=utf-8');

echo json_encode([
    'code' => 0,
    'orderInfo' => $orderString,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

JSON 编码可能会将部分字符写成 Unicode 转义形式,但客户端完成 JSON 解析后,拿到的应该仍是包含真实 & 的字符串。

不要对支付参数调用以下函数:

htmlspecialchars($orderString);
htmlentities($orderString);

这些函数用于在 HTML 页面中安全显示内容,会把 & 转换为 &amp;。

2. 检查模板引擎或统一响应层

订单字符串经过 Blade、Twig、Vue 模板、富文本组件或统一 XSS 过滤器时,可能会被自动执行 HTML 转义。

错误示例:

$orderString = htmlspecialchars($orderString, ENT_QUOTES, 'UTF-8');
return ['orderInfo' => $orderString];

支付接口应返回原始订单字符串:

return [
    'orderInfo' => $orderString,
];

如果项目启用了全局 XSS 过滤,应排除支付下单接口或 orderInfo 字段。支付参数不是 HTML 内容,不该使用 HTML 实体编码处理。

3. 客户端先解析 JSON,再调用支付 SDK

不要把完整的 HTTP 响应文本直接交给支付宝 SDK。先按接口格式解析响应,再取出订单字段。

const response = await fetch('/api/alipay/order');
const result = await response.json();

const orderInfo = result.orderInfo;

if (orderInfo.includes('&amp;')) {
    throw new Error('支付宝订单参数被 HTML 实体转义');
}

// 将 orderInfo 原样传给对应平台的支付宝支付接口
pay(orderInfo);

具体调用方法取决于 Android、iOS、Flutter、React Native、uni-app 等客户端环境,但处理原则相同:将服务端生成的完整订单字符串原样传入,不要重新排序、拼接或编码。

4. 已被转义时的临时处理

如果暂时无法修改服务端,可以在调用 SDK 前还原一次 HTML 实体。

JavaScript 示例:

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

PHP 示例:

$orderString = html_entity_decode(
    $escapedOrderString,
    ENT_QUOTES | ENT_HTML5,
    'UTF-8'
);

这只适合作为临时兼容措施。更稳妥的办法是找出发生 HTML 转义的位置,并从源头停止转义。

不要使用没有明确范围的替换,例如:

orderInfo.replace(/amp;/g, '');

这种写法可能误改参数值或签名内容。

签名相关注意事项

支付宝订单字符串通常由服务端生成并签名。生成完成后,客户端应尽量原样传给 SDK。

如果服务端生成并签名的字符串原本正确,响应层却把分隔符从 & 改成了 &amp;,订单结构就会被破坏。把分隔符恢复为 &,通常可以还原原始订单字符串。但如果参数值、编码形式、顺序或 sign 本身也发生了变化,就应该重新生成订单和签名。

不要在客户端执行以下操作:

  • 对整个订单字符串再次调用 encodeURIComponent
  • 将字符串拆分后重新排序
  • 对 sign 再次进行 URL 编码或解码
  • 随意将 + 替换为空格
  • 提前解码 %2F、%3D 等内容
  • 修改 biz_content 的 JSON 表示形式

示例中,biz_content 的中文分别表现为 %5Cu8ba2... 和 %E8%AE%A2...。仅凭这两种表示方式的差异,不能直接判断订单无效。需要确认的是,它们是否由服务端按照支付宝接口要求生成,以及最终传给 SDK 的内容是否与签名时使用的内容一致。

为什么代码没改也可能突然出现

即使业务代码没有改动,数据也可能在其他环节发生变化,例如:

  • 接口从 JSON 响应切换成了模板页面响应
  • 公共响应封装新增了 htmlspecialchars
  • XSS 中间件或安全组件开始过滤响应
  • 前端把订单字符串写入 DOM 后,又从 HTML 中读取
  • 网关、低代码平台或日志组件对内容进行了 HTML 编码
  • 客户端读取了 innerHTML,而不是接口返回的原始字段

可以沿数据链路逐段对比:

支付宝 SDK 生成结果
→ 服务端业务层返回值
→ HTTP 原始响应
→ 客户端 JSON 解析结果
→ 调用支付 SDK 前的最终字符串

&amp; 第一次出现在哪一步,问题就出在哪一层。

还需要检查的事项

修复 &amp; 后,如果支付页面仍然无法拉起,还要检查:

  • app_id 是否正确
  • method 是否为 alipay.trade.app.pay
  • product_code 是否为 QUICK_MSECURITY_PAY
  • 订单号是否重复
  • 签名与应用公私钥配置是否匹配
  • 客户端支付宝 SDK 是否正常初始化
  • 手机上是否安装了可用的支付宝 App
  • SDK 回调是否返回了具体错误码
  • 服务端时间、订单金额和请求参数是否符合要求

就给出的两段数据来看,& 被实际转换为 &amp; 是最明显的结构错误,应优先处理。原则很简单:HTML 页面展示时可以转义,传给支付宝 SDK 的订单字符串中不能保留 HTML 实体编码。

备注:内容仅供参考。