H5跳转到APP提示订单参数异常,如何排查?
结论
从链接参数来看,首先要排查的是支付接口、产品码和跳转场景不匹配:
- 请求调用的是
alipay.trade.page.pay,用于网页支付。 biz_content.product_code却设置为GENERAL_WITHHOLDING,并包含agreement_sign_params和integration_type=ALIAPP,这些参数属于代扣签约场景。- 外层还使用
alipays://platformapi/startapp强制拉起支付宝 App。
同一个请求混入了多种产品场景,支付宝收银台可能无法正确解析订单,进而提示“订单参数异常,请重新下单后再发起付款”。
另外,示例中的 timestamp=2022-07-20 09:26:44 已经过期,out_trade_no=1658280404789 也可能已经使用。现在直接复用这条链接,即使其他参数没有问题,也无法完成有效测试。
原因分析
1. method 与 product_code 不匹配
解码链接后,可以看到以下参数:
method=alipay.trade.page.pay
product_code=GENERAL_WITHHOLDING
integration_type=ALIAPP
alipay.trade.page.pay 应按照网页支付产品的接口文档传参,不能混入其他产品的 product_code。如果实际业务是签约代扣或签约并支付,需要改用相应产品的接口和参数结构。
修改 product_code 后不能继续使用原签名。请求参数只要发生变化,就必须重新生成签名。
2. H5、App 和 PC 网页支付场景混用
当前调用链大致如下:
H5 页面
→ mdeduct-landing
→ alipays://platformapi/startapp
→ openapi.alipay.com/gateway.do
→ alipay.trade.page.pay
这条链路混合了 H5 落地页、支付宝 App Scheme 和网页支付接口。alipay.trade.page.pay 的 SDK 执行结果通常是供浏览器提交的表单,不能把网关地址再次放进 Scheme,作为通用的 App 支付链接使用。
如果需要从手机浏览器唤起支付宝,应接入手机网站支付能力。如果是原生 App 支付,应由服务端调用 App 支付接口,再把订单串交给客户端 SDK。代扣签约则要使用对应产品规定的跳转地址或接口返回值。
3. URL 多层编码可能损坏参数
该链接至少嵌套了三层内容:
落地页 URL
└── scheme 参数
└── startapp 的 url 参数
└── 支付宝网关请求参数
每嵌套一层,只应对当前层的参数值进行一次 URL 编码。反复对完整链接执行 URLEncoder.encode(),可能造成以下问题:
%变成%25+被解析为空格- 签名中的
/、+、=被改变 biz_content解码后不再是合法 JSON- 验签参数与签名时使用的参数不一致
外层链接可以打开,并不代表内层订单参数仍然完整。
4. 链接不能重复使用
示例请求中使用了固定值:
timestamp=2022-07-20 09:26:44
out_trade_no=1658280404789
支付请求应实时生成。排查时需要使用:
- 按当前时间生成的
timestamp - 全新的
out_trade_no - 根据当前全部参数生成的新签名
- 尚未失效、关闭或支付的业务订单
不要重复使用浏览器历史记录、日志中的旧链接或以前生成的二维码。
排查步骤
第一步:确认实际业务场景
先确定实际使用的是哪一种场景:
- PC 网站支付;
- 手机网站 H5 支付;
- 原生 App 支付;
- 代扣签约;
- 签约并支付。
这些场景不能混用。确定后,应以对应的支付宝接口文档为准,检查 method、product_code、必填参数和调用方式。
如果业务需要使用 GENERAL_WITHHOLDING,还要确认当前应用是否已经开通相应产品,以及 personal_product_code、sign_scene 是否为签约配置中实际分配或约定的值。这些值不能根据示例推断,应以商户后台配置和对应产品文档为准。
第二步:绕过外层 Scheme,单独验证接口请求
暂时不要拼接 mdeduct-landing 和 alipays://。先通过官方 SDK 生成请求,再按照接口规定的方式提交。
如果直接调用接口仍然提示订单参数异常,问题通常出在业务参数、产品权限或接口选择上。如果直接调用正常,加入 Scheme 后才出现异常,则应检查跳转方式和多层 URL 编码。
第三步:逐层解码并对比参数
每次只解码一层,确认最终网关参数仍然完整,包括:
app_id
method
biz_content
charset
sign_type
timestamp
version
sign
notify_url
return_url
重点检查:
sign 中的 + / =
biz_content 中的中文、双引号和大括号
sign_scene 中的 |
notify_url 和 return_url
不要使用从页面复制后已经经过 HTML 转义的字符串参与签名。
第四步:记录签名前的原始参数
服务端应记录以下内容,但不能把私钥写入日志:
接口名称
app_id
out_trade_no
timestamp
未经二次加工的 biz_content
签名前参数字符串
SDK返回的完整结果
支付宝返回的错误码、sub_code 和 sub_msg
客户端显示的“订单参数异常”只是一条统一提示。真正有助于定位问题的,通常是服务端接口响应以及支付宝网关返回的错误信息。
第五步:检查签名流程
先准备业务参数,再交给官方 SDK 编码和签名。签名完成后,不要修改以下内容:
biz_contentnotify_urlreturn_urltimestampapp_idmethod- 其他参与签名的公共参数
如果外层还需要进行 Scheme 编码,应把已经生成的完整请求作为外层 url 参数值进行编码,不要重新拆分或修改内部参数。
Java 示例
下面的代码仅演示如何通过官方 SDK 生成网页支付请求。实际使用的接口和 product_code 必须根据业务场景调整。
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl,
appId,
merchantPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setNotifyUrl(notifyUrl);
request.setReturnUrl(returnUrl);
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", generateNewOrderNo());
bizContent.put("total_amount", "0.01");
bizContent.put("subject", "3S甄选权益包");
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
request.setBizContent(bizContent.toJSONString());
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
if (!response.isSuccess()) {
System.err.println("code=" + response.getCode());
System.err.println("msg=" + response.getMsg());
System.err.println("subCode=" + response.getSubCode());
System.err.println("subMsg=" + response.getSubMsg());
throw new IllegalStateException("支付宝下单失败");
}
String form = response.getBody();
如果实际业务是 App 支付或代扣签约,不能直接使用这段代码,需要替换为相应产品的 Request 类、接口名称和业务参数。
多层 URL 编码示例
需要把完整地址嵌入另一层参数时,应明确区分“内部请求”和“外部容器”:
String gatewayRequestUrl = generatedGatewayUrl;
String scheme =
"alipays://platformapi/startapp"
+ "?appId=20000067"
+ "&url="
+ URLEncoder.encode(gatewayRequestUrl, StandardCharsets.UTF_8);
String landingUrl =
"https://render.alipay.com/p/c/mdeduct-landing"
+ "?scheme="
+ URLEncoder.encode(scheme, StandardCharsets.UTF_8);
这段代码只用于说明编码层级,并不表示 appId=20000067 或该落地页适用于当前产品。Scheme、容器 App ID 和落地页地址都必须来自对应产品的正式接入方案,不能参照其他案例自行拼接。
注意事项
- 不要手工拼接支付宝网关请求,优先使用官方 SDK。
- 不要把
pageExecute()返回的 HTML 表单当作 App 支付订单串。 - 生成签名后,不要修改或重新序列化
biz_content。 - 每笔新订单都要使用新的
out_trade_no和当前timestamp。 notify_url必须是支付宝服务器可以访问的地址。生产环境建议使用有效的 HTTPS 域名。- 应用、商户账号、签约产品和密钥必须属于同一套环境,不能混用沙箱和生产配置。
- 如果参数核对无误后仍然报错,应使用
app_id、out_trade_no、请求时间以及网关返回的sub_code/sub_msg继续查询,不能只根据 App 页面上的统一提示判断。
备注:内容仅供参考。
