PAYATHON 2026

支付宝小程序支付此前正常,现在提示付款失败是什么原因?

支付小周

结论

之前可以正常付款,现在却统一提示“付款失败”,问题通常不在小程序前端按钮,而是支付链路中的某个条件发生了变化。常见原因有:

  • 商户支付产品的签约状态、账户状态或风控状态发生变化;
  • 小程序 appId、商户身份与支付签约主体不匹配;
  • 应用私钥、支付宝公钥或证书配置失效;
  • 服务端创建交易失败后,仍让前端继续拉起支付;
  • 订单号重复、订单已关闭,或金额、订单参数不合法;
  • 前端传入的 tradeNO 错误、为空或已经失效;
  • 用户取消付款、余额不足或支付方式受限,但页面统一显示为付款失败;
  • 网络、支付宝客户端或运行环境异常。

排查时不能只看页面上的“付款失败”。还要同时检查前端的失败回调、服务端创建订单的响应,以及支付宝侧记录的真实交易状态。

先确认失败发生在哪一步

支付宝小程序支付通常分为两个关键步骤:

  1. 服务端调用支付接口创建交易,取得支付宝交易号。
  2. 小程序调用 my.tradePay 拉起收银台。

如果服务端创建交易时已经失败,前端自然无法完成付款。若交易创建成功,但拉起收银台或付款时失败,则要继续检查订单状态、用户支付条件和风险控制。

前端应完整记录支付回调,不能只显示一条固定提示:

my.tradePay({
  tradeNO: tradeNo,
  success: (result) => {
    console.log('tradePay success:', JSON.stringify(result));

    // 不建议仅凭前端回调确认最终支付结果
    // 应请求服务端查询订单状态
    queryOrderStatus();
  },
  fail: (error) => {
    console.error('tradePay fail:', JSON.stringify(error));

    my.alert({
      title: '付款未完成',
      content: error.errorMessage || error.message || '请稍后重试'
    });
  }
});

日志中应保留完整的错误码、错误信息、订单号、tradeNO、请求时间和当前环境。只记录“付款失败”四个字,很难判断故障出在哪一层。

常见原因及处理方法

1. 商户账户或支付产品状态异常

即使代码没有改动,商户账户、支付产品签约、结算能力或风控状态仍可能发生变化。例如:

  • 支付产品尚未生效、被暂停或需要补充资料;
  • 商户账户受到风险限制;
  • 企业资质、证件或协议需要更新;
  • 当前交易触发了支付宝侧风控;
  • 某种支付方式不适用于当前买家。

先到支付宝开放平台和商家后台检查应用及商户状态,确认相关支付能力仍然有效。如果只有部分用户、部分金额或特定商品付款失败,应重点考虑支付方式限制和风险控制。

风控问题通常无法靠反复修改前端代码解决。应保存失败时间、商户订单号和支付宝返回的错误信息,再通过对应的官方支持渠道核查。

2. appId、商户主体或环境不匹配

检查应用实际运行时加载的配置,尤其要确认生产环境是否误用了沙箱配置、其他应用的密钥或错误的网关地址。

需要核对:

  • 小程序的 appId;
  • 服务端请求支付接口时使用的 app_id;
  • 签约支付能力的商户主体;
  • 应用绑定关系;
  • 生产环境和沙箱环境;
  • 网关地址及回调地址配置。

前端小程序与服务端创建交易时使用的应用身份必须一致。如果近期切换过应用、重新发布过小程序、迁移过商户主体或调整过服务端配置,应优先检查这一项。

3. 密钥或证书配置发生变化

如果应用近期更换过密钥、切换到证书模式或更新过证书,而服务端仍使用旧配置,支付请求可能因验签或身份认证失败而无法完成。

需要检查:

  • 应用私钥是否属于当前应用;
  • 支付宝公钥是否配置正确;
  • 是否混用了“支付宝公钥”和“应用公钥”;
  • 证书文件是否完整、是否过期;
  • 部署环境是否加载了正确的证书;
  • 签名方式及字符集是否与当前配置一致;
  • 多台服务器是否使用同一套密钥配置。

不要在日志中输出完整私钥。可以记录应用 appId、证书序列号或配置版本,以确认服务器实际加载的是哪套配置。

4. 服务端创建交易失败

服务端调用 alipay.trade.create 等接口后,要先判断支付宝返回的结果是否成功,再把交易号交给小程序。不能只检查 HTTP 状态码,也不能在接口失败后继续返回空的 tradeNO。

下面是示意代码,具体类名和字段应以所使用的支付宝 SDK 版本为准:

AlipayTradeCreateRequest request = new AlipayTradeCreateRequest();
request.setNotifyUrl(notifyUrl);

AlipayTradeCreateModel model = new AlipayTradeCreateModel();
model.setOutTradeNo(outTradeNo);
model.setTotalAmount(totalAmount);
model.setSubject(subject);
model.setBuyerId(buyerId);

request.setBizModel(model);

AlipayTradeCreateResponse response = alipayClient.execute(request);

if (!response.isSuccess()) {
    log.error(
        "create payment failed, outTradeNo={}, code={}, subCode={}, msg={}, subMsg={}",
        outTradeNo,
        response.getCode(),
        response.getSubCode(),
        response.getMsg(),
        response.getSubMsg()
    );
    throw new IllegalStateException("创建支付宝交易失败");
}

String tradeNo = response.getTradeNo();
if (tradeNo == null || tradeNo.isEmpty()) {
    throw new IllegalStateException("支付宝未返回 trade_no");
}

排查时应重点查看支付宝响应中的这些字段:

  • code
  • msg
  • sub_code
  • sub_msg
  • trade_no
  • out_trade_no

其中,sub_code 和 sub_msg 通常比页面上的统一提示更接近真实原因。

5. 订单号重复或订单状态异常

out_trade_no 必须在商户系统内保持唯一。重复使用旧订单号时,可能出现订单状态、金额或商品信息冲突。

常见情况包括:

  • 时间戳精度不足,导致并发生成相同订单号;
  • 支付失败后直接复用已经关闭的订单;
  • 同一订单号对应的金额发生变化;
  • 订单已经支付,却再次发起付款;
  • 交易已经超时关闭;
  • 数据库事务已经回滚,但外部交易仍然存在。

订单号可以由业务前缀、日期、数据库序列或随机标识组合生成,例如:

String outTradeNo =
    "MP"
    + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
    + UUID.randomUUID().toString().replace("-", "").substring(0, 12);

用户重新付款时,应根据订单当前状态决定继续支付原交易还是创建新交易,不能无条件复用原订单号。

6. 金额和业务参数不合法

金额必须由服务端校验,不能直接信任前端上传的价格。常见问题有:

  • 金额为 0 或负数;
  • 金额格式不符合接口要求;
  • 使用浮点数计算,导致金额出现精度误差;
  • 商品标题为空或包含异常内容;
  • 买家身份参数与当前登录用户不一致;
  • 必填参数为空;
  • 请求参数编码异常。

涉及金额计算时,应使用十进制定点类型,例如 Java 的 BigDecimal,不要直接使用 double:

BigDecimal unitPrice = new BigDecimal("19.90");
BigDecimal quantity = new BigDecimal("2");
BigDecimal totalAmount = unitPrice.multiply(quantity);

String paymentAmount = totalAmount.setScale(2, RoundingMode.HALF_UP).toPlainString();

不要只依赖前端支付结果

前端的支付成功回调只能说明客户端调用得到了相应结果,不能替代服务端对账。用户可能在付款后关闭页面,异步通知也可能因为网络问题延迟到达。

服务端应通过以下方式确认最终状态:

  • 接收并验签支付宝异步通知;
  • 使用 alipay.trade.query 主动查询交易;
  • 以服务端记录的订单状态为准;
  • 更新订单时保证幂等,防止重复发货或重复记账。

查询订单的示意逻辑如下:

AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();

AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setOutTradeNo(outTradeNo);
request.setBizModel(model);

AlipayTradeQueryResponse response = alipayClient.execute(request);

if (response.isSuccess()) {
    String tradeStatus = response.getTradeStatus();
    // 根据支付宝实际返回的交易状态更新本地订单
} else {
    log.warn(
        "query payment failed, outTradeNo={}, code={}, subCode={}, msg={}, subMsg={}",
        outTradeNo,
        response.getCode(),
        response.getSubCode(),
        response.getMsg(),
        response.getSubMsg()
    );
}

异步通知验签成功后,还要核对 app_id、商户订单号、金额和收款主体。不能只看到支付状态就直接发货。

建议的排查顺序

可以按照下面的顺序缩小问题范围:

  1. 记录一笔失败交易的商户订单号、发生时间和用户环境。
  2. 查看小程序 my.tradePay 的完整失败回调。
  3. 检查服务端创建交易的完整响应,确认是否取得有效的 trade_no。
  4. 使用 out_trade_no 或 trade_no 查询支付宝交易状态。
  5. 核对金额、订单号、买家身份和订单有效期。
  6. 核对生产环境中的 appId、密钥、证书、网关和商户绑定关系。
  7. 检查支付产品签约、商户账户及应用状态。
  8. 分别使用不同账号、不同金额和不同设备测试,判断问题是全量发生,还是只在特定条件下出现。
  9. 如果返回信息指向风控、权限或商户状态,应携带错误码和订单号联系支付宝官方支持核查。

注意事项

  • 不要把应用私钥、支付宝公钥原文或用户敏感信息写入日志。
  • 不要直接向用户展示内部异常堆栈。可以显示简洁提示,并在内部保留错误编号。
  • 不要在前端生成最终金额。商品价格应由服务端重新计算。
  • 不要因为前端提示失败就立即认定没有扣款,应查询服务端交易状态。
  • 不要在用户连续点击时重复创建多笔交易,应增加防重复提交和订单幂等控制。
  • 如果近期完全没有修改代码,应重点检查商户状态、证书配置、部署环境和订单数据,而不只是排查小程序页面。
  • 不同 SDK、接口模式和商户接入方案返回的字段可能不同,应以实际响应和当前接入方式的官方接口文档为准。

备注:内容仅供参考。