alipay.trade.close 与 app支付接口2.0 状态问题
结论
-
用户还没有输入密码时,通常可以关闭交易。
只要支付宝服务端的交易状态仍是WAIT_BUYER_PAY,调用alipay.trade.close一般可以成功。用户是否停留在收银台或已经进入密码输入页面,都不能作为判断依据,最终要看支付宝服务端记录的交易状态。 -
交易已经支付成功时,关闭通常会失败。
如果交易状态已经变为TRADE_SUCCESS或TRADE_FINISHED,就不能再通过alipay.trade.close撤销支付。需要退回资金时,应根据业务情况调用退款接口。 -
用户输入密码后,商户系统不一定能立即确认交易结果。
输入密码不代表商户端已经收到最终支付结果。网络延迟、客户端中断、异步通知延迟或支付仍在处理中,都可能让商户暂时无法确认结果。最终应以支付宝服务端查询结果和异步通知为准,不能只看 App 返回的resultStatus。
为什么会出现这些情况
alipay.trade.app.pay、alipay.trade.close 和支付宝的实际支付处理可能同时进行,因此会产生状态竞争:
创建交易
↓
WAIT_BUYER_PAY
├─ 商户先关闭成功 → TRADE_CLOSED
└─ 支付先完成 → TRADE_SUCCESS
例如,用户已经输入密码,但支付宝还没有完成支付处理。此时商户发起关闭请求,可能出现以下结果:
- 关闭操作先成功,交易进入
TRADE_CLOSED,之后无法再完成支付; - 支付操作先成功,交易进入
TRADE_SUCCESS,关闭请求失败; - 调用过程中发生超时或通信异常,仅凭这次接口响应无法确定最终状态,必须重新查询。
所以,不能根据用户是否输入密码来判断交易能否关闭,也不能把关闭请求报错直接当成已经支付成功。请求参数错误、交易不存在、权限问题或当前交易状态不允许关闭,也可能导致关闭失败。排查时应检查返回的 code、sub_code 和 sub_msg。
推荐处理流程
1. 使用同一个商户订单号
调用 alipay.trade.app.pay 时生成唯一的 out_trade_no,后续查询、关闭和退款都通过这个订单号关联本地交易。
本地订单至少应区分以下状态:
- 待支付;
- 支付处理中;
- 支付成功;
- 已关闭;
- 退款中或已退款。
App 退出、支付页面关闭或客户端返回失败,都不足以证明订单尚未支付,不能直接把订单改成未支付状态。
2. 关闭前先查询交易
准备关闭超时订单时,先调用 alipay.trade.query,再根据查询结果处理:
WAIT_BUYER_PAY:可以尝试调用alipay.trade.close;TRADE_SUCCESS:按支付成功处理,不再关闭;TRADE_FINISHED:按支付完成处理,不再关闭;TRADE_CLOSED:把本地订单更新为已关闭;- 查询不到交易或状态无法确定:不要直接认定关闭成功,应结合重试、异步通知和人工补偿机制继续处理。
查询可以缩小状态竞争的时间窗口,但无法完全避免并发。因此,调用关闭接口后仍需检查返回结果,必要时再次查询。
3. 根据关闭结果更新订单
只有支付宝明确返回关闭成功,才能把本地订单更新为已关闭。
如果关闭失败,应按以下步骤处理:
- 记录完整的接口返回码和支付宝交易号;
- 再次调用
alipay.trade.query; - 如果查询结果为支付成功,执行支付成功逻辑;
- 如果交易仍处于待支付状态,根据失败原因决定是否重试关闭;
- 如果查询结果仍不明确,保留支付处理中状态,等待异步通知或后续补偿查询。
4. 以服务端结果确认支付
App 端的同步返回主要用于改善用户体验,不应作为最终入账依据。服务端需要同时处理:
- 支付宝异步通知;
alipay.trade.query主动查询;- 定时补偿任务。
处理异步通知时,必须验证签名,并核对以下信息:
app_id;out_trade_no;- 订单金额;
- 收款方信息;
- 本地订单是否已经处理。
支付成功逻辑必须具备幂等性,避免异步通知和主动查询同时到达时重复发货或重复记账。
Java 处理示例
下面的示例只展示核心判断逻辑。具体异常类型、SDK 初始化方式和日志实现,应以项目实际使用的支付宝 SDK 为准。
public void closeOrderSafely(String outTradeNo) throws AlipayApiException {
// 1. 先查询支付宝侧状态
AlipayTradeQueryRequest queryRequest = new AlipayTradeQueryRequest();
queryRequest.setBizContent(
"{\"out_trade_no\":\"" + outTradeNo + "\"}"
);
AlipayTradeQueryResponse queryResponse = alipayClient.execute(queryRequest);
if (!queryResponse.isSuccess()) {
// 查询失败不代表未支付,保留处理中状态并安排补偿查询
throw new IllegalStateException(
"Query failed, code=" + queryResponse.getCode()
+ ", subCode=" + queryResponse.getSubCode()
+ ", subMsg=" + queryResponse.getSubMsg()
);
}
String tradeStatus = queryResponse.getTradeStatus();
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
markOrderPaidIdempotently(
outTradeNo,
queryResponse.getTradeNo()
);
return;
}
if ("TRADE_CLOSED".equals(tradeStatus)) {
markOrderClosedIdempotently(outTradeNo);
return;
}
if (!"WAIT_BUYER_PAY".equals(tradeStatus)) {
// 未识别或处于暂不能关闭的状态,暂不修改为已关闭
scheduleQueryRetry(outTradeNo);
return;
}
// 2. 交易待支付,尝试关闭
AlipayTradeCloseRequest closeRequest = new AlipayTradeCloseRequest();
closeRequest.setBizContent(
"{\"out_trade_no\":\"" + outTradeNo + "\"}"
);
AlipayTradeCloseResponse closeResponse =
alipayClient.execute(closeRequest);
if (closeResponse.isSuccess()) {
markOrderClosedIdempotently(outTradeNo);
return;
}
// 3. 关闭失败可能是并发支付导致,再查询一次最终状态
logCloseFailure(
outTradeNo,
closeResponse.getCode(),
closeResponse.getSubCode(),
closeResponse.getSubMsg()
);
reconcileOrderByQuery(outTradeNo);
}
实际项目中不要通过字符串拼接生成 JSON。这里采用这种写法只是为了突出处理流程,正式代码应使用 SDK 提供的业务模型或可靠的 JSON 序列化组件。
异步通知示例
public String handleAlipayNotify(
Map<String, String> params) throws AlipayApiException {
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!verified) {
return "failure";
}
String outTradeNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
// 还应校验 app_id、金额、收款方等是否与本地订单一致
validateNotify(outTradeNo, totalAmount, params);
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
// 必须幂等,避免重复记账或重复发货
markOrderPaidIdempotently(outTradeNo, tradeNo);
}
return "success";
}
注意事项
alipay.trade.close只能关闭尚未支付的交易,不能用于退款。- 如果交易已经扣款且状态为
TRADE_SUCCESS,应先核对订单和资金状态,再按照业务规则退款。 - 接口超时、网络断开或没有收到响应,都表示结果未知,不能直接判定操作失败。
- App 返回取消、失败或未知状态,不能证明支付宝侧没有扣款。
- 主动查询和异步通知可能乱序到达。本地状态更新必须保持幂等,同时防止后到的关闭结果覆盖已经成功支付的订单状态。
- 自动关单应给用户留出合理的支付时间,并在关单前后核对交易状态,尽量减少用户付款过程中发生的状态竞争。
备注:内容仅供参考。
