H5 支付完成后如何配置“完成”页的跳转 URL?
支付阿杰
结论
H5 支付完成后的“完成”页地址,需要在调用支付宝手机网站支付接口 alipay.trade.wap.pay 时通过 return_url 设置。
return_url 仅用于支付完成后的浏览器同步跳转,不能作为判断支付是否成功的唯一依据。订单的最终状态应以支付宝发送到 notify_url 的异步通知为准,也可以由服务端主动调用查单接口确认。
配置方式
创建支付请求时,需要同时设置以下两个地址:
return_url:用户完成支付后跳转的页面地址。notify_url:支付宝向商家服务端发送支付结果的异步通知地址。
以 Java SDK 为例:
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl,
appId,
merchantPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setReturnUrl("https://www.example.com/pay/complete");
request.setNotifyUrl("https://api.example.com/alipay/notify");
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo("ORDER_202609130001");
model.setTotalAmount("99.00");
model.setSubject("订单支付");
model.setProductCode("QUICK_WAP_WAY");
request.setBizModel(model);
// pageExecute 返回可直接输出给浏览器的支付页面或表单内容
AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);
if (response.isSuccess()) {
String paymentHtml = response.getBody();
// 将 paymentHtml 输出给浏览器
}
较早版本的 SDK 可能通过业务参数字符串传递支付参数。不过,return_url 和 notify_url 通常仍是请求级公共参数,不应放入 biz_content:
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setReturnUrl("https://www.example.com/pay/complete");
request.setNotifyUrl("https://api.example.com/alipay/notify");
request.setBizContent("""
{
"out_trade_no": "ORDER_202609130001",
"total_amount": "99.00",
"subject": "订单支付",
"product_code": "QUICK_WAP_WAY"
}
""");
String paymentHtml = alipayClient.pageExecute(request).getBody();
支付宝 SDK 的具体类名和方法签名可能因版本而异,请以项目实际使用的版本为准。
“完成”页应如何处理
用户通过 return_url 返回后,完成页不应直接显示“支付成功”。可以按以下流程确认支付结果:
- 从跳转参数中取得商户订单号等信息。
- 由服务端验签,不直接信任浏览器传入的参数。
- 根据商户订单号查询本地订单状态。
- 如果尚未收到异步通知,由服务端调用支付宝查单接口确认。
- 按实际状态显示“支付成功”“处理中”或“支付失败”。
例如,完成页可以调用商家自己的订单查询接口:
async function loadPaymentResult(outTradeNo) {
const response = await fetch(
`/api/orders/${encodeURIComponent(outTradeNo)}/payment-status`
);
const result = await response.json();
if (result.status === "PAID") {
window.location.replace(`/orders/${encodeURIComponent(outTradeNo)}/success`);
} else if (result.status === "PENDING") {
// 显示“支付结果确认中”,随后轮询或允许用户手动刷新
} else {
// 显示未支付或支付失败
}
}
为什么不能只依赖 return_url
以下情况都可能导致同步跳转没有发生:
- 用户支付后直接关闭支付宝或浏览器。
- 网络中断。
- 页面被系统回收。
- 用户手动返回其他页面。
- 浏览器拦截了跳转。
同样,用户能够访问完成页,也不代表订单一定支付成功。发货、充值、开通权益等业务操作,必须由服务端根据可靠的支付结果触发。
异步通知可以按下面的基本结构处理:
@PostMapping("/alipay/notify")
public String handleAlipayNotify(HttpServletRequest request) {
Map<String, String> params = extractParameters(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 appId = params.get("app_id");
// 还应核对 app_id、订单金额、商户订单号、收款方等信息。
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
// 以幂等方式更新订单,避免重复通知导致重复发货或重复入账。
markOrderAsPaid(outTradeNo, totalAmount, appId);
}
return "success";
}
注意事项
return_url和notify_url应是外网可以访问的完整 URL,生产环境建议使用 HTTPS。notify_url必须允许支付宝服务器直接访问,不能依赖登录状态,也不能使用仅限内网访问的地址。- 两者用途不同:
return_url面向用户浏览器,notify_url面向支付宝服务器。 - 支付结果通知可能重复发送,因此订单更新必须具备幂等性。
- 即使验签通过,也要核对
app_id、out_trade_no、total_amount、交易状态和收款方信息,不能只检查trade_status。 - 不要根据前端传入的订单号和支付状态直接更新订单。
- 二维码只负责引导用户进入商家订单页。“完成”页地址不配置在二维码中,而是在订单页发起
alipay.trade.wap.pay请求时设置。 - 如果实际调用的不是
alipay.trade.wap.pay,而是当面付、预创建支付或其他产品,返回机制可能有所不同,需要按照对应支付产品提供的接口能力处理。
备注:内容仅供参考。
