支付宝支付成功后如何跳转到自定义H5页面?
结论
支付宝支付成功后,如果需要跳转到自定义 H5 页面,应在创建支付请求时设置同步跳转地址 return_url。用户完成支付并离开支付宝收银台后,支付宝会将浏览器重定向到这个地址。
还需要配置异步通知地址 notify_url,让服务端接收支付结果并更新订单。return_url 只用于页面跳转,不能单独作为支付成功的判断依据。
实现流程
通常的处理流程如下:
- 服务端创建支付宝支付订单。
- 在支付请求中设置
return_url和notify_url。 - 前端跳转到支付宝返回的支付页面。
- 用户完成支付后,浏览器跳转到
return_url。 - H5 结果页携带商户订单号,向商户服务端查询订单状态。
- 服务端通过异步通知或支付宝订单查询接口确认实际支付结果。
相关参数的作用:
return_url:同步跳转地址,用于将用户带回自定义 H5 页面。notify_url:异步通知地址,用于可靠地接收支付结果。out_trade_no:商户订单号,建议由服务端生成,并保证唯一。
创建支付请求
下面以支付宝手机网站支付 alipay.trade.wap.pay 和 Java SDK 为例:
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
appId,
merchantPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
// 支付宝服务器异步通知地址
request.setNotifyUrl("https://api.example.com/payment/alipay/notify");
// 用户完成支付后的同步跳转地址
request.setReturnUrl(
"https://m.example.com/payment/result?out_trade_no=ORDER_202609130001"
);
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo("ORDER_202609130001");
model.setTotalAmount("99.00");
model.setSubject("会员服务");
model.setProductCode("QUICK_WAP_WAY");
request.setBizModel(model);
AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);
if (!response.isSuccess()) {
throw new IllegalStateException(
"创建支付宝支付失败:" + response.getSubMsg()
);
}
// response.getBody() 通常是自动提交到支付宝收银台的 HTML 表单
String paymentForm = response.getBody();
服务端可以将 paymentForm 直接返回给浏览器:
@GetMapping(value = "/payment/alipay", produces = "text/html;charset=UTF-8")
@ResponseBody
public String createAlipayPayment() throws AlipayApiException {
return paymentService.createWapPayment();
}
浏览器收到自动提交的表单后,会进入支付宝收银台。支付流程结束后,支付宝会尝试跳转到 return_url。
支付宝 SDK 的不同版本可能使用略有差异的请求对象或模型类名称,具体应以项目实际使用的 SDK 接口为准。需要配置的核心参数仍是 return_url 和 notify_url。
H5 结果页不要直接认定支付成功
用户进入 return_url,不代表订单已经支付成功。同步跳转既可能被伪造,也可能早于异步通知到达。
结果页应调用商户自己的订单查询接口:
const params = new URLSearchParams(window.location.search);
const outTradeNo = params.get("out_trade_no");
async function loadPaymentResult() {
if (!outTradeNo) {
showResult("无法识别订单");
return;
}
const response = await fetch(
`/api/orders/${encodeURIComponent(outTradeNo)}/payment-status`,
{
credentials: "include"
}
);
if (!response.ok) {
showResult("订单状态查询失败,请稍后重试");
return;
}
const result = await response.json();
switch (result.status) {
case "PAID":
showResult("支付成功");
break;
case "PENDING":
showResult("支付结果确认中");
break;
case "CLOSED":
showResult("订单已关闭");
break;
default:
showResult("尚未支付");
}
}
订单查询接口应从数据库读取订单状态。必要时,服务端可以再调用支付宝的交易查询接口。前端不能根据 URL 中的 trade_status、金额或其他参数自行修改订单状态。
处理异步通知
支付宝会向 notify_url 发送服务端通知。收到通知后,至少要完成以下处理:
- 验证支付宝签名。
- 检查
app_id、商户订单号和支付金额。 - 确认交易状态符合业务要求。
- 以幂等方式更新订单。
- 处理成功后返回纯文本
success。
示例:
@PostMapping(
value = "/payment/alipay/notify",
produces = "text/plain;charset=UTF-8"
)
@ResponseBody
public String handleNotify(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((key, values) -> {
params.put(key, String.join(",", values));
});
try {
boolean valid = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!valid) {
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");
String notifiedAppId = params.get("app_id");
Order order = orderService.findByOutTradeNo(outTradeNo);
if (order == null) {
return "failure";
}
if (!appId.equals(notifiedAppId)) {
return "failure";
}
if (new BigDecimal(totalAmount).compareTo(order.getPayAmount()) != 0) {
return "failure";
}
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
orderService.markPaidIfNecessary(outTradeNo, tradeNo);
}
return "success";
} catch (Exception e) {
return "failure";
}
}
这段示例省略了日志、事务、卖家身份核验等项目代码。实际业务还应核对通知中的收款方信息,并保证重复收到同一通知时,不会重复发货、充值或记账。
同步跳转页是否需要验签
如果 return_url 中带有支付宝返回的参数,可以先按照支付宝的签名规则验签。但即使验签成功,也不能只凭同步返回更新支付状态。
同步返回适合用于:
- 展示支付结果页;
- 获取商户订单号;
- 提示用户支付结果正在确认;
- 发起一次服务端订单状态查询。
最终订单状态应以服务端异步通知为主,必要时通过主动查询进行补偿。
App 支付需要单独处理
如果使用支付宝 App 支付,而非手机网站支付,支付完成后通常会回到商户 App。客户端可以在 SDK 的支付回调中判断返回状态,然后由 App 打开目标 H5 页面,例如加载 WebView 或跳转到浏览器。
伪代码如下:
payWithAlipay(orderString, async result => {
// resultStatus 只能用于客户端展示,最终状态仍需向服务端查询
const order = await queryOrderFromServer(outTradeNo);
if (order.status === "PAID") {
openWebView(
`https://m.example.com/payment/result?out_trade_no=${encodeURIComponent(outTradeNo)}`
);
}
});
App 支付不能直接套用 Web 支付场景中的 return_url 作为通用跳转方案。具体的回调方式取决于所使用的客户端 SDK。
注意事项
return_url和notify_url应使用公网可访问的 HTTPS 地址。notify_url不能要求用户登录,也不能依赖 Cookie 或浏览器会话。- 用户支付后可能直接关闭支付宝,不能假设
return_url一定会被访问。 - 异步通知和同步跳转的到达顺序不固定。结果页应允许短时间显示”确认中”,并进行有限次数的轮询。
- 支付成功状态、金额和任意目标地址都不能直接交给前端决定。
- 如果
return_url支持动态跳转,应通过服务端白名单限制目标地址,避免出现开放重定向漏洞。 - 商户订单号可以放在跳转地址中,但 URL 不应携带身份证号、手机号、访问令牌等敏感数据。
- 沙箱环境与正式环境使用不同的网关、应用配置和密钥,切换环境时需要分别配置。
备注:内容仅供参考。
