网页应用支付成功后如何从支付宝付款页跳转至成功页面?
结论
发起支付宝网页支付时设置 return_url,用户完成支付后,支付宝收银台会将浏览器跳转到这个地址。
return_url 只负责页面跳转,不能作为支付成功的最终依据。服务端应通过 notify_url 接收支付宝的异步通知,验签后再确认订单状态。如果用户返回成功页时异步通知尚未到达,页面应向服务端查询订单状态。
发起支付时设置跳转地址
以支付宝电脑网站支付接口 alipay.trade.page.pay 为例,支付请求中可以同时设置两个地址:
return_url:用户完成支付后,浏览器跳转到的地址。notify_url:支付宝服务器发送异步支付结果的地址。
Java 示例:
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl,
appId,
privateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl("https://www.example.com/payment/return");
request.setNotifyUrl("https://www.example.com/api/alipay/notify");
request.setBizContent("""
{
"out_trade_no": "ORDER_202609090001",
"total_amount": "99.00",
"subject": "网站订单支付",
"product_code": "FAST_INSTANT_TRADE_PAY"
}
""");
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
if (response.isSuccess()) {
String paymentForm = response.getBody();
// 将 paymentForm 返回给浏览器,由浏览器提交或直接渲染
} else {
// 记录支付宝返回的错误码和错误信息
}
return_url 和 notify_url 都应使用外网可以访问的 HTTPS 地址。不同版本的 Java SDK 在类名和调用方式上可能略有差异,但这两个地址的用途不变。
正确处理支付结果
1. 通过异步通知更新订单
支付宝会向 notify_url 发送支付结果。服务端收到通知后,需要依次处理:
- 获取支付宝发送的全部参数。
- 使用支付宝公钥验证签名。
- 校验
app_id、商户订单号、支付金额、卖家身份等关键字段。 - 检查交易状态是否符合业务要求。
- 以幂等方式把本地订单更新为已支付。
- 处理成功后返回纯文本
success。
示意代码:
@PostMapping("/api/alipay/notify")
@ResponseBody
public String handleNotify(HttpServletRequest request) throws Exception {
Map<String, String> params = extractAlipayParams(request);
boolean valid = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!valid) {
return "failure";
}
String outTradeNo = params.get("out_trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
String appIdFromNotify = params.get("app_id");
// 还应校验 app_id、金额、商户身份等是否与本地订单一致
if (!appId.equals(appIdFromNotify)) {
return "failure";
}
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
paymentService.markPaidIdempotently(outTradeNo, totalAmount);
return "success";
}
return "failure";
}
extractAlipayParams 需要根据实际使用的 Web 框架,读取请求中的全部参数。验签前不要自行删除、拼接或修改这些字段。
支付宝可能重复发送异步通知,因此更新订单时必须保证幂等。例如,订单已经处于 已支付 状态,后续收到相同通知时,不应再次发货、增加余额或生成权益。
2. 同步跳转只用于页面展示
用户完成支付后,浏览器会进入类似下面的地址:
https://www.example.com/payment/return?out_trade_no=...&trade_no=...&sign=...
同步跳转中的参数也应验签。不过,即使验签通过,也不建议直接把订单改为已支付。更稳妥的方式是使用商户订单号查询本地订单状态:
@GetMapping("/payment/return")
public String paymentReturn(
@RequestParam("out_trade_no") String outTradeNo,
Model model) {
PaymentOrder order = paymentService.findByOrderNo(outTradeNo);
model.addAttribute("orderNo", outTradeNo);
model.addAttribute("status", order.getStatus());
return "payment-result";
}
如果异步通知还未处理完成,页面可以先显示 支付结果确认中,再轮询业务服务器:
async function waitForPayment(orderNo) {
const timer = setInterval(async () => {
const response = await fetch(
`/api/orders/${encodeURIComponent(orderNo)}/payment-status`
);
const result = await response.json();
if (result.status === "PAID") {
clearInterval(timer);
window.location.replace(
`/payment/success?orderNo=${encodeURIComponent(orderNo)}`
);
}
if (result.status === "CLOSED" || result.status === "FAILED") {
clearInterval(timer);
}
}, 2000);
}
如果服务端长时间没有收到异步通知,可以调用支付宝交易查询接口核实交易状态。前端不能直接调用需要商户私钥签名的接口,也不能把商户私钥写入 JavaScript。
推荐的完整流程
网站创建本地订单
↓
服务端调用 alipay.trade.page.pay,并设置 return_url、notify_url
↓
浏览器进入支付宝收银台
↓
用户完成支付
├─ 支付宝服务器调用 notify_url
│ ↓
│ 服务端验签、核对金额并更新订单
│
└─ 浏览器跳转到 return_url
↓
页面查询本地订单状态
↓
展示支付成功或“确认中”
常见问题
设置了 return_url,为什么没有跳转?
浏览器跳转不是绝对可靠的。用户可能在支付后直接关闭页面,网络也可能中断,支付宝收银台的实际交互还可能受到终端环境影响。因此,业务流程不能假定用户一定会返回网站。
可以检查以下项目:
return_url是否为完整的外网地址。- 该地址能否通过 HTTPS 正常访问。
- 发起支付时是否确实将该地址设置到了请求对象中。
- 页面是否存在登录拦截、循环重定向或跨域跳转限制。
- 当前支付产品是否支持这种返回方式,参数是否符合接入文档。
可以只使用 return_url 吗?
不建议。只处理同步跳转,会漏掉用户关闭页面等情况,也容易把页面参数当成可信的支付凭证。至少应配置 notify_url,并以服务端验签后的异步通知或主动查询结果为准。
可以直接跳到固定的“支付成功”页面吗?
可以跳转,但不能因为这个页面被访问,就认定付款已经成功。成功页应携带商户订单号,并向服务端查询订单的真实状态。
订单号只能用于定位订单,不能代替身份校验。查询接口还需要确认当前用户是否有权查看该订单。
notify_url 返回 HTML 可以吗?
不应返回成功页面。异步通知是支付宝服务器与商户服务器之间的通信,处理成功后通常应返回以下纯文本:
success
浏览器页面由 return_url 负责展示,不要混用这两个地址。
备注:内容仅供参考。
