手机网站支付中如何关闭未支付且查询为交易不存在的订单
结论
查询结果显示“交易不存在”,说明支付宝侧还没有创建这笔交易。此时无法调用 alipay.trade.close 将其关闭,也不会收到订单超时的异步通知。
商家系统应自行关闭本地订单,并禁止再次使用该订单号发起支付。如果之后发现支付宝侧已经创建交易,再根据实际状态处理:交易仍在等待支付时调用关闭接口;交易已经支付时,转入支付成功或退款流程,不能直接关闭。
为什么不是“等待支付”
调用手机网站支付接口后,商家通常只生成并返回支付页面或跳转表单。如果用户没有继续打开收银台、确认支付,或者请求未成功进入支付宝交易系统,支付宝侧可能不会生成交易记录。
这时使用商家订单号 out_trade_no 调用 alipay.trade.query,得到的结果会是“交易不存在”,而不是等待付款状态。
具体来说:
timeout_express只对支付宝侧已经创建的交易生效;- 不存在的交易不会发生状态变化;
- 没有状态变化,也就不会触发支付超时的异步通知;
alipay.trade.close无法关闭一笔尚不存在的支付宝交易。
因此,商家不能只靠异步通知关闭超时订单。
推荐的处理流程
1. 商家系统独立管理订单有效期
创建订单时,需要在本地数据库中记录订单状态和过期时间,例如:
status = WAIT_PAY
expire_time = 2026-09-09 15:30:00
订单超过有效期后,如果仍未确认支付,就将本地状态更新为 CLOSED。这一操作不依赖支付宝异步通知。
更新时需要检查原状态,避免并发操作覆盖已经支付成功的订单:
UPDATE merchant_order
SET status = 'CLOSED',
close_reason = 'PAY_TIMEOUT',
closed_at = CURRENT_TIMESTAMP
WHERE order_no = :order_no
AND status = 'WAIT_PAY'
AND expire_time <= CURRENT_TIMESTAMP;
如果受影响行数为 0,应重新读取订单状态,确认订单是否已经支付、已经关闭或正在处理中。
2. 查询支付宝交易状态
在关闭本地订单前后,可以使用 out_trade_no 调用:
alipay.trade.query
查询后按结果处理:
| 查询结果 | 处理方式 |
|---|---|
| 交易不存在 | 直接关闭商家本地订单,无需调用支付宝关闭接口 |
WAIT_BUYER_PAY | 调用 alipay.trade.close,成功后关闭本地订单 |
TRADE_SUCCESS | 按支付成功处理,不得直接关闭 |
TRADE_FINISHED | 按已完成交易处理 |
TRADE_CLOSED | 将本地订单同步为关闭状态 |
| 网络异常或结果不明确 | 暂不做最终判断,稍后重试查询 |
查询响应中的业务错误码或子错误码通常会表明交易不存在。具体字段和值应以当前接口的实际返回数据及支付宝开放平台文档为准,不要只根据错误文案判断。
3. 交易存在时再调用关闭接口
只有查询确认支付宝侧已经存在这笔交易,并且仍处于待支付状态,才能调用:
alipay.trade.close
请求时通常使用商家订单号:
{
"out_trade_no": "ORDER_202609090001"
}
如果已经取得支付宝交易号,也可以按照接口要求使用 trade_no。不要同时传入彼此矛盾的订单标识。
参考代码
下面的代码只用于说明业务流程。SDK 类名、异常类型和响应字段需要根据所用语言及支付宝 SDK 调整。
public void closeExpiredOrder(String orderNo) {
MerchantOrder order = orderRepository.findByOrderNo(orderNo);
if (order == null || !"WAIT_PAY".equals(order.getStatus())) {
return;
}
if (order.getExpireTime().isAfter(LocalDateTime.now())) {
return;
}
AlipayQueryResult queryResult = alipayService.queryByOutTradeNo(orderNo);
switch (queryResult.getType()) {
case TRADE_NOT_EXIST:
// 支付宝侧没有可关闭的交易,只关闭本地订单
orderRepository.closeIfWaiting(orderNo, "PAY_TIMEOUT");
break;
case WAIT_BUYER_PAY:
AlipayCloseResult closeResult =
alipayService.closeByOutTradeNo(orderNo);
if (closeResult.isSuccess()) {
orderRepository.closeIfWaiting(orderNo, "PAY_TIMEOUT");
} else {
// 关闭失败时重新查询,不能直接认定订单已关闭
scheduleQueryRetry(orderNo);
}
break;
case TRADE_SUCCESS:
case TRADE_FINISHED:
// 查询确认已支付,按支付成功流程处理
handlePaymentSuccess(orderNo, queryResult);
break;
case TRADE_CLOSED:
orderRepository.closeIfWaiting(orderNo, "ALIPAY_TRADE_CLOSED");
break;
case UNKNOWN:
default:
// 网络异常、系统错误或无法确认结果时稍后重试
scheduleQueryRetry(orderNo);
break;
}
}
防止已关闭订单再次付款
本地订单关闭后,应禁止继续使用该订单号生成支付请求:
public String createPayment(String orderNo) {
MerchantOrder order = orderRepository.findByOrderNo(orderNo);
if (order == null) {
throw new IllegalArgumentException("订单不存在");
}
if (!"WAIT_PAY".equals(order.getStatus())) {
throw new IllegalStateException("订单当前状态不允许支付");
}
if (!order.getExpireTime().isAfter(LocalDateTime.now())) {
orderRepository.closeIfWaiting(orderNo, "PAY_TIMEOUT");
throw new IllegalStateException("订单已过期");
}
return alipayService.createWapPayment(order);
}
如果用户已经拿到之前生成的支付页面或跳转表单,本地订单状态改变后,旧页面未必会立即失效。因此,还需要设置补偿机制:
- 继续接收支付宝异步通知并完成验签。
- 主动查询接近超时边界的订单。
- 本地订单已经关闭,但支付宝查询确认支付成功时,应将其记录为异常支付。
- 按照业务规则恢复订单、转人工处理或调用退款接口,不能直接丢弃成功通知。
注意事项
- 商家订单状态应由本地系统维护,不能完全依赖支付宝异步通知。
- “交易不存在”不等于“支付失败”,只表示当前查询不到对应的支付宝交易。
- 查询和关闭之间可能发生并发支付。关闭接口调用失败后,应重新查询交易状态。
- 异步通知、同步跳转和主动查询都可能重复到达,业务处理必须保证幂等。
- 支付宝返回成功后,仍需核对
app_id、out_trade_no、金额、币种及收款方等关键字段。 - 同一个
out_trade_no不应在不同商家订单之间复用。 - 定时关闭任务应支持重试,并记录查询结果、关闭结果和失败原因,以便后续对账。
- 如果业务要求过期订单绝对不能付款,应缩短支付入口的有效期,并为迟到支付设置自动退款或人工审核流程。
备注:内容仅供参考。
