电脑网站支付订单过期后未收到 TRADE_CLOSED 回调
结论
这两类状态不需要分别配置回调地址。电脑网站支付产生的交易状态通知,都会发送到调用 alipay.trade.page.pay 时设置的 notify_url。既然已经能正常收到 TRADE_SUCCESS,异步通知地址和基础网络配置就是有效的。
还要注意以下几点:
WAIT_BUYER_PAY只是交易状态,不能可靠地表示“用户已经扫码”。支付宝不保证所有中间状态都会按照顺序逐一通知。timeout_express表示订单可以付款的时限,不能当作商户系统的精确定时器。订单超时后,支付宝侧的关闭处理可能有延迟,因此本地关单不能只依赖TRADE_CLOSED通知。- 开放平台中的“应用网关”和“消息接收地址”不是电脑网站支付交易状态通知的配置项,无需为这两个状态另行订阅。
检查支付请求
每次调用 alipay.trade.page.pay 时,都应传入异步通知地址。只在本地业务系统中保存该地址是不够的。
以下为 Java SDK 示例:
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl("https://www.example.com/alipay/return");
request.setNotifyUrl("https://www.example.com/alipay/notify");
request.setBizContent("""
{
"out_trade_no": "MERCHANT_ORDER_20201023_001",
"product_code": "FAST_INSTANT_TRADE_PAY",
"total_amount": "100.00",
"subject": "测试订单",
"timeout_express": "30m"
}
""");
String form = alipayClient.pageExecute(request).getBody();
timeout_express 必须写在 biz_content 中。可用格式以及最小、最大取值,应以当前接入产品的支付宝开放平台文档为准,不要传入该接口不支持的格式。
如果同一地址已经收到支付成功通知,通常不需要修改 notify_url。此时应检查超时订单在支付宝侧是否确实已经变为 TRADE_CLOSED。
正确处理异步通知
通知接口应先保存原始请求并验证签名,再按 trade_status 分别处理,不能只接收 TRADE_SUCCESS。
@PostMapping("/alipay/notify")
@ResponseBody
public String alipayNotify(HttpServletRequest request) throws Exception {
Map<String, String> params = new HashMap<>();
for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
params.put(entry.getKey(), String.join(",", entry.getValue()));
}
boolean verified = AlipaySignature.rsaCheckV1(
params,
ALIPAY_PUBLIC_KEY,
"UTF-8",
"RSA2"
);
if (!verified) {
return "failure";
}
String appId = params.get("app_id");
String outTradeNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
String tradeStatus = params.get("trade_status");
if (!EXPECTED_APP_ID.equals(appId)) {
return "failure";
}
// 还应校验 out_trade_no、seller_id、金额和币种等业务字段。
switch (tradeStatus) {
case "WAIT_BUYER_PAY":
// 可记录支付宝侧已创建交易,但不要据此判断用户一定完成了扫码。
break;
case "TRADE_SUCCESS":
case "TRADE_FINISHED":
// 使用幂等方式更新支付状态。
break;
case "TRADE_CLOSED":
// 仅在本地订单尚未支付时更新为关闭状态。
break;
default:
// 记录未知状态,避免直接丢弃。
break;
}
return "success";
}
通知参数应在判断状态前完整保存。一种常见情况是,业务代码只记录支付成功分支,其他状态虽然已经到达,却没有留下日志。
处理完成后,接口必须直接返回以下纯文本:
success
不要返回 JSON、HTML 或附加任何其他字符。如果处理失败,应返回 failure,由支付宝按照通知重试机制再次发送。
超时订单的推荐处理方式
商户系统需要自行维护订单截止时间,并结合主动查询和关单完成处理:
- 创建订单时保存本地过期时间,同时向支付宝传入合法的
timeout_express。 - 本地订单到期后,调用
alipay.trade.query查询支付宝侧的交易状态。 - 如果状态仍是
WAIT_BUYER_PAY,调用alipay.trade.close主动关闭交易。 - 根据关单接口的响应或再次查询的结果,更新本地订单。
- 后续收到异步通知时,仍按幂等方式处理。
查询示例:
AlipayTradeQueryRequest queryRequest = new AlipayTradeQueryRequest();
queryRequest.setBizContent("""
{
"out_trade_no": "MERCHANT_ORDER_20201023_001"
}
""");
AlipayTradeQueryResponse queryResponse =
alipayClient.execute(queryRequest);
主动关单示例:
AlipayTradeCloseRequest closeRequest = new AlipayTradeCloseRequest();
closeRequest.setBizContent("""
{
"out_trade_no": "MERCHANT_ORDER_20201023_001"
}
""");
AlipayTradeCloseResponse closeResponse =
alipayClient.execute(closeRequest);
调用关单接口后,不一定会立即收到 TRADE_CLOSED 通知。判断订单状态时,还应参考关单接口的响应和后续查询结果。
排查建议
可以针对给出的支付宝订单号,在支付宝商家后台或通过 alipay.trade.query 查询实际状态和关闭时间。本地截止时间已到,并不代表支付宝侧已经关闭了交易。
还应逐项检查:
- 对应的支付请求是否确实传入了
notify_url。 - 通知入口是否记录了全部 POST 参数,而不是只记录成功状态。
- Web 服务器、网关或 WAF 是否拦截了部分请求。
- 通知接口是否超时、发生重定向或返回了非
200状态码。 - 验签代码是否错误过滤了
TRADE_CLOSED或WAIT_BUYER_PAY。 - 是否混淆了商户订单号
out_trade_no和支付宝交易号trade_no。 - 回调中的
app_id、商户身份和订单金额是否与本地订单一致。 - 订单是否因为支付成功、全额退款或其他操作进入了不同的最终状态。
支付通知可以推动本地状态更新,但不能作为唯一依据。可靠的订单系统还需要配合本地超时任务、交易查询和主动关单。
备注:内容仅供参考。
