沙箱调用 alipay.fund.auth.order.app.freeze 提示订单不存在
结论
这个提示通常不是说支付宝里已有一笔订单却查不到,而是支付宝客户端无法根据 order_str 创建或识别有效的资金授权订单。
从现有请求看,建议优先检查这些问题:
amount很可能按“分”传入,但该接口通常以“元”为单位。金额应使用字符串,例如"699.00",而不是数值69900。- 参数名写成了
timeoutExpress,实际请求参数应核对为timeout_express。 payee_user_id必须是当前沙箱应用对应的沙箱收款方 UID,不能使用生产环境 UID,也不能误填沙箱买家 UID。post_payments和enable_pay_channels的类型及结构必须符合当前接口文档,不能混用对象、数组和 JSON 字符串。- 沙箱应用、密钥、账号和客户端必须属于同一套沙箱环境。
- 已生成的
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、签约权限、密钥配置还是环境混用。
备注:内容仅供参考。
