支付宝沙箱 APP 支付提示商家订单参数异常
结论
这组参数里的 biz_content 格式基本正确。金额、订单号、product_code、字符集等字段没有明显的语法错误。仅凭最终请求串无法判断签名是否正确,但“商家订单参数异常”通常与以下问题有关:
- 服务端生成
orderString后,客户端又对其进行了 URL 编码、解码或重新拼接。 - 混用了沙箱与正式环境的
app_id、应用私钥、支付宝公钥或客户端。 - 签名完成后又修改了参数,例如
timestamp、notify_url或biz_content。 - 请求已经过期,或者设备时间偏差较大。
- 正式应用尚未开通 APP 支付能力,或者应用配置与签约主体不匹配。
当前配置的回调地址是:
notify_url=http://127.0.0.1:8810/uni_alipay_result_notify.html
支付宝服务器无法访问这个地址。它通常不会直接导致付款页面提示“商家订单参数异常”,但异步通知一定无法送达,需要改成公网可访问的回调地址。
参数检查
biz_content 解码后的内容如下:
{
"out_trade_no": "TEST1745470984414",
"product_code": "QUICK_MSECURITY_PAY",
"subject": "紫拍拍商城-沙箱测试",
"timeout_express": "30m",
"total_amount": "0.01"
}
这些字段符合 APP 支付的常见用法:
product_code使用QUICK_MSECURITY_PAY,没有问题。total_amount是字符串"0.01",可以接受。timeout_express是"30m",格式正常。subject的 UTF-8 URL 编码正常。out_trade_no中没有明显的非法字符。
timestamp=2025-04-24+13%3A03%3A04 里的 + 代表空格,解码后为:
2025-04-24 13:03:04
这种编码本身没有问题。不过,订单串生成后应立即使用,不能保存下来长期重复调用。排查时需要重新生成订单号、时间戳和签名。
优先检查是否发生二次编码
Java SDK 生成的订单串通常已经包含 URL 编码,例如:
biz_content=%7B%22out_trade_no%22...
sign=BPnUVaWp...%2B...
timestamp=2025-04-24+13%3A03%3A04
客户端必须把完整订单串原样传给支付宝 SDK,不能再次调用:
URLEncoder.encode(orderString, "UTF-8")
也不能先执行 URLDecoder.decode(),再自行拼接参数。
发生二次编码后,原来的 %7B 会变成 %257B,%2B 会变成 %252B。这样一来,支付宝实际收到的参数就与签名内容不一致。
Android 端可以按下面的方式调用:
String orderInfo = responseFromYourServer;
// orderInfo 必须是服务端返回的完整订单串,不能再次 URL 编码。
PayTask alipay = new PayTask(activity);
Map<String, String> result = alipay.payV2(orderInfo, true);
如果使用 uni-app、Flutter、React Native 或其他封装,还要检查框架是否自动执行了 encodeURIComponent、表单编码或 JSON 转义。
服务端正确生成订单串
建议直接使用支付宝 Java SDK 生成签名和订单串,不要手动排序、拼接或编码:
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi-sandbox.dl.alipaydev.com/gateway.do",
sandboxAppId,
merchantPrivateKey,
"json",
"UTF-8",
sandboxAlipayPublicKey,
"RSA2"
);
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo("TEST" + System.currentTimeMillis());
model.setProductCode("QUICK_MSECURITY_PAY");
model.setSubject("紫拍拍商城-沙箱测试");
model.setTimeoutExpress("30m");
model.setTotalAmount("0.01");
request.setBizModel(model);
request.setNotifyUrl("https://example.com/alipay/notify");
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
if (!response.isSuccess()) {
throw new IllegalStateException(
"生成支付宝订单失败:" + response.getCode() + " " + response.getSubMsg()
);
}
String orderString = response.getBody();
具体是否使用 sdkExecute,要以项目实际采用的支付宝 Java SDK 版本为准。重点是让 SDK 生成完整订单串,并将 response.getBody() 原样交给客户端。
核对沙箱和正式环境配置
沙箱环境中的以下配置必须配套使用:
沙箱 app_id
商户应用私钥
与该私钥对应、已配置到沙箱应用的应用公钥
沙箱提供的支付宝公钥
沙箱网关
沙箱版支付宝客户端及沙箱买家账号
沙箱网关通常配置为:
https://openapi-sandbox.dl.alipaydev.com/gateway.do
正式环境使用:
https://openapi.alipay.com/gateway.do
还要注意区分“支付宝公钥”和“应用公钥”:
- 应用私钥:商户服务端用它生成签名。
- 应用公钥:需要配置到支付宝开放平台,供支付宝验签。
- 支付宝公钥:商户服务端用它验证支付宝返回的内容。
如果重新生成了密钥,但开放平台中仍然保存着旧的应用公钥,也会导致签名校验失败。
建议的排查顺序
- 重新生成全新的
out_trade_no、timestamp和签名。 - 打印服务端 SDK 返回的原始
orderString。 - 打印客户端调用支付宝 SDK 前的最终字符串。
- 逐字符对比这两个字符串,确认它们完全一致。
- 搜索代码中是否使用了
URLEncoder.encode、URLDecoder.decode、encodeURIComponent,或者存在手动拼接参数的逻辑。 - 确认沙箱的
app_id、应用私钥、应用公钥和支付宝公钥属于同一个沙箱应用。 - 确认没有使用正式支付宝客户端支付沙箱订单,也没有使用沙箱客户端支付正式订单。
- 将
notify_url改成支付宝服务器可以访问的公网地址。 - 查看支付宝 SDK 返回的
resultStatus、memo和完整结果,不要只根据付款页面的提示判断问题。
注意事项
签名应按照 SDK 规则,基于编码前的业务参数生成,再由 SDK 完成编码。不要把已经经过 URL 编码的整段字符串再次用于签名,也不要在签名完成后修改任何参数。
仅凭当前请求串无法确认 sign 是否正确,因为验签还需要对应的公钥和签名原文。如果沙箱与正式环境都出现相同提示,先检查两边共用的客户端传参代码,特别是是否发生了二次 URL 编码。然后再检查密钥、公钥和应用环境是否配套。
备注:内容仅供参考。
