PAYATHON 2026

支付宝沙箱 alipay.trade.app.pay 提示商家订单参数异常

支付老李

明确结论

从题目列出的 biz_content 来看,各字段没有明显违反 alipay.trade.app.pay 的要求:

  • product_code 使用 QUICK_MSECURITY_PAY,配置正确。
  • total_amount 为 "0.01",格式正常。
  • out_trade_no、subject、body 和 timeout_express 也没有明显问题。

问题更可能出在业务参数以外的环节:

  1. 请求没有正确生成或携带 sign。
  2. 使用证书模式时缺少 app_cert_sn 或 alipay_root_cert_sn。
  3. 应用私钥与应用公钥证书不属于同一密钥对。
  4. 服务端返回的 App 支付订单串被解析、重新拼接或重复 URL 编码。
  5. App 端没有将完整订单串原样传给支付宝客户端 SDK。
  6. 沙箱应用没有正确开通或配置 App 支付能力。
  7. 如果仍在使用示例中的 2023-09-12 16:53:35,该时间戳已经失效,必须换成当前时间。

预创建接口能够成功,只能说明网关地址、app_id 和部分签名配置基本可用,不能证明 App 支付订单串的生成和客户端调用流程没有问题。

首先检查完整请求参数

最终提交给支付宝的请求不能只有题目列出的字段,至少还要包含 sign。如果采用证书模式,请求中还会带有证书序列号参数。

典型的证书模式请求参数如下:

app_id
method
format
charset
sign_type
timestamp
version
biz_content
app_cert_sn
alipay_root_cert_sn
sign

各参数的作用如下:

  • sign:使用应用私钥对待签名字符串进行 RSA2 签名。
  • app_cert_sn:应用公钥证书序列号。
  • alipay_root_cert_sn:支付宝根证书序列号。
  • 支付宝公钥证书用于验签,并不是应用请求签名时使用的私钥。

如果日志只是手工打印了签名前的参数,没有看到 sign 不一定代表实际请求缺少签名。需要查看 SDK 最终生成的完整订单串才能确认。

正确理解四个证书或密钥文件

证书模式通常需要以下内容:

应用私钥
应用公钥证书
支付宝公钥证书
支付宝根证书

应用私钥本身不是证书。它必须与生成应用公钥证书时使用的公钥属于同一密钥对。

常见的配置错误有:

  • 下载了新证书,但程序仍在使用旧的应用私钥。
  • 重新生成密钥后只替换了私钥,没有重新上传公钥或下载证书。
  • 混用了”支付宝公钥”字符串模式和”支付宝公钥证书”模式。
  • 混用了沙箱证书和正式环境证书。
  • 配置文件路径指向另一套应用的证书。
  • 私钥头尾、空格或换行处理有误。

使用证书模式时,建议由支付宝官方 SDK 计算证书序列号,不要自行拼接 app_cert_sn 和 alipay_root_cert_sn。

使用 SDK 生成 App 支付订单串

以 Java SDK 的常见写法为例。使用证书模式时,应先完成证书配置,再调用 sdkExecute:

CertAlipayRequest certRequest = new CertAlipayRequest();
certRequest.setServerUrl("https://openapi-sandbox.dl.alipaydev.com/gateway.do");
certRequest.setAppId("9021000127609612");
certRequest.setPrivateKey(appPrivateKey);
certRequest.setFormat("json");
certRequest.setCharset("UTF-8");
certRequest.setSignType("RSA2");

certRequest.setCertPath("/path/to/appCertPublicKey.crt");
certRequest.setAlipayPublicCertPath("/path/to/alipayCertPublicKey_RSA2.crt");
certRequest.setRootCertPath("/path/to/alipayRootCert.crt");

AlipayClient alipayClient = new DefaultAlipayClient(certRequest);

AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();

AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo("AC20123444");
model.setProductCode("QUICK_MSECURITY_PAY");
model.setSubject("测试01");
model.setTotalAmount("0.01");
model.setBody("描述1233455");
model.setTimeoutExpress("30m");

request.setBizModel(model);

// 如需异步通知,可设置公网可访问的 HTTPS 地址
// request.setNotifyUrl("https://example.com/alipay/notify");

AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);

if (response.isSuccess()) {
    String orderInfo = response.getBody();
    System.out.println(orderInfo);
} else {
    System.out.println(response.getCode());
    System.out.println(response.getSubCode());
    System.out.println(response.getMsg());
    System.out.println(response.getSubMsg());
}

不同版本的支付宝 Java SDK 在类构造方式上可能略有差异,应以项目当前使用的 SDK 接口为准。需要确认的是:

  • 使用沙箱网关。
  • 使用当前沙箱应用对应的私钥和证书。
  • 通过证书模式初始化客户端。
  • 使用 sdkExecute() 生成客户端支付订单串。

不要把 alipay.trade.app.pay 当作普通服务端接口,通过 execute() 获取 JSON 后再自行拼接支付参数。

App 端必须原样使用订单串

服务端生成的 response.getBody() 通常是一整段已签名、已编码的查询字符串,例如:

app_id=...&biz_content=...&charset=UTF-8&method=alipay.trade.app.pay&sign=...&sign_type=RSA2&timestamp=...&version=1.0

客户端需要将这段字符串完整传给支付宝 SDK。

Android 示例:

PayTask payTask = new PayTask(activity);
Map<String, String> result = payTask.payV2(orderInfo, true);

不要对订单串做以下处理:

不要把订单串解析成 Map 后重新排序
不要自行修改 biz_content
不要删除 sign 中的字符
不要对整个订单串再次 URLEncoder.encode
不要先 URLDecoder.decode 再传给支付宝 SDK
不要把字符串中的 + 自动替换为空格
不要只把 biz_content 传给 payV2

sign 通常已经经过 URL 编码。再次编码会把 % 变成 %25,最终造成签名校验失败。此时客户端往往只会显示”商家订单参数异常”之类的概括性提示。

如果订单串通过 JSON 返回给 App,需要确保传输过程中只发生了正常的 JSON 字符串转义。App 解析 JSON 后得到的内容,应与服务端的 response.getBody() 完全一致。

推荐的排查顺序

1. 检查环境是否一致

确认以下内容全部来自同一个沙箱应用:

app_id
应用私钥
应用公钥证书
支付宝公钥证书
支付宝根证书
沙箱网关地址

沙箱网关通常应配置为:

https://openapi-sandbox.dl.alipaydev.com/gateway.do

不要混用正式环境的网关或证书。

2. 检查时间戳

示例中的时间戳:

2023-09-12 16:53:35

只能作为历史日志,不能继续写死在请求中。实际请求必须动态生成当前时间:

String timestamp =
        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());

还要检查服务器的时区和系统时间是否正确。

3. 检查最终订单串

不要只记录 biz_content,还应检查 SDK 生成的完整订单串,确认其中包含:

method=alipay.trade.app.pay
product_code=QUICK_MSECURITY_PAY
sign_type=RSA2
sign=...

在证书模式下,还要确认 SDK 已正确处理:

app_cert_sn
alipay_root_cert_sn

私钥和完整签名属于敏感信息,不应写入生产日志或对外提供。

4. 对比服务端与 App 收到的内容

分别记录以下信息:

  • 服务端 sdkExecute() 返回内容的长度和摘要。
  • App 收到的订单串长度和摘要。
  • 调用 payV2() 前的订单串长度和摘要。

可以通过 SHA-256 摘要进行比对,避免打印完整支付参数:

String digest = DigestUtils.sha256Hex(orderInfo);

如果三处摘要不同,说明订单串在传输或处理过程中发生了变化。

5. 查看客户端 SDK 的完整返回结果

不要只展示弹窗文案,还应记录 payV2() 返回的完整 Map,重点检查:

resultStatus
memo
result

例如:

Map<String, String> result = payTask.payV2(orderInfo, true);

Log.d("Alipay", "resultStatus=" + result.get("resultStatus"));
Log.d("Alipay", "memo=" + result.get("memo"));
Log.d("Alipay", "result=" + result.get("result"));

这些信息通常比”商家订单参数异常”更有助于定位问题。记录日志时要做好脱敏,避免泄露完整签名和交易信息。

6. 检查沙箱账号和应用能力

App 支付需要使用支付宝沙箱提供的买家账号和对应的沙箱客户端环境。同时还要在沙箱应用后台确认 App 支付能力已经开通,签约状态正常。

如果预创建成功,但 App 支付始终失败,应重点比较两个产品的能力开通状态。预创建接口可用,并不代表 App 支付能力也一定可用。

参数本身的建议

当前 biz_content 可以整理为:

{
  "out_trade_no": "AC20123444",
  "product_code": "QUICK_MSECURITY_PAY",
  "subject": "测试01",
  "total_amount": "0.01",
  "body": "描述1233455",
  "timeout_express": "30m"
}

这些字段没有明显问题,但还需要保证:

  • out_trade_no 在当前商户应用范围内唯一。
  • 金额使用字符串形式,单位为元。
  • subject 不为空。
  • JSON 使用 UTF-8 编码。
  • JSON 由序列化库生成,不要手工处理引号和转义字符。
  • 每次重新测试时最好使用新的 out_trade_no,以排除旧订单状态的干扰。

最可能的修正方案

建议按以下顺序调整:

  1. 使用官方 SDK 的证书模式客户端。
  2. 确认应用私钥与应用公钥证书匹配。
  3. 使用当前时间,不要写死 timestamp。
  4. 通过 sdkExecute() 生成 App 支付订单串。
  5. 将 response.getBody() 原样返回给 App。
  6. App 端直接把完整订单串传给 payV2(),不要重复编码或重新拼接。
  7. 检查沙箱应用的 App 支付能力和沙箱买家环境。

如果完成这些检查后仍然失败,需要结合客户端 payV2() 的完整返回值、SDK 生成的订单串参数名,以及支付宝开放平台侧的请求日志继续定位。仅凭当前弹窗还无法确定具体是哪个字段出错,但从现有业务参数来看,应优先检查签名、证书序列号和订单串的传递过程。

备注:内容仅供参考。