PAYATHON 2026

电脑网站支付链接在商城订单关闭后仍可支付,如何解决?

支付小周

结论

关闭自建商城订单时,也要关闭支付平台中的对应交易。只修改本地订单状态,已经生成的支付链接不会自动失效。

如果使用支付宝电脑网站支付,可以在关闭商城订单时调用 alipay.trade.close。交易成功关闭后,即使原支付页面还能打开,用户也无法继续付款。

创建支付交易时还可以设置 timeout_express,让交易到期后自动失效。不过,这只适合用作异常兜底,不能代替主动关单。

为什么商城订单关闭后仍能支付

商城订单和支付平台交易是两个相互独立的对象:

  • 商城订单状态保存在自建系统中;
  • 支付链接对应的交易状态保存在支付平台中;
  • 把本地订单更新为“已关闭”,不会自动通知支付平台;
  • 只要平台侧交易仍处于待支付状态,旧链接通常就能继续付款。

完整的关单流程需要同时处理本地订单和平台交易。隐藏订单或修改数据库字段,都无法关闭平台侧交易。

推荐的处理流程

1. 创建支付交易时保存交易标识

发起支付时,需要保存商城订单与支付平台交易的对应关系。在支付宝场景中,通常至少要保存:

  • out_trade_no:商户订单号;
  • trade_no:支付宝交易号,获取后可以保存;
  • 当前支付状态;
  • 支付链接生成时间;
  • 订单过期时间。

out_trade_no 在商户系统中必须唯一。后续查询交易、关闭交易和处理支付通知时,都应使用同一个订单号。

2. 到期后先关闭平台交易

定时任务或人工关闭订单时,可以按以下顺序处理:

  1. 先把本地订单改为“关闭处理中”,阻止新的支付请求;
  2. 查询平台交易状态;
  3. 如果交易仍待支付,调用关单接口;
  4. 平台确认交易关闭后,再把本地订单改为“已关闭”;
  5. 如果交易已经支付,则进入支付成功补偿流程,不能再按未支付订单关闭。

增加“关闭处理中”状态,可以降低关单与客户付款同时发生时的竞态风险。

3. 调用支付宝关单接口

下面的示例使用支付宝 Java SDK。具体类名和调用方式,应以项目使用的 SDK 版本为准:

AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();

AlipayTradeCloseModel model = new AlipayTradeCloseModel();
model.setOutTradeNo(order.getOrderNo());
// 如果已经保存了支付宝交易号,也可以根据接口要求使用 trade_no。
// model.setTradeNo(order.getAlipayTradeNo());

request.setBizModel(model);

AlipayTradeCloseResponse response = alipayClient.execute(request);

if (response.isSuccess()) {
    orderService.markClosed(order.getId());
} else {
    orderService.markCloseFailed(
        order.getId(),
        response.getCode(),
        response.getSubCode(),
        response.getSubMsg()
    );
}

调用关单接口时,通常从 out_trade_no 和 trade_no 中选择一个定位交易。具体限制应以当前接口文档和项目所用 SDK 为准。

接口没有抛出异常,并不代表交易已经成功关闭。还要检查业务响应,例如:

response.isSuccess()
response.getCode()
response.getSubCode()
response.getSubMsg()

遇到网络超时或响应结果不明确时,不要立即把订单标记为已关闭。应调用交易查询接口确认平台侧状态,或者安排任务重试。

创建支付时设置自动过期时间

发起电脑网站支付时,可以按照业务规则设置 timeout_express,让平台侧交易在指定时间后失效。例如:

AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();

AlipayTradePagePayModel model = new AlipayTradePagePayModel();
model.setOutTradeNo(order.getOrderNo());
model.setTotalAmount(order.getPayAmount().toPlainString());
model.setSubject(order.getSubject());
model.setProductCode("FAST_INSTANT_TRADE_PAY");
model.setTimeoutExpress("30m");

request.setBizModel(model);

timeout_express 支持的格式、最小值和最大值可能受接口规则限制,应以当前支付宝开放平台文档为准。

平台超时时间最好与商城订单的关闭时间保持一致,但它们仍由两个不同的系统管理。定时主动关单能更快地同步状态,timeout_express 则用于处理主动关单未执行或执行失败等异常情况。

必须处理支付与关单同时发生的情况

客户付款和商城定时关单可能恰好同时发生。例如:

  1. 定时任务读取到订单尚未支付;
  2. 客户此时完成付款;
  3. 定时任务继续关闭本地订单;
  4. 支付成功的异步通知随后到达。

因此,更新订单状态时必须带上条件,不能直接覆盖原状态:

UPDATE orders
SET status = 'CLOSING'
WHERE id = ?
  AND status = 'WAIT_PAY';

如果受影响行数为 0,说明订单状态已经改变,需要重新读取订单并根据新状态处理。

可以在关单前后查询平台状态。如果平台返回交易已经支付,应把本地订单转入支付成功或人工核对流程,不能继续标记为已关闭。

支付通知也要校验状态

即使已经发起主动关单,系统仍要正常接收和验证支付成功的异步通知。通知处理至少需要检查:

  • 签名是否合法;
  • app_id 是否属于当前应用;
  • out_trade_no 是否存在并与订单匹配;
  • total_amount 是否等于订单应付金额;
  • seller_id 或收款方信息是否符合预期;
  • 通知中的交易状态能否确认支付成功;
  • 当前订单状态是否允许转为已支付。

示例逻辑:

@Transactional
public void handlePaySuccess(PayNotify notify) {
    Order order = orderRepository.lockByOrderNo(notify.getOutTradeNo());

    verifyAppId(notify);
    verifySeller(notify);
    verifyAmount(order, notify.getTotalAmount());

    if (order.isPaid()) {
        return; // 重复通知,幂等返回
    }

    if (order.isClosing() || order.isClosed()) {
        // 不能简单丢弃,因为平台侧可能已经真实扣款。
        exceptionService.recordPaidAfterClose(order, notify);
        return;
    }

    order.markPaid(notify.getTradeNo());
    orderRepository.save(order);
}

如果本地订单已经关闭,但平台显示交易已支付,应按实际资金状态处理。常见做法是记录异常支付、停止发货,再根据业务规则自动退款或交由人工核对。不能因为商城订单已经关闭,就忽略支付通知。

失败重试与对账

关单请求可能因网络超时、平台繁忙或状态冲突而失败。可以增加这些处理机制:

  • 使用幂等的商户订单号发起关单请求;
  • 关单失败后记录错误码和错误信息;
  • 对状态不明确的交易调用查询接口确认;
  • 定时扫描“关闭处理中”和“关单失败”的订单;
  • 每日使用支付平台账单进行对账;
  • 对本地已关闭但平台侧已付款的记录触发退款或人工告警。

请求已经发送,不等于平台交易已经关闭。最终状态应根据平台查询结果、关单响应和后续对账结果确定。

注意事项

  • 删除或隐藏支付链接无法关闭平台交易,用户可能还保留着旧地址、二维码或浏览器页面。
  • 前端禁止支付只能改善交互,不能代替服务端关单。
  • 已支付成功的交易通常无法再通过关单接口撤销,应根据实际情况调用退款接口。
  • 订单关闭后再次购买时,应创建新的 out_trade_no,不能复用旧交易。
  • 金额、商户身份和订单号都必须由服务端校验,不能信任支付回跳页面中的参数。
  • 同步跳转只用于展示结果。最终支付状态应通过验签后的异步通知和主动查询共同确认。

商城关单时,需要同步关闭支付平台交易。支付与关单并发产生的状态问题,则应通过订单状态机、幂等处理、主动查询和对账机制解决。

备注:内容仅供参考。