APP 内 WebView 使用支付宝 H5 支付成功后不跳转 return_url
结论
return_url 只是支付宝 H5 收银台结束后用于浏览器同步跳转的地址。它不是支付结果回调,也不能保证用户完成支付后自动从支付宝 App 返回原来的 WKWebView。
在 iOS 中,支付过程通常会从 App 内的 WebView 切换到支付宝 App。此时,原 WebView 的导航链已经中断。如果应用没有配置回跳入口,也没有处理支付宝相关的 URL Scheme,支付结束后页面可能继续停留在支付宝。return_url 不会加载,WebView 的历史记录也无法让用户回到原页面。
建议按以下方式处理:
- App 内支付优先使用支付宝原生支付 SDK。
- 必须使用 H5 支付时,由 iOS 端处理外部跳转和 App 回跳,并在 App 恢复后主动查询订单状态。
- 服务端必须以
notify_url异步通知或主动查单结果作为支付成功依据,不能依赖return_url。
为什么配置了 return_url 仍然不跳转
return_url 不是服务端支付通知
用户完成支付流程后,支付宝会尝试在当前浏览器上下文中跳转到 return_url 指定的页面。
但 return_url 无法做到以下事情:
- 可靠地通知商户服务端订单已经支付。
- 保证支付宝 App 一定返回原 App。
- 恢复已经中断的
WKWebView页面栈。 - 代替
notify_url。 - 保证用户关闭支付宝、网络异常或中途切换应用时仍会执行跳转。
因此,支付成功并不意味着 return_url 一定会被访问。
WebView 与支付宝 App 属于不同的导航上下文
典型流程如下:
App WKWebView
→ 支付宝 H5 收银台
→ alipays:// 或其他外部链接
→ 支付宝 App
iOS 打开支付宝 App 后,后续页面便不再运行于原来的 WKWebView。用户点击支付宝中的“完成”后能否返回,取决于支付产品能力、唤起方式,以及商户应用是否提供支付宝能够识别的回跳入口。WebView 的 goBack() 无法控制这个过程。
WebView 可能没有正确处理外部 Scheme
如果 WKWebView 将 alipays:// 等链接当成普通网页加载,通常会加载失败,或者无法正常唤起支付宝。应用需要通过导航代理识别非 HTTP/HTTPS Scheme,再将链接交给系统处理。
不同支付宝产品和接入版本生成的 Scheme 可能不同,不能只围绕某个固定字符串编写全部逻辑。具体判断应以实际接入文档和捕获到的跳转 URL 为准。
推荐方案:使用支付宝原生支付 SDK
如果支付发生在原生 App 内,支付宝原生支付 SDK 通常比在 WebView 中嵌入 H5 收银台更合适。原生 SDK 可以通过回调将控制权交还给 App,不需要依赖浏览器历史记录或 return_url。
以下是示意代码,具体类名和方法签名应以当前使用的支付宝 iOS SDK 文档为准:
AlipaySDK.defaultService().payOrder(
orderString,
fromScheme: "your-app-scheme"
) { result in
// 这里只能作为客户端展示依据之一
// 最终支付状态仍应由服务端异步通知或主动查单确认
print(result ?? [:])
}
应用还需要注册与 fromScheme 对应的 URL Scheme,并将回跳 URL 交给支付宝 SDK 处理。不同生命周期模式的处理位置有所区别。使用 Scene 生命周期时,可以在 SceneDelegate 中处理:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else {
return
}
AlipaySDK.defaultService().processOrder(
withPaymentResult: url
) { result in
print(result ?? [:])
}
}
如果项目仍使用 AppDelegate,则应在相应的 URL 打开回调中处理,同时确认应用已经完成 URL Scheme 配置。
必须使用 H5 支付时的处理方式
1. 正确配置支付请求
服务端生成支付请求时,需要分别配置:
return_url:支付流程结束后的前端落地页。notify_url:支付宝向商户服务端发送异步支付通知的地址。
return_url 建议使用公网可访问的 HTTPS 地址,例如:
https://m.example.com/payment/return?order_no=202609130001
还需要检查以下事项:
- 地址可以从公网访问,不依赖 App 内网环境。
- 登录态丢失后不会出现无限重定向。
- 参数已经正确编码。
- 参与签名的请求参数没有在签名完成后再次修改。
return_url配置在当前支付接口规定的位置。
具体参数结构和签名规则以实际使用的支付宝产品接口文档为准。
2. 在 WKWebView 中处理外部跳转
可以通过 WKNavigationDelegate 识别非网页 Scheme,再调用系统接口打开:
import UIKit
import WebKit
final class PaymentViewController: UIViewController, WKNavigationDelegate {
private let webView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
webView.navigationDelegate = self
view.addSubview(webView)
webView.frame = view.bounds
}
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
) {
guard let url = navigationAction.request.url,
let scheme = url.scheme?.lowercased() else {
decisionHandler(.allow)
return
}
if scheme == "http" || scheme == "https" {
decisionHandler(.allow)
return
}
decisionHandler(.cancel)
UIApplication.shared.open(url, options: [:]) { success in
if !success {
print("无法打开外部支付链接:\(url)")
}
}
}
}
生产环境不应无条件放行所有非 HTTP/HTTPS 地址。应建立允许列表,只处理业务需要的 Scheme,避免任意跳转到外部应用。
3. 为自己的 App 配置回跳入口
如果当前支付链路支持返回商户 App,需要为 App 配置自定义 URL Scheme 或 Universal Link,例如:
myapp://payment/result?order_no=202609130001
收到回跳后,不能直接根据 URL 参数将订单标记为成功。应用应提取订单号,再向商户服务端查询订单状态:
func handlePaymentCallback(_ url: URL) {
guard url.scheme == "myapp",
url.host == "payment" else {
return
}
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
let orderNo = components?
.queryItems?
.first(where: { $0.name == "order_no" })?
.value
guard let orderNo else {
return
}
queryOrderStatus(orderNo: orderNo)
}
其他应用也可以构造并调用自定义 Scheme。因此,它只能用于恢复页面和传递查询线索,不能作为可信的支付凭证。
4. App 回到前台后主动查单
如果支付成功后没有发生明确回跳,可以监听应用进入前台的事件,并向自己的服务端查询订单状态:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(applicationDidBecomeActive),
name: UIApplication.didBecomeActiveNotification,
object: nil
)
}
@objc private func applicationDidBecomeActive() {
guard let orderNo = currentOrderNo else {
return
}
queryOrderStatus(orderNo: orderNo)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
服务端返回确定结果后,再关闭支付 WebView 或跳转到结果页:
private func handleOrderStatus(_ status: OrderStatus) {
switch status {
case .paid:
navigationController?.popViewController(animated: true)
case .pending:
// 可以短时间轮询,或提示用户稍后刷新
break
case .closed, .failed:
// 展示失败或订单关闭状态
break
}
}
查单时应设置合理的频率、次数和超时时间,避免应用每次进入前台都无限轮询。
服务端应如何确认支付结果
可靠的订单处理流程如下:
客户端发起支付
→ 支付宝处理支付
→ 支付宝请求 notify_url
→ 商户服务端验签并核对订单
→ 更新订单状态
→ App 查询商户服务端订单状态
处理异步通知时,至少需要完成以下检查:
- 验证支付宝签名。
- 校验商户身份。
- 校验订单号。
- 校验订单金额和币种。
- 校验交易状态。
- 保证订单更新具有幂等性。
- 按接口要求返回通知处理结果。
客户端显示“支付成功”页面、收到 SDK 回调或访问了 return_url,都不能替代服务端验签。
排查建议
可以按照以下顺序定位问题:
- 确认支付宝支付请求中实际提交了
return_url,并检查编码和签名是否正确。 - 确认
return_url是公网可访问的 HTTPS 地址。 - 记录
WKWebView的每一次导航 URL,检查是否出现alipays://、自定义 Scheme 或中间重定向失败。 - 检查
WKNavigationDelegate是否错误拦截了return_url。 - 检查 App 是否注册并处理了自己的回跳 Scheme 或 Universal Link。
- 检查支付后 App 是否重新进入前台,以及进入前台后是否主动查单。
- 检查
notify_url是否收到通知、验签是否成功、订单状态是否已经更新。 - 分别测试已安装和未安装支付宝 App 的情况,因为两种情况下的跳转链路可能不同。
return_url 只能改善支付流程结束后的页面体验,无法保证用户支付成功后一定返回 App。App 内支付应优先使用原生 SDK。如果继续使用 H5 支付,则需要由 iOS 处理外部跳转和回跳,最终支付状态仍由服务端异步通知或主动查单确认。
备注:内容仅供参考。
