PAYATHON 2026

沙箱环境调用 alipay.trade.page.pay 提示订单不存在

支付阿杰

结论

PAYORDER_NOT_EXIST 表示支付宝收银台找不到当前支付链接对应的收银台订单,或者订单已经失效。这不等于商户系统中不存在对应的 out_trade_no。

如果代码和配置没有改动,但所有沙箱订单从某个时间点开始同时报错,可以优先怀疑沙箱环境临时异常。不过,在确认是环境问题之前,仍要逐一排除支付表单重复使用、订单超时、网关配置混用、请求参数被页面转义等常见问题。

常见原因

1. 重复使用已经生成的支付表单

alipay.trade.page.pay 返回的 HTML form 应用于一次新的支付跳转。不要长期缓存生成结果,也不要在支付失败后反复打开同一个收银台地址。

收银台页面中的支付令牌可能会过期。刷新旧页面、从浏览器历史记录重新进入,或者由前端重复使用缓存的 form,都可能触发 PAYORDER_NOT_EXIST。

2. 订单已经超过有效期

如果请求中设置了 timeout_express,超过指定时间后,原收银台订单便无法继续支付。例如:

{
  "out_trade_no": "ORDER_20260909_10001",
  "product_code": "FAST_INSTANT_TRADE_PAY",
  "total_amount": "99.00",
  "subject": "测试订单",
  "timeout_express": "15m"
}

订单超过 15 分钟后,应重新发起支付,不能继续打开原来的 form。

3. 沙箱与正式环境配置混用

以下配置必须来自同一个环境:

  • 支付宝网关地址
  • 沙箱应用的 app_id
  • 应用私钥
  • 支付宝公钥或支付宝公钥证书
  • 沙箱买家账号
  • 异步通知和同步跳转参数

例如,使用沙箱 app_id 时,不能把请求发送到正式环境网关。网关地址应以支付宝开放平台当前的沙箱文档和控制台信息为准,不要直接照搬旧文章中的配置。

4. form 被前端框架修改

SDK 返回的内容通常是一个需要通过 POST 自动提交的 form。以下操作可能造成参数丢失,或改变签名所对应的内容:

  • 使用字符串替换修改 form 内容;
  • 对隐藏字段再次执行 URL 编码;
  • 只提取 form 的 action,再改成普通链接跳转;
  • 通过模板引擎输出时被 HTML 转义;
  • 前端框架过滤 <form>、<input> 或 <script>;
  • 页面重复提交旧的 form。

建议让后端直接输出 SDK 返回的完整内容,并将响应编码设为 UTF-8。

解决步骤

第一步:每次支付都生成新订单号

不要继续使用报错时保存的 form。生成唯一的 out_trade_no,然后重新调用 alipay.trade.page.pay。

String outTradeNo = "ORDER_" + System.currentTimeMillis();

即使支付宝允许重新处理部分未支付订单,排查时也建议使用全新的商户订单号,避免旧订单状态和缓存干扰判断。

第二步:核对沙箱配置

初始化客户端时,确认网关、app_id、密钥和签名类型都来自同一个沙箱应用:

AlipayClient alipayClient = new DefaultAlipayClient(
    sandboxGateway,
    appId,
    merchantPrivateKey,
    "json",
    "UTF-8",
    alipayPublicKey,
    "RSA2"
);

sandboxGateway 应填写支付宝开放平台当前提供的沙箱网关。沙箱地址和配置方式可能调整,请以控制台和官方文档中的最新值为准。

第三步:重新调用接口并原样输出 form

Java SDK 示例:

AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();

request.setReturnUrl("https://example.com/alipay/return");
request.setNotifyUrl("https://example.com/alipay/notify");

AlipayTradePagePayModel model = new AlipayTradePagePayModel();
model.setOutTradeNo("ORDER_" + System.currentTimeMillis());
model.setProductCode("FAST_INSTANT_TRADE_PAY");
model.setTotalAmount("0.01");
model.setSubject("沙箱支付测试");
model.setTimeoutExpress("15m");

request.setBizModel(model);

AlipayTradePagePayResponse response = alipayClient.pageExecute(request);

if (!response.isSuccess()) {
    throw new IllegalStateException(
        "调用失败,code=" + response.getCode()
            + ", subCode=" + response.getSubCode()
            + ", msg=" + response.getMsg()
            + ", subMsg=" + response.getSubMsg()
    );
}

String form = response.getBody();

控制器应直接将 response.getBody() 作为 HTML 返回,不要再次编码,也不要拆解其中的内容:

@GetMapping(value = "/pay", produces = "text/html;charset=UTF-8")
@ResponseBody
public String pay() throws AlipayApiException {
    return createAlipayPagePayForm();
}

第四步:检查实际提交内容

打开浏览器开发者工具,在 Network 面板中检查:

  • form 是否通过 POST 提交;
  • 提交地址是否为预期的沙箱网关;
  • app_id 是否属于沙箱应用;
  • method 是否为 alipay.trade.page.pay;
  • sign_type 是否与客户端配置一致;
  • biz_content 是否完整;
  • 页面是否提交了之前缓存的参数。

不要修改 sign、biz_content 或其他已经参与签名的字段。

第五步:判断是否为沙箱环境异常

可以用最小参数创建一笔新的 0.01 元订单。如果以下条件都满足,问题仍能稳定复现,那么沙箱环境异常的可能性较高:

  • 同一套代码此前可以正常支付;
  • 正式环境没有出现相同问题;
  • 已使用全新的 out_trade_no;
  • form 没有被修改;
  • 沙箱网关、应用和密钥完全匹配;
  • 多次重新生成支付请求后,仍立即报 PAYORDER_NOT_EXIST。

这时需要保存请求时间、app_id、out_trade_no、接口返回的 code、sub_code、msg、sub_msg,以及支付宝返回的请求追踪信息,再通过支付宝开放平台的官方支持渠道反馈。沙箱环境确实可能发生临时故障,但没有官方公告或工单确认时,不应直接断定是平台事故。

注意事项

  • out_trade_no 应在商户应用范围内保持唯一。
  • 不要根据同步跳转结果判断支付成功,应以异步通知或主动查询结果为准。
  • 支付失败后,应由后端重新生成请求,不能只让前端刷新旧的收银台页面。
  • notify_url 必须能被支付宝服务器访问,本机地址和内网地址通常无法接收通知。
  • 排查日志时,不要公开应用私钥、完整签名、用户身份信息或其他敏感数据。
  • 如果只有少数旧订单报错,先检查订单是否超时、form 是否被重复使用。如果所有新订单从同一时间开始报错,再重点检查配置变更和沙箱服务状态。

备注:内容仅供参考。