支付宝 APP 支付参数异常,URL 编码及缺失参数如何排查?
明确结论
传给支付宝客户端 SDK 的 orderInfo 通常是已完成签名和 URL 编码的订单字符串。字符串中出现 %7B、%22、%2B、%3D 等内容是正常的。
需要注意以下几点:
- URL 编码的对象是各个参数的值。参数之间的
&和键值之间的=必须保留。 - 签名应使用编码前的参数值。生成签名后,再对包括
sign在内的各个参数值进行 URL 编码。 - 不要对完整的订单字符串再次编码,否则
%会变成%25,造成双重编码。 alipay.trade.app.pay的biz_content通常需要包含固定值:
"product_code": "QUICK_MSECURITY_PAY"
题目中的 biz_content 缺少这个字段,应先补上。建议由服务端调用支付宝官方 SDK 的 sdkExecute() 生成完整订单字符串,APP 不再自行拼接、编码或签名。
URL 编码的正确形式
最终订单字符串大致如下:
app_id=...&biz_content=%7B...%7D&charset=utf-8&format=json&method=alipay.trade.app.pay¬ify_url=https%3A%2F%2Fexample.com%2Falipay%2Fnotify&sign=...&sign_type=RSA2×tamp=2021-12-09%2017%3A17%3A03&version=1.0
其中:
biz_content中的 JSON 经过 URL 编码是正常的。sign中的+、/、=分别编码为%2B、%2F、%3D,这是必要的。&和=是订单参数的结构分隔符,不能随参数值一起编码。- APP 收到服务端返回的字符串后,应原样传给支付宝 SDK。
Android 端可以直接这样调用:
PayTask alipay = new PayTask(activity);
Map<String, String> result = alipay.payV2(orderInfo, true);
调用 payV2() 前不要再执行:
URLEncoder.encode(orderInfo, "UTF-8");
否则,原来的 %7B 会变成 %257B,支付宝解析时可能提示订单参数异常。
当前参数需要重点检查的问题
1. 补充 product_code
建议将 biz_content 调整为:
{
"out_trade_no": "0622021120902000124",
"total_amount": "8.00",
"subject": "法式复古chic气质小众侧边不规则花边显瘦娃娃衫上衣短袖女",
"product_code": "QUICK_MSECURITY_PAY"
}
APP 支付的 product_code 通常固定为:
QUICK_MSECURITY_PAY
如果使用支付宝官方服务端 SDK,应通过业务模型或请求对象设置这个字段,不要手工拼接。
2. 检查是否发生二次编码或提前解码
常见的二次编码如下:
正确:biz_content=%7B%22out_trade_no%22...
错误:biz_content=%257B%2522out_trade_no%2522...
第二种写法中,% 又被编码成了 %25。
还要检查 APP 或中间接口是否提前执行了 URL 解码。签名中的 %2B 解码后会变成 +,后续如果再按照表单规则解析,+ 可能被当成空格,最终导致验签失败。
建议分别记录以下三个位置的完整字符串,然后逐字比较:
- 服务端生成后的
orderInfo。 - APP 接收到的
orderInfo。 - 调用支付宝 SDK 前的
orderInfo。
这三个字符串应完全一致。
3. 确认签名顺序
正确流程如下:
- 准备所有待签名参数。
- 排除
sign参数和空值参数。 - 按参数名的 ASCII 顺序排序。
- 使用编码前的参数值拼接待签名字符串。
- 使用应用私钥和
SHA256withRSA生成 RSA2 签名。 - 将签名写入
sign。 - 分别对最终参数值进行 UTF-8 URL 编码。
- 使用
&连接各项参数,再交给 APP。
待签名字符串类似:
app_id=应用ID&biz_content={"out_trade_no":"...","product_code":"QUICK_MSECURITY_PAY","subject":"...","total_amount":"8.00"}&charset=utf-8&format=json&method=alipay.trade.app.pay¬ify_url=https://example.com/alipay/notify&sign_type=RSA2×tamp=2021-12-09 17:17:03&version=1.0
除非所使用的 SDK 明确要求,否则不要用已经包含 %7B、%22 的字符串计算签名。支付宝官方 SDK 通常会处理签名和最终编码,手工实现时最容易在这个环节出错。
4. 检查 notify_url
示例中的地址:
http://please-to-set-default-host.com/v1/notifies/alipay
看起来是占位地址。如果真实请求中使用的也是这个地址,需要将其替换为自己的异步通知地址,例如:
https://pay.example.com/v1/notifies/alipay
该地址应满足以下条件:
- 可以从公网访问。
- 不依赖登录状态。
- 能接收支付宝发送的 POST 通知。
- 不使用内网地址、
localhost或临时占位域名。 - 收到通知后先验签,再更新订单状态。
notify_url 并非创建订单时所有场景下都绝对必填,但地址错误或无法访问会导致支付结果通知无法送达。它是否会直接触发当前提示,还要结合支付宝返回的具体错误码判断。
5. 检查时间和参数格式
timestamp 应使用以下格式:
yyyy-MM-dd HH:mm:ss
例如:
2021-12-09 17:17:03
最终传输时编码为:
2021-12-09%2017%3A17%3A03
同时还要确认:
- 时间是当前请求时间,不是已经过期的固定值。
- 服务器时钟准确。
charset为utf-8。sign_type为RSA2。version为1.0。total_amount是合法的金额字符串,如"8.00"。out_trade_no在商户系统内唯一。app_id、应用私钥和公钥配置属于同一个支付宝应用。
题目说明部分参数经过修改,因此无法只根据展示的 app_id、订单号和签名判断这些参数实际是否合法。
推荐的服务端生成方式
以支付宝 Java SDK 为例,服务端可以直接生成 APP 需要的订单字符串:
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
APP_ID,
APP_PRIVATE_KEY,
"json",
"UTF-8",
ALIPAY_PUBLIC_KEY,
"RSA2"
);
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo("0622021120902000124");
model.setTotalAmount("8.00");
model.setSubject("法式复古chic气质小众侧边不规则花边显瘦娃娃衫上衣短袖女");
model.setProductCode("QUICK_MSECURITY_PAY");
request.setBizModel(model);
request.setNotifyUrl("https://pay.example.com/v1/notifies/alipay");
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
if (!response.isSuccess()) {
throw new IllegalStateException(
"生成支付宝订单失败:" + response.getCode() + " " + response.getSubMsg()
);
}
String orderInfo = response.getBody();
response.getBody() 就是应当返回给 APP 的内容。APP 收到后直接交给支付宝客户端 SDK,不要自行补充参数、重新排序、重新签名或再次执行 URL 编码。
建议的排查顺序
- 在
biz_content中增加product_code=QUICK_MSECURITY_PAY。 - 使用官方服务端 SDK 的
sdkExecute()生成订单字符串。 - 确认 APP 没有对完整的
orderInfo再次执行 URL 编码或解码。 - 检查服务端私钥、支付宝公钥和
app_id是否对应同一个应用及环境。 - 确认使用 RSA2,也就是
SHA256withRSA。 - 将占位的
notify_url替换为真实且能从公网访问的地址。 - 检查时间、金额、订单号和 JSON 格式。
- 获取支付宝 SDK 返回结果中的
resultStatus、memo和result,以及服务端接口返回的code、sub_code、sub_msg,再根据具体错误码继续排查。
仅凭客户端显示的“商家订单参数异常”,无法确定唯一的故障点。缺少 product_code、签名原文不一致、订单字符串被二次编码,以及 app_id 与密钥不匹配,都是常见原因。
备注:内容仅供参考。
