PAYATHON 2026

支付宝沙箱 APP 支付提示商家订单参数异常

支付小周

结论

这组参数里的 biz_content 格式基本正确。金额、订单号、product_code、字符集等字段没有明显的语法错误。仅凭最终请求串无法判断签名是否正确,但“商家订单参数异常”通常与以下问题有关:

  1. 服务端生成 orderString 后,客户端又对其进行了 URL 编码、解码或重新拼接。
  2. 混用了沙箱与正式环境的 app_id、应用私钥、支付宝公钥或客户端。
  3. 签名完成后又修改了参数,例如 timestamp、notify_url 或 biz_content。
  4. 请求已经过期,或者设备时间偏差较大。
  5. 正式应用尚未开通 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

还要注意区分“支付宝公钥”和“应用公钥”:

  • 应用私钥:商户服务端用它生成签名。
  • 应用公钥:需要配置到支付宝开放平台,供支付宝验签。
  • 支付宝公钥:商户服务端用它验证支付宝返回的内容。

如果重新生成了密钥,但开放平台中仍然保存着旧的应用公钥,也会导致签名校验失败。

建议的排查顺序

  1. 重新生成全新的 out_trade_no、timestamp 和签名。
  2. 打印服务端 SDK 返回的原始 orderString。
  3. 打印客户端调用支付宝 SDK 前的最终字符串。
  4. 逐字符对比这两个字符串,确认它们完全一致。
  5. 搜索代码中是否使用了 URLEncoder.encode、URLDecoder.decode、encodeURIComponent,或者存在手动拼接参数的逻辑。
  6. 确认沙箱的 app_id、应用私钥、应用公钥和支付宝公钥属于同一个沙箱应用。
  7. 确认没有使用正式支付宝客户端支付沙箱订单,也没有使用沙箱客户端支付正式订单。
  8. 将 notify_url 改成支付宝服务器可以访问的公网地址。
  9. 查看支付宝 SDK 返回的 resultStatus、memo 和完整结果,不要只根据付款页面的提示判断问题。

注意事项

签名应按照 SDK 规则,基于编码前的业务参数生成,再由 SDK 完成编码。不要把已经经过 URL 编码的整段字符串再次用于签名,也不要在签名完成后修改任何参数。

仅凭当前请求串无法确认 sign 是否正确,因为验签还需要对应的公钥和签名原文。如果沙箱与正式环境都出现相同提示,先检查两边共用的客户端传参代码,特别是是否发生了二次 URL 编码。然后再检查密钥、公钥和应用环境是否配套。

备注:内容仅供参考。