PAYATHON 2026

如何让 alipay.trade.wap.pay 返回的 URL 立即失效?

支付小周

结论

alipay.trade.wap.pay 生成的支付 URL,有些 SDK 返回的是自动提交的 HTML 表单,不能通过另一个接口直接撤销。如果支付宝还没有创建交易,alipay.trade.close 就没有可关闭的交易对象,也无法让已经泄露给用户的支付宝支付 URL 立即失效。

如果需要控制支付入口,应避免提前向用户暴露支付宝的原始支付地址,改为提供商户自己的短期支付入口。取消支付时,先禁用商户入口;如果支付宝已经创建交易,再调用 alipay.trade.close。

还要处理取消和付款同时发生的情况。如果交易已经支付,应进入退款流程,不能继续调用关闭接口。

为什么不能直接让 URL 失效

alipay.trade.wap.pay 返回的内容本质上是一份带签名的支付请求。支付宝通常会在用户访问并提交请求后创建交易,因此可能出现以下状态:

  1. 商户已经生成支付请求。
  2. 用户还没有打开支付页面。
  3. 支付宝侧还没有对应交易。
  4. 此时调用 alipay.trade.close,无法根据 out_trade_no 找到可关闭的交易。

支付宝的交易关闭接口只处理已经存在且状态允许关闭的交易,并不是用来撤销支付 URL 的。

return_url 和 quit_url 只控制页面跳转,不能限制支付。删除本地订单、修改前端页面或停止接收异步通知,同样无法阻止用户通过已经拿到的原始 URL 继续付款。

推荐方案:使用商户侧支付入口

不要长期保存预先生成的支付宝 URL,也不要直接把它发送给用户。可以先生成一个商户自己的支付地址:

https://pay.example.com/pay/7d0f1c8a...

该地址绑定一个短期、随机且可撤销的支付令牌。用户访问时,服务端先检查业务订单状态。只有订单仍然允许付款,才调用 alipay.trade.wap.pay,并向用户输出支付表单。

示例流程:

用户访问商户支付入口
        ↓
校验支付令牌、订单状态和有效期
        ↓
订单已取消 ──→ 返回“订单已取消”
        ↓
订单可支付
        ↓
调用 alipay.trade.wap.pay
        ↓
将支付宝返回的表单响应给用户

下面是简化后的 Java 示例。具体类名和异常处理应以项目使用的支付宝 SDK 为准:

@GetMapping("/pay/{token}")
public void pay(@PathVariable String token,
                HttpServletResponse response) throws Exception {

    PayToken payToken = payTokenService.find(token);

    if (payToken == null
            || payToken.isExpired()
            || payToken.isRevoked()) {
        response.sendError(HttpServletResponse.SC_GONE, "支付链接已失效");
        return;
    }

    Order order = orderService.findById(payToken.getOrderId());

    if (order == null || !order.isPayable()) {
        response.sendError(HttpServletResponse.SC_CONFLICT, "订单当前不可支付");
        return;
    }

    AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
    request.setNotifyUrl("https://pay.example.com/alipay/notify");
    request.setReturnUrl("https://pay.example.com/alipay/return");

    String bizContent = """
        {
          "out_trade_no": "%s",
          "total_amount": "%s",
          "subject": "%s",
          "product_code": "QUICK_WAP_WAY",
          "timeout_express": "10m"
        }
        """.formatted(
            order.getOutTradeNo(),
            order.getAmount().toPlainString(),
            order.getSubject()
        );

    request.setBizContent(bizContent);

    String form = alipayClient.pageExecute(request).getBody();

    response.setContentType("text/html;charset=UTF-8");
    response.getWriter().write(form);
}

取消订单时,先让商户入口失效:

@Transactional
public void cancelOrder(String orderId) {
    Order order = orderService.lockById(orderId);

    if (order.isPaid()) {
        throw new IllegalStateException("订单已支付,不能直接取消");
    }

    order.markCancelled();
    payTokenService.revokeByOrderId(orderId);
}

这样可以阻止尚未取得支付宝原始请求的用户继续发起支付。

已经返回原始 URL 后怎么处理

如果支付宝原始 URL 或表单已经发给用户,商户就无法保证它立即失效,只能通过状态补偿降低风险:

  1. 将业务订单标记为“取消中”或“已取消”,并停止生成新的支付请求。
  2. 使用同一个 out_trade_no 调用 alipay.trade.query 查询交易。
  3. 如果交易已经存在且状态允许关闭,调用 alipay.trade.close。
  4. 如果查询结果仍为交易不存在,可以在有限时间内重试查询。
  5. 如果异步通知显示交易已经支付,不要交付业务权益,并根据业务规则调用退款接口。
  6. 关闭或退款完成后,再把业务订单更新为最终状态。

示意代码:

public void cancelPayment(String outTradeNo) {
    orderService.markCancelling(outTradeNo);
    payTokenService.revokeByOutTradeNo(outTradeNo);

    TradeStatus status = alipayService.query(outTradeNo);

    switch (status) {
        case WAIT_BUYER_PAY:
            alipayService.close(outTradeNo);
            orderService.markCancelled(outTradeNo);
            break;

        case TRADE_SUCCESS:
        case TRADE_FINISHED:
            orderService.markRefundRequired(outTradeNo);
            break;

        case NOT_EXIST:
            retryService.scheduleTradeQuery(outTradeNo);
            break;

        default:
            orderService.markManualReviewRequired(outTradeNo);
    }
}

其中的 TradeStatus、retryService 和业务状态都是示意封装,并非支付宝 SDK 提供的固定类型。

设置支付超时时间

创建支付请求时,可以根据当前接口文档和签约产品的支持情况设置 timeout_express,例如:

{
  "out_trade_no": "ORDER_202609090001",
  "total_amount": "99.00",
  "subject": "测试订单",
  "product_code": "QUICK_WAP_WAY",
  "timeout_express": "10m"
}

该参数用于限制交易创建后的待支付时间,可以降低旧订单被继续支付的风险。但它不能让 URL 立即失效,也不能代替商户侧支付入口和交易关闭逻辑。具体支持的格式、最短期限和实际生效范围,应以当前支付宝开放平台文档及商户签约产品为准。

注意事项

  • 不要试图通过更换应用密钥让单个支付 URL 失效。这会影响整个应用,而且已经生成的签名请求是否会立即受到影响,也不能作为可靠的订单控制手段。
  • 不要重复使用同一个支付令牌。令牌应短期有效、随机且不可预测,并在成功进入支付流程后,根据业务需要将其标记为已使用。
  • 检查订单状态与生成支付请求之间存在并发窗口。应使用数据库锁、条件更新或状态机,避免取消和支付同时发生。
  • 支付是否成功,应以支付宝服务端异步通知和主动查询结果为准,不能只依赖同步跳转。
  • 即使业务订单已经取消,也要正常验签并处理支付宝异步通知,以免出现支付宝已经收款但商户没有入账的资金差错。
  • 交易关闭只适用于尚未支付且状态允许关闭的交易。已经支付的交易必须按退款流程处理。

备注:内容仅供参考。