H5 支付宝花呗分期支付成功后 return_url 未调用
结论
不要依赖 return_url 更新订单状态。
return_url 是浏览器端的同步跳转地址。用户关闭或刷新页面、网络中断,或者跳转被拦截,都可能导致它无法访问。判断订单是否支付成功,应以支付宝服务器发送到 notify_url 的异步通知为主,并使用 alipay.trade.query 主动查询来补偿异常情况。
建议采用以下处理方式:
- 创建支付时配置
return_url和notify_url。 - 在
notify_url中完成验签和订单信息核对,再幂等更新订单。 return_url只展示支付结果,不确认支付状态。- 前端结果页通过后端接口轮询订单状态。
- 定时查询长时间停留在”支付中”状态的订单,调用
alipay.trade.query进行对账补偿。
为什么 return_url 可能不调用
以 alipay.trade.wap.pay 为例,支付完成后有两类通知:
return_url:支付宝通过用户浏览器跳转到商户页面,属于同步跳转。notify_url:支付宝服务器直接请求商户服务器,属于异步通知。
return_url 依赖浏览器继续运行。如果用户支付后立即关闭或刷新页面、切换网络,或者浏览器中断了跳转,请求可能根本无法到达商户服务器,所以不能将它作为确认支付成功的可靠依据。
notify_url 不依赖用户页面,更适合用于更新订单状态。不过,除了正确配置异步通知,还需要准备主动查询机制,以处理网络异常、商户服务不可用等情况。
创建支付时配置异步通知
下面是 Java SDK 的示意代码。具体类名和调用方式以项目实际使用的支付宝 SDK 版本为准:
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setReturnUrl("https://www.example.com/pay/alipay/return");
request.setNotifyUrl("https://api.example.com/pay/alipay/notify");
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo(orderNo);
model.setTotalAmount(totalAmount);
model.setSubject("订单支付");
model.setProductCode("QUICK_WAP_WAY");
// 花呗分期参数应根据实际签约能力和支付宝接口要求设置
model.setExtendParams(extendParams);
request.setBizModel(model);
String form = alipayClient.pageExecute(request).getBody();
notify_url 必须是支付宝服务器能够访问的公网地址,不能使用 localhost、内网 IP,也不能指向需要登录后才能访问的地址。
正确处理 notify_url
异步通知应按以下流程处理:
- 读取支付宝提交的全部参数。
- 使用支付宝 SDK 验证签名。
- 核对
app_id、商户订单号、金额和收款方等信息。 - 确认
trade_status是可以接受的支付成功状态。 - 使用数据库事务幂等更新订单。
- 处理成功后返回纯文本
success。
Spring Boot 示例:
@PostMapping(
value = "/pay/alipay/notify",
produces = "text/plain;charset=UTF-8"
)
public String alipayNotify(HttpServletRequest request) throws Exception {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((key, values) -> {
params.put(key, String.join(",", values));
});
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"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");
String totalAmount = params.get("total_amount");
if (!expectedAppId.equals(appId)) {
return "failure";
}
Order order = orderService.findByOrderNo(outTradeNo);
if (order == null) {
return "failure";
}
if (new BigDecimal(totalAmount).compareTo(order.getPayAmount()) != 0) {
return "failure";
}
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
orderService.markPaidIfUnpaid(outTradeNo, tradeNo);
}
return "success";
}
生产环境还要根据实际业务核对收款账号、商户身份等字段。不能只看到 trade_status 就直接修改订单状态。
保证订单更新幂等
支付宝可能重复发送异步通知。通知接口必须能够多次处理同一笔交易,同时避免重复发货、充值或记账。
可以在数据库层增加约束,并采用条件更新:
UPDATE orders
SET
status = 'PAID',
alipay_trade_no = :tradeNo,
paid_at = CURRENT_TIMESTAMP
WHERE order_no = :orderNo
AND status = 'PAYING';
如果影响行数为 0,还要检查订单是否已经处于 PAID 状态。对于已支付的订单,可以直接按成功处理,但不能再次扣减库存或发放权益。
发货、充值等操作可以使用独立业务流水号或唯一索引,进一步避免重复执行。
前端不要直接展示”支付成功”
用户进入 return_url 后,页面应向商户后端查询订单状态,不能根据 URL 参数直接判定支付成功。
async function waitForPayment(orderNo) {
for (let i = 0; i < 10; i++) {
const response = await fetch(
`/api/orders/${encodeURIComponent(orderNo)}/status`,
{ cache: "no-store" }
);
const result = await response.json();
if (result.status === "PAID") {
showPaymentSuccess();
return;
}
if (result.status === "CLOSED") {
showPaymentFailed();
return;
}
await new Promise(resolve => setTimeout(resolve, 2000));
}
showPaymentProcessing();
}
即使 return_url 没有打开,用户下次进入订单详情页时,仍能从服务器获取真实的订单状态。
增加主动查询补偿
只配置 notify_url 也无法覆盖所有异常。例如,异步通知到达时商户服务不可用,或者通知接口持续返回非 success 内容,都可能导致订单状态更新延迟。
可以定时扫描创建时间超过一定阈值、状态仍为 PAYING 的订单,然后调用 alipay.trade.query:
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setOutTradeNo(orderNo);
request.setBizModel(model);
AlipayTradeQueryResponse response = alipayClient.execute(request);
if (response.isSuccess()
&& ("TRADE_SUCCESS".equals(response.getTradeStatus())
|| "TRADE_FINISHED".equals(response.getTradeStatus()))) {
orderService.markPaidIfUnpaid(
orderNo,
response.getTradeNo()
);
}
查询成功后,仍要核对订单号、支付金额和商户身份,并复用异步通知的幂等更新逻辑。
补偿任务的执行频率和订单超时时间,应结合业务量、支付宝接口限制和项目容错要求确定。不要对每个订单进行高频、无限次查询。
注意事项
- 不要在
return_url中执行唯一一次的支付确认、发货或充值。 - 不要相信前端提交的”支付成功”标志,也不要只根据跳转参数修改订单状态。
notify_url不应要求登录,也不应执行页面跳转或输出 HTML。处理成功后直接返回success。- 验签前不要自行拼接或修改支付宝传入的参数。
- 异步通知接口应记录必要的请求、验签和处理日志,同时避免泄露私钥和完整的敏感数据。
- 如果通知处理包含耗时操作,应先可靠落库并返回
success,再通过事务消息或任务队列处理后续业务。 - 浏览器的
beforeunload、unload或前端 AJAX 无法解决这个问题,因为它们也可能随页面关闭而中断。
完整的支付处理流程应以 notify_url 异步通知为主,以主动查询作为补偿。return_url 只用于展示结果,所有订单更新都必须保持幂等。
备注:内容仅供参考。
