付款码支付时如何获取用户关闭密码弹窗的取消行为
结论
alipay.trade.pay 没有专门标识“用户关闭密码弹窗”的字段,也没有可稳定用于判断这一动作的错误码。商户无法仅凭该接口确认用户是否点击了密码弹窗的关闭按钮。
付款码交易需要用户输入密码时,alipay.trade.pay 可能返回处理中状态。商户应根据接口结果查询交易的最终状态。如果超过规定时间仍未支付,再撤销交易。前端可以提示“支付未完成”或“支付已取消”,但不能断言“用户关闭了密码弹窗”。
原因
密码弹窗是支付宝客户端内部的支付交互。商户支付接口提供的是交易结果,不是用户在客户端内的具体点击记录。
接口可能返回以下结果:
- 支付成功;
- 交易处理中,需要继续查询;
- 支付失败;
- 交易已关闭;
- 商户主动撤销成功。
即使交易最终没有成功,也无法据此认定用户关闭了密码弹窗。网络超时、余额不足、风控限制和其他支付异常都可能产生类似结果。
推荐处理流程
1. 检查 alipay.trade.pay 的同步响应
先检查响应中的 code、sub_code 和相关业务字段:
code = 10000:支付成功;code = 10003:等待用户付款或交易处理中,不能立即按支付失败处理;- 明确的业务失败:根据
sub_code和sub_msg记录原因,并向用户显示合适的提示; - 网络超时或响应结果未知:不能直接判定支付失败,需要查询交易状态。
不要通过匹配 sub_msg 文本判断用户是否关闭了弹窗。提示文案可能发生变化,不适合用作程序分支条件。
2. 查询处理中或结果未知的交易
使用同一个 out_trade_no 调用 alipay.trade.query,主要检查 trade_status:
TRADE_SUCCESS:支付成功;TRADE_FINISHED:交易完成;WAIT_BUYER_PAY:仍在等待付款;TRADE_CLOSED:交易已经关闭,无法继续付款。
如果查询结果仍是 WAIT_BUYER_PAY,可以在有限时间内短间隔轮询。商户应根据自己的收银业务设置轮询次数和总时长,不能无限查询。
3. 超过等待时间后撤销交易
达到商户设置的支付等待时间后,调用 alipay.trade.cancel 撤销交易。撤销成功后,收银端可以显示:
支付未完成,订单已取消,请重新付款。
这里的“订单已取消”表示交易已经关闭或撤销,不代表系统识别到了用户关闭密码弹窗的动作。
如果撤销结果不明确,需要继续查询最终状态,避免出现商户端显示已取消、用户却实际支付成功的情况。
Java 处理示例
以下代码用于说明状态处理思路。具体类名和字段访问方式以项目所使用的支付宝 Java SDK 版本为准。
AlipayTradePayRequest request = new AlipayTradePayRequest();
request.setBizContent(payBizContent);
AlipayTradePayResponse response = alipayClient.execute(request);
String code = response.getCode();
if ("10000".equals(code)) {
// 支付成功
showMessage("支付成功");
savePaidResult(response);
} else if ("10003".equals(code)) {
// 等待用户付款或交易处理中
TradeResult result = queryUntilFinalState(outTradeNo);
switch (result.getTradeStatus()) {
case "TRADE_SUCCESS":
case "TRADE_FINISHED":
showMessage("支付成功");
break;
case "TRADE_CLOSED":
showMessage("支付未完成,交易已取消");
break;
case "WAIT_BUYER_PAY":
CancelResult cancelResult = cancelTrade(outTradeNo);
if (cancelResult.isSuccess()) {
showMessage("支付未完成,订单已取消,请重新付款");
} else {
// 撤销结果不确定时再次查询,不能直接认定失败
enqueueReconciliation(outTradeNo);
showMessage("支付结果确认中,请勿重复付款");
}
break;
default:
enqueueReconciliation(outTradeNo);
showMessage("支付结果确认中,请稍后查看");
break;
}
} else {
// 明确的业务失败
log.warn(
"alipay.trade.pay failed, code={}, subCode={}, subMsg={}",
response.getCode(),
response.getSubCode(),
response.getSubMsg()
);
showMessage(buildSafePaymentMessage(response.getSubCode()));
}
查询时可以设置有限的轮询次数:
private TradeResult queryUntilFinalState(String outTradeNo)
throws InterruptedException {
int maxAttempts = 5;
long intervalMillis = 2000L;
for (int i = 0; i < maxAttempts; i++) {
TradeResult result = queryTrade(outTradeNo);
String status = result.getTradeStatus();
if ("TRADE_SUCCESS".equals(status)
|| "TRADE_FINISHED".equals(status)
|| "TRADE_CLOSED".equals(status)) {
return result;
}
Thread.sleep(intervalMillis);
}
return TradeResult.waitBuyerPay();
}
生产环境中不建议长期占用 Web 请求线程进行轮询。查询和补偿可以交给支付任务、消息队列或定时任务处理。
商户端提示建议
由于商户无法可靠识别用户关闭密码弹窗的动作,建议按照交易状态显示提示:
| 交易情况 | 推荐提示 |
|---|---|
| 支付成功 | 支付成功 |
| 等待付款 | 请在支付宝中完成支付 |
| 已关闭或撤销成功 | 支付未完成,订单已取消 |
| 明确失败 | 支付失败,请更换支付方式或重试 |
| 状态未知 | 支付结果确认中,请勿重复付款 |
如果商户自己的收银页面有“取消支付”按钮,可以记录该按钮的点击事件。但这个事件只能说明用户在商户端主动取消,不能说明用户关闭了支付宝密码弹窗。
注意事项
- 不要把
10003视为支付失败,此时交易仍有可能成功。 - 不要根据接口耗时或客户端弹窗消失来推断支付结果。
- 不要依赖
sub_msg文本进行业务判断,应优先使用code、sub_code和trade_status。 - 查询、撤销和支付成功通知可能并发发生,需要使用同一个
out_trade_no做幂等处理。 - 撤销失败或响应超时后,必须继续查询并进行对账。
- 如果业务必须统计“关闭密码弹窗”这一交互,只能通过能够观察该动作的客户端能力来获取。在没有对应官方字段或回调的情况下,
alipay.trade.pay无法单独完成这项统计。
备注:内容仅供参考。
