PAYATHON 2026

沙箱调用 alipay.fund.auth.order.app.freeze 提示订单不存在

支付老李

结论

这个提示通常不是说支付宝里已有一笔订单却查不到,而是支付宝客户端无法根据 order_str 创建或识别有效的资金授权订单。

从现有请求看,建议优先检查这些问题:

  1. amount 很可能按“分”传入,但该接口通常以“元”为单位。金额应使用字符串,例如 "699.00",而不是数值 69900。
  2. 参数名写成了 timeoutExpress,实际请求参数应核对为 timeout_express。
  3. payee_user_id 必须是当前沙箱应用对应的沙箱收款方 UID,不能使用生产环境 UID,也不能误填沙箱买家 UID。
  4. post_payments 和 enable_pay_channels 的类型及结构必须符合当前接口文档,不能混用对象、数组和 JSON 字符串。
  5. 沙箱应用、密钥、账号和客户端必须属于同一套沙箱环境。
  6. 已生成的 order_str 不应长期保存或重复使用。示例中的时间是 2024-11-09,如果现在还在使用这段字符串,应重新生成后再调试。

可以先删除非必需参数,只保留最小请求完成一次冻结,然后逐项恢复其他参数。

请求中需要重点检查的参数

1. amount 的单位和类型

当前请求包含:

"amount": 69900

如果业务金额是 699 元,建议先改为:

"amount": "699.00"

资金类接口的金额字段通常以“元”为单位,并使用符合金额格式的字符串。直接传入 69900,可能会被解释为 69900 元,而不是 69900 分。

post_payments 中的金额也要采用相同的单位和格式,并与主金额及业务描述保持一致。

2. timeoutExpress 参数名有误

当前 biz_content 使用的是:

"timeoutExpress": "2d"

支付宝开放平台接口通常采用下划线命名:

"timeout_express": "2d"

如果业务模型或 SDK 没有自动转换字段名,timeoutExpress 可能会被视为未知字段或直接忽略。不要根据 Java 属性名猜测最终的 JSON 字段名,应检查实际发送的 biz_content。

排查时也可以先删除这个参数,避免无关字段干扰测试。

3. 核实 payee_user_id

当前值是:

"payee_user_id": "2088721038784732"

需要确认这个 UID:

  • 属于沙箱环境中的收款方;
  • 与 app_id=2021004195629043 对应的沙箱应用及商家账号关系正确;
  • 不是生产环境支付宝 UID;
  • 不是用于付款测试的沙箱买家 UID;
  • 没有与登录账号、PID 或应用 ID 混淆。

如果是商家自研应用,并且接口允许根据应用签约主体确定收款方,可以在最小化测试时暂时不传 payee_user_id。如果当前业务模式要求明确指定收款方,就必须填写沙箱控制台显示的正确 UID。该参数能否省略,应以应用当前的签约模式和接口文档为准。

4. 检查 post_payments 的结构

当前请求把 post_payments 传成了一个对象:

"post_payments": {
  "name": "押金",
  "amount": 69900,
  "description": "699元/小时,99元封顶"
}

需要根据当前接口文档确认它要求的是:

  • JSON 对象;
  • JSON 数组;
  • 序列化后的 JSON 字符串;
  • 仅在特定产品模式下允许传递的结构。

如果文档要求数组,写法应类似:

"post_payments": [
  {
    "name": "押金",
    "amount": "699.00",
    "description": "699元/小时,99元封顶"
  }
]

不能只根据字段名判断最终结构。建议先移除 deposit_product_mode 和 post_payments,确认基础预授权冻结能够成功,再按照当前签约产品的文档恢复这些字段。

5. 检查 enable_pay_channels 是否被重复序列化

URL 解码后,当前值类似:

"enable_pay_channels": "[{\"payChannelType\":\"PCREDIT_PAY\"},{\"payChannelType\":\"MONEY_FUND\"}]"

这说明它在外层 JSON 中是字符串,而不是数组。

这种写法不一定有问题,因为部分支付宝接口的复杂扩展参数确实要求传入 JSON 字符串。但如果当前接口要求数组或对象,这里就发生了重复序列化。内容看起来像 JSON,并不代表参数类型正确,仍需以接口文档为准。

排查时可以暂时删除该字段,让支付宝使用默认渠道。基础请求成功后,再单独添加一个渠道测试。

建议的排查顺序

第一步:确认整套环境一致

后端网关应使用沙箱地址:

https://openapi.alipaydev.com/gateway.do

同时确认:

  • app_id 是沙箱应用 ID;
  • 商户私钥属于该沙箱应用;
  • 支付宝公钥与沙箱应用当前配置一致;
  • 买家账号来自沙箱账号页面;
  • 使用支持该沙箱环境的支付宝沙箱客户端;
  • 没有在正式版支付宝客户端中打开沙箱订单;
  • payee_user_id 来自同一沙箱环境。

任何一项混入生产环境配置,都可能导致客户端无法识别订单。

第二步:使用最小参数重新生成订单

先只保留接口需要的基础字段:

{
  "out_order_no": "AUTH202609090001",
  "out_request_no": "AUTHREQ202609090001",
  "order_title": "押金预授权",
  "amount": "699.00",
  "product_code": "PREAUTH_PAY"
}

如果当前签约模式要求明确指定收款方,再加入已经确认的沙箱 payee_user_id。

暂时不要添加这些参数:

post_payments
deposit_product_mode
enable_pay_channels
timeout_express

最小请求成功后,每次只增加一个可选参数。这样更容易判断问题来自基础配置还是某个扩展字段。

第三步:每次使用新的业务单号

out_order_no 和 out_request_no 必须满足接口的唯一性要求。一次调试失败后,不要持续复用完全相同的单号。修改金额、产品模式或收款方后,也不要继续使用旧单号。

例如:

String outOrderNo = "AUTH" + System.currentTimeMillis();
String outRequestNo = "AUTHREQ" + System.currentTimeMillis();

生产环境不建议只用时间戳生成唯一号,但在单机沙箱排查中,这种方式比较直观。

第四步:检查服务端生成订单串时的响应

不要只看支付宝客户端显示的“订单不存在或状态异常”。服务端生成 order_str 时,还要记录并检查:

  • SDK 是否抛出异常;
  • 接口响应中的 code;
  • sub_code;
  • sub_msg;
  • request_id;
  • 返回体是否确实是一段完整且非空的订单串。

如果服务端接口已经返回参数错误,却仍将错误响应当作订单串交给客户端,客户端通常只能显示较为笼统的失败提示。

Java 最小化示例

下面使用支付宝 Java SDK 的通用调用方式。密钥只能保存在服务端,不能写入 App。

import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayFundAuthOrderAppFreezeRequest;
import com.alipay.api.response.AlipayFundAuthOrderAppFreezeResponse;

public class AlipayAuthFreezeDemo {

    public static void main(String[] args) throws Exception {
        String appId = "2021004195629043";
        String merchantPrivateKey = "YOUR_SANDBOX_APP_PRIVATE_KEY";
        String alipayPublicKey = "YOUR_SANDBOX_ALIPAY_PUBLIC_KEY";

        AlipayClient alipayClient = new DefaultAlipayClient(
                "https://openapi.alipaydev.com/gateway.do",
                appId,
                merchantPrivateKey,
                "json",
                "UTF-8",
                alipayPublicKey,
                "RSA2"
        );

        String outOrderNo = "AUTH" + System.currentTimeMillis();
        String outRequestNo = "AUTHREQ" + System.currentTimeMillis();

        String bizContent = "{"
                + "\"out_order_no\":\"" + outOrderNo + "\","
                + "\"out_request_no\":\"" + outRequestNo + "\","
                + "\"order_title\":\"押金预授权\","
                + "\"amount\":\"699.00\","
                + "\"product_code\":\"PREAUTH_PAY\""
                + "}";

        AlipayFundAuthOrderAppFreezeRequest request =
                new AlipayFundAuthOrderAppFreezeRequest();
        request.setBizContent(bizContent);

        AlipayFundAuthOrderAppFreezeResponse response =
                alipayClient.sdkExecute(request);

        if (response.isSuccess()) {
            String orderStr = response.getBody();
            System.out.println("orderStr=" + orderStr);
        } else {
            System.err.println("code=" + response.getCode());
            System.err.println("subCode=" + response.getSubCode());
            System.err.println("subMsg=" + response.getSubMsg());
            System.err.println("body=" + response.getBody());
        }
    }
}

如果确实需要指定收款方,可以在确认沙箱 UID 后加入:

String bizContent = "{"
        + "\"out_order_no\":\"" + outOrderNo + "\","
        + "\"out_request_no\":\"" + outRequestNo + "\","
        + "\"order_title\":\"押金预授权\","
        + "\"amount\":\"699.00\","
        + "\"product_code\":\"PREAUTH_PAY\","
        + "\"payee_user_id\":\"CONFIRMED_SANDBOX_PAYEE_UID\""
        + "}";

实际项目中建议使用 JSON 序列化库构造 biz_content,避免手工拼接引发引号、转义或数据类型错误。

客户端传递 order_str 时的注意事项

服务端返回订单串后,应将完整字符串原样交给支付宝 SDK:

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

具体方法名要与当前支付宝客户端 SDK 的资金授权接入方式一致。不要在客户端对订单串做这些处理:

  • 再次进行 URL 编码;
  • 先做 URL 解码,再重新拼接;
  • 将 + 替换为空格;
  • 截断 sign;
  • 修改参数顺序或参数值;
  • 缓存旧订单串并重复调用;
  • 通过 JSON、表单或 URL 传输时忽略特殊字符的处理。

sign 中可能包含 +、/、= 等字符,错误的表单解码很容易破坏签名。客户端收到的字符串应与服务端生成的内容逐字一致。

推荐的最终处理方案

先统一使用沙箱网关、沙箱密钥、沙箱买家和沙箱客户端,并以最小参数重新生成订单。将金额改为 "699.00",每次测试都生成新的 out_order_no 和 out_request_no。确认基础预授权能够正常拉起后,再依次加入并验证 payee_user_id、timeout_express、deposit_product_mode、post_payments 和 enable_pay_channels。

如果最小请求仍然失败,应重点查看服务端返回的 sub_code、sub_msg 和 request_id。客户端显示的“订单不存在或状态异常”只是概括性提示,单凭这句话无法判断问题究竟来自金额格式、收款方 UID、签约权限、密钥配置还是环境混用。

备注:内容仅供参考。