alipay.trade.app.pay周期扣款支付后签约页面跳转异常
结论
alipay.trade.app.pay 是 App 支付接口。在“周期扣款-支付后签约”场景中,接口返回的数据需要交给支付宝客户端 SDK,作为支付参数使用。不能将返回内容当作普通 H5 表单嵌入网页并提交。
示例中出现了 <form>,说明服务端很可能调用了网页执行方法,例如 Java SDK 的 pageExecute()。这里应该改用 sdkExecute() 获取订单字符串,再由 Android 或 iOS 的支付宝 SDK 发起支付。
跳转到 /none 是调用方式与产品场景不匹配造成的异常路由结果。拼接或替换这个地址无法解决问题。
问题原因
请求配置明确指定了支付宝 App 渠道:
"access_params": {
"channel": "ALIPAYAPP"
}
对应的正确流程是:
H5/业务页面
→ 调用商户服务端创建订单
→ 服务端调用 alipay.trade.app.pay
→ 返回 orderString
→ App 通过支付宝客户端 SDK 支付
→ 支付完成后继续处理签约
当前实际执行的却是:
服务端生成 form
→ H5 嵌入 form
→ 浏览器提交到 gateway.do
→ 支付宝尝试按 App 能力进行路由
→ 无法找到正确的浏览器跳转目标
→ 跳转到 /none
问题有两层。
第一,alipay.trade.app.pay 的返回结果被当成了网页支付表单。App 支付返回的是客户端 SDK 所需的订单参数,不是浏览器支付页面。
第二,返回内容中包含:
<form name="punchout_form" method="post" action="https://openapi.alipay.com/gateway.do?...">
这通常说明服务端使用了 pageExecute() 一类用于生成 HTML 表单的方法。调用 alipay.trade.app.pay 时,应通过 SDK 执行方法获取订单字符串。
修复表单中的空格、引号转义或自动提交逻辑,也无法将 App 支付流程可靠地转换为 H5 支付流程。
正确处理方式
1. 服务端使用 sdkExecute()
以 Java SDK 为例:
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo("P202010221927422441");
model.setTotalAmount("8.00");
model.setSubject("apitest85732");
model.setBody("5f743d14ccc4ccdacf56828c");
model.setProductCode("CYCLE_PAY_AUTH");
// 按当前所使用支付宝 SDK 的模型定义设置签约参数。
// agreement_sign_params 的具体 Java 类型和 setter
// 可能因 SDK 版本不同而不同。
model.setAgreementSignParams(agreementSignParams);
request.setBizModel(model);
// 异步通知地址属于公共请求参数,应通过 request 设置。
request.setNotifyUrl(
"https://dev-open-ishare.iask.com.cn/openapi/payment/notify/ali"
);
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
if (!response.isSuccess()) {
throw new IllegalStateException(
"创建支付宝 App 支付订单失败:" +
response.getSubCode() + " - " + response.getSubMsg()
);
}
// 该字符串应原样返回给 App,不要包装成 form,也不要再次 URL 编码。
String orderString = response.getBody();
两种调用方式的区别是:
// 错误:生成网页 form
alipayClient.pageExecute(request);
// 正确:生成交给 App 支付 SDK 的订单字符串
alipayClient.sdkExecute(request);
服务端可以用自己的 JSON 格式将结果返回给 App:
{
"success": true,
"orderString": "app_id=...&biz_content=...&charset=utf-8&method=alipay.trade.app.pay&sign=..."
}
必须完整保留 orderString。不要解析后重新排列参数,也不要对整个字符串重复编码。
2. App 调用支付宝客户端 SDK
Android 侧示意:
PayTask payTask = new PayTask(activity);
Map<String, String> result = payTask.payV2(orderString, true);
String resultStatus = result.get("resultStatus");
String resultData = result.get("result");
String memo = result.get("memo");
实际项目中,不能只根据客户端返回结果更新订单状态。客户端结果可以用于页面提示,最终的支付和签约状态应通过服务端通知或主动查询确认。
如果业务页面运行在 App 内的 WebView 中,需要通过 JSBridge 将支付请求交给原生层:
window.NativeBridge.alipay(orderString);
原生层收到 orderString 后,再调用支付宝 SDK。不能在 WebView 中创建并自动提交 <form>,以此代替原生支付调用。
3. 分别处理支付结果和签约结果
“支付后签约”包含支付和协议签约两个结果,不能只检查页面是否跳回。
这两个结果应分别处理:
- 支付异步通知通过
request.setNotifyUrl(...)设置。 - 签约结果通知通过
agreement_sign_params.sign_notify_url设置。 - 客户端支付结果只用于交互提示,不能作为最终记账依据。
- 如果没有收到通知或状态不明确,应通过对应的支付、协议查询能力核实状态。
处理通知时还需要完成验签,并根据商户订单号、支付宝交易号、协议号等字段做好幂等控制。
如果业务只能运行在普通 H5 页面
如果业务没有 Android 或 iOS 原生容器,也无法通过 JSBridge 调用支付宝客户端 SDK,就不能继续将 alipay.trade.app.pay 的返回值提交到浏览器。
这时需要根据实际产品权限和签约方案,选择支持手机网站或浏览器拉起的支付、签约能力。具体可以使用哪些接口,以及如何组合“支付后签约”流程,取决于商户已经开通的支付宝产品。
不能简单地把:
"channel": "ALIPAYAPP"
改成其他值,也不能将 App 支付订单字符串转换成网页表单。
参数设置注意事项
示例把 notifyUrl、returnUrl 放进了业务参数 JSON。按照支付宝开放平台 SDK 的一般调用方式,这类公共参数通常应通过请求对象设置:
request.setNotifyUrl("https://example.com/alipay/notify");
request.setReturnUrl("https://example.com/pay/result");
不要自行写成:
{
"notifyUrl": "...",
"returnUrl": "..."
}
当前动态 SDK 是否会对这些字段进行特殊处理,需要结合实际 SDK 版本确认。为了避免字段被忽略,应优先使用 SDK 提供的标准 setter,并检查最终参与签名的参数。
还需要检查以下问题:
- 通知地址尽量使用公网可访问的 HTTPS 地址。
- URL 前后不能有空格。示例返回内容中的
action和部分通知地址疑似带有前导空格。 - 如果确实需要将
biz_content放入 HTML 属性,必须进行 HTML 属性转义。不过,本场景不应生成 HTML 表单。 - 不要依赖
return_url判断 App 支付是否成功。 - 不要修改 SDK 生成的签名、参数顺序或编码结果。
- 不要继续根据
/none地址拼接跳转。它不是业务回跳地址。
解决这个问题需要调整支付调用方式:服务端通过 sdkExecute() 生成订单字符串,再由 App 客户端 SDK 发起支付,而不是继续修复 H5 自动提交代码。
备注:内容仅供参考。
