支付宝小程序预支付提示订单创建失败是什么原因?
结论
仅凭前端提示“订单创建失败,请联系商户核实订单信息”,无法判断具体原因。这是支付宝收银台返回的通用提示,还需要结合前端回调、支付宝服务端日志和商户平台配置排查。
从给出的 orderStr 来看,参数结构基本完整。建议优先检查以下几项:
- 小程序应用
2021006102667907是否绑定了已开通预授权支付的同一商户主体。 orderStr是否通过alipay.fund.auth.order.app.freeze对应的 SDK 方法生成,并由前端原样传入。- 签名使用的应用私钥,是否与该小程序 AppID 配置的支付宝公钥模式匹配。
- 商户的预授权产品权限是否已经生效,应用场景、类目和签约主体是否符合准入要求。
out_order_no、out_request_no是否重复使用,或者此前是否创建过失败、关闭或冻结中的授权订单。- 请求时间、服务器时钟、金额和业务描述是否触发了参数校验或风控。
“已开通预授权支付产品”不代表任意 AppID 都能直接调用。产品签约主体、应用 AppID、收款主体和签名配置必须属于同一套有效关系。
先获取前端完整回调
不要只显示统一的失败文案。调用 my.tradePay 时,需要记录 success、fail 和 complete 返回的完整对象:
my.tradePay({
orderStr: orderStr,
success: (res) => {
console.log('tradePay success:', JSON.stringify(res));
if (res.resultCode === '9000') {
// 授权成功
return;
}
console.error(
'预授权未成功',
res.resultCode,
res.memo,
res.result
);
},
fail: (err) => {
console.error('tradePay fail:', JSON.stringify(err));
},
complete: (res) => {
console.log('tradePay complete:', JSON.stringify(res));
}
});
以下字段需要重点保存:
resultCodememoresulterrorerrorMessage
不同客户端版本和调用场景返回的字段可能不同,所以要记录完整对象,不要只读取其中一个字段。
常见的 resultCode 有:
9000:处理成功8000:处理中,需要主动查询最终状态4000:处理失败6001:用户取消6002:网络异常
如果只能拿到 4000 和通用提示,还要继续检查服务端及支付宝侧的日志。
检查 AppID 与产品签约关系
当前请求使用的 AppID 是:
2021006102667907
请在支付宝开放平台确认:
- 该 AppID 对应当前运行的小程序。
- 小程序已经上线,或者当前账号处于允许测试的状态。
- 预授权支付产品绑定的是这个 AppID,而不是同一商户名下的其他网页或移动应用。
- 应用所属主体与预授权产品的签约主体一致。
- 如果采用代调用、服务商或第三方应用模式,授权令牌和商户身份参数是否传递正确。
- 当前业务类目和授权场景是否属于产品准入范围。
如果产品开通在另一个应用下,即使商户主体相同,也可能在拉起收银台后提示订单创建失败。
检查 orderStr 的生成方式
alipay.fund.auth.order.app.freeze 需要客户端拉起支付宝完成授权。PHP SDK 应使用适合生成客户端请求字符串的方法,例如 sdkExecute()。不要把服务端接口响应、网页跳转地址或自行拼接的参数当作 orderStr。
示例结构如下:
<?php
$aop = new AopClient();
$aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
$aop->appId = '2021006102667907';
$aop->rsaPrivateKey = $appPrivateKey;
$aop->alipayrsaPublicKey = $alipayPublicKey;
$aop->signType = 'RSA2';
$aop->format = 'json';
$aop->charset = 'UTF-8';
$request = new AlipayFundAuthOrderAppFreezeRequest();
$bizContent = [
'out_order_no' => 'EM20251107176248672918',
'out_request_no' => 'EM20251107176248672918_1762486729',
'order_title' => '环保排污登记服务',
'amount' => '0.01',
'product_code' => 'PREAUTH_PAY',
'timeout_express' => '2d',
'extra_param' => json_encode([
'company_id' => 64,
'emission_id' => 15,
'uid' => 1105,
'type' => 'emission',
], JSON_UNESCAPED_UNICODE),
];
$request->setBizContent(
json_encode($bizContent, JSON_UNESCAPED_UNICODE)
);
$request->setNotifyUrl(
'https://zfb.52xv.com/alipaynotify.php'
);
$orderStr = $aop->sdkExecute($request);
具体类名和调用方式以项目使用的 PHP SDK 为准。应由 SDK 负责构造待签名字符串、生成 RSA2 签名并完成 URL 编码。生成后不要再次对整个字符串编码。
前端直接传递服务端返回的完整字符串即可:
my.tradePay({
orderStr: serverResponse.orderStr
});
不要再执行以下操作:
// 不建议:可能造成二次编码
const encoded = encodeURIComponent(serverResponse.orderStr);
二次编码会让 %2F、%2B、%3D 等内容再次转义,支付宝端可能无法正确还原签名和业务参数。
检查签名配置
给出的 orderStr 使用:
sign_type=RSA2
请逐项确认:
- 签名使用的是 AppID
2021006102667907对应的应用私钥。 - 开放平台中配置的是与这把应用私钥对应的应用公钥。
- 验签使用的是该应用对应的支付宝公钥,不是应用公钥。
- 没有混用沙箱和正式环境的密钥。
- 没有混用普通公钥模式和支付宝证书模式。
- 私钥内容没有缺行或异常空格,也没有读取到旧配置。
- 多应用部署时,没有因缓存或环境变量配置错误而读取其他 AppID 的私钥。
服务端可以记录以下非敏感信息,方便核对:
error_log('alipay app_id=' . $aop->appId);
error_log('alipay sign_type=' . $aop->signType);
error_log('out_order_no=' . $bizContent['out_order_no']);
error_log('out_request_no=' . $bizContent['out_request_no']);
不要记录应用私钥,也不要把私钥、用户身份信息或完整授权结果返回给前端。
检查业务参数
当前 biz_content 解码后的主要内容是:
{
"out_order_no": "EM20251107176248672918",
"out_request_no": "EM20251107176248672918_1762486729",
"order_title": "环保排污登记服务",
"amount": "0.01",
"product_code": "PREAUTH_PAY",
"timeout_express": "2d",
"extra_param": "{\"company_id\":64,\"emission_id\":15,\"uid\":1105,\"type\":\"emission\"}"
}
可以按下面几项逐一检查。
订单号是否重复
out_order_no 和 out_request_no 必须符合支付宝接口的唯一性要求。如果调试时反复使用同一组编号,旧订单的状态可能影响后续创建。
每次测试都使用新的订单号,例如:
$outOrderNo = 'EM' . date('YmdHis') . random_int(100000, 999999);
$outRequestNo = $outOrderNo . '_01';
不要只依赖秒级时间戳。如果存在并发请求,还应增加数据库唯一约束或使用可靠的随机序列。
金额是否符合实际签约规则
当前金额为:
0.01
从格式上看,这是合法的两位小数字符串。但某些产品方案、业务场景或风控规则可能另有最低金额要求。是否允许一分钱预授权,要以当前商户签约产品的实际规则为准。
排查时可以改用符合真实业务的金额,避免长期使用明显异常的小额授权进行测试。
extra_param 是否必要
当前的 extra_param 是 JSON 字符串。这种序列化方式本身不一定有问题,但该字段属于附加业务数据。为了缩小排查范围,可以先删除它,只用最小参数集测试:
$bizContent = [
'out_order_no' => $outOrderNo,
'out_request_no' => $outRequestNo,
'order_title' => '服务预授权',
'amount' => '1.00',
'product_code' => 'PREAUTH_PAY',
'timeout_express' => '30m',
];
如果最小参数集能够成功,再依次恢复 extra_param、原订单标题和超时时间,判断问题来自基础配置还是业务参数。
检查服务器时间
请求中包含:
timestamp=2025-11-07 11:38:49
该时间需要与实际发起请求的时间接近。如果这是以前保存的 orderStr,不能再次用于调用。每次发起支付或授权前,都应实时生成新的 orderStr。
还要检查服务器时区和 NTP 同步:
date_default_timezone_set('Asia/Shanghai');
不要长期缓存并重复使用同一个 orderStr。
增加服务端主动查询
客户端提示不能代表最终订单状态。取得授权订单号后,应调用授权订单查询接口,确认支付宝侧是否已创建订单。
查询时通常使用商户授权订单号或支付宝授权订单号。示意代码如下:
<?php
$request = new AlipayFundAuthOperationDetailQueryRequest();
$request->setBizContent(json_encode([
'out_order_no' => $outOrderNo,
], JSON_UNESCAPED_UNICODE));
$response = $aop->execute($request);
error_log(json_encode(
$response,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
));
查询参数和具体类名请根据当前接口文档及实际使用的 SDK 调整。重点记录以下内容:
codemsgsub_codesub_msgauth_nooperation_id- 授权订单状态
相比小程序前端的统一文案,sub_code 和 sub_msg 通常更有助于定位问题。
推荐的排查顺序
- 在前端补充完整的
success、fail和complete日志。 - 每次请求都实时生成新的
orderStr和唯一订单号。 - 使用最小
biz_content测试,暂时删除extra_param。 - 确认前端没有对
orderStr二次编码、截断或转成 JSON。 - 核对 AppID、应用私钥、支付宝公钥和正式环境网关。
- 确认预授权产品绑定的是当前小程序 AppID,而不只是商户主体已经签约。
- 调用授权订单查询接口,检查支付宝侧是否实际创建了订单。
- 在支付宝开放平台或商户平台查询接口调用日志,取得
sub_code、sub_msg和请求流水号。 - 如果平台日志仍未给出明确原因,将 AppID、请求时间、商户订单号和支付宝请求流水号提交给支付宝技术支持核查。
不要仅根据页面上的“订单创建失败”修改业务逻辑。应先取得具体错误码或平台调用日志,否则无法判断是签名错误、应用未签约、订单号冲突、参数限制,还是风控拦截。
备注:内容仅供参考。
