PAYATHON 2026

支付宝小程序预支付提示订单创建失败是什么原因?

支付阿杰

结论

仅凭前端提示“订单创建失败,请联系商户核实订单信息”,无法判断具体原因。这是支付宝收银台返回的通用提示,还需要结合前端回调、支付宝服务端日志和商户平台配置排查。

从给出的 orderStr 来看,参数结构基本完整。建议优先检查以下几项:

  1. 小程序应用 2021006102667907 是否绑定了已开通预授权支付的同一商户主体。
  2. orderStr 是否通过 alipay.fund.auth.order.app.freeze 对应的 SDK 方法生成,并由前端原样传入。
  3. 签名使用的应用私钥,是否与该小程序 AppID 配置的支付宝公钥模式匹配。
  4. 商户的预授权产品权限是否已经生效,应用场景、类目和签约主体是否符合准入要求。
  5. out_order_no、out_request_no 是否重复使用,或者此前是否创建过失败、关闭或冻结中的授权订单。
  6. 请求时间、服务器时钟、金额和业务描述是否触发了参数校验或风控。

“已开通预授权支付产品”不代表任意 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));
  }
});

以下字段需要重点保存:

  • resultCode
  • memo
  • result
  • error
  • errorMessage

不同客户端版本和调用场景返回的字段可能不同,所以要记录完整对象,不要只读取其中一个字段。

常见的 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 调整。重点记录以下内容:

  • code
  • msg
  • sub_code
  • sub_msg
  • auth_no
  • operation_id
  • 授权订单状态

相比小程序前端的统一文案,sub_code 和 sub_msg 通常更有助于定位问题。

推荐的排查顺序

  1. 在前端补充完整的 success、fail 和 complete 日志。
  2. 每次请求都实时生成新的 orderStr 和唯一订单号。
  3. 使用最小 biz_content 测试,暂时删除 extra_param。
  4. 确认前端没有对 orderStr 二次编码、截断或转成 JSON。
  5. 核对 AppID、应用私钥、支付宝公钥和正式环境网关。
  6. 确认预授权产品绑定的是当前小程序 AppID,而不只是商户主体已经签约。
  7. 调用授权订单查询接口,检查支付宝侧是否实际创建了订单。
  8. 在支付宝开放平台或商户平台查询接口调用日志,取得 sub_code、sub_msg 和请求流水号。
  9. 如果平台日志仍未给出明确原因,将 AppID、请求时间、商户订单号和支付宝请求流水号提交给支付宝技术支持核查。

不要仅根据页面上的“订单创建失败”修改业务逻辑。应先取得具体错误码或平台调用日志,否则无法判断是签名错误、应用未签约、订单号冲突、参数限制,还是风控拦截。

备注:内容仅供参考。