PAYATHON 2026

APP 内 WebView 使用支付宝 H5 支付成功后不跳转 return_url

支付老李

结论

return_url 只是支付宝 H5 收银台结束后用于浏览器同步跳转的地址。它不是支付结果回调,也不能保证用户完成支付后自动从支付宝 App 返回原来的 WKWebView。

在 iOS 中,支付过程通常会从 App 内的 WebView 切换到支付宝 App。此时,原 WebView 的导航链已经中断。如果应用没有配置回跳入口,也没有处理支付宝相关的 URL Scheme,支付结束后页面可能继续停留在支付宝。return_url 不会加载,WebView 的历史记录也无法让用户回到原页面。

建议按以下方式处理:

  1. App 内支付优先使用支付宝原生支付 SDK。
  2. 必须使用 H5 支付时,由 iOS 端处理外部跳转和 App 回跳,并在 App 恢复后主动查询订单状态。
  3. 服务端必须以 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,都不能替代服务端验签。

排查建议

可以按照以下顺序定位问题:

  1. 确认支付宝支付请求中实际提交了 return_url,并检查编码和签名是否正确。
  2. 确认 return_url 是公网可访问的 HTTPS 地址。
  3. 记录 WKWebView 的每一次导航 URL,检查是否出现 alipays://、自定义 Scheme 或中间重定向失败。
  4. 检查 WKNavigationDelegate 是否错误拦截了 return_url。
  5. 检查 App 是否注册并处理了自己的回跳 Scheme 或 Universal Link。
  6. 检查支付后 App 是否重新进入前台,以及进入前台后是否主动查单。
  7. 检查 notify_url 是否收到通知、验签是否成功、订单状态是否已经更新。
  8. 分别测试已安装和未安装支付宝 App 的情况,因为两种情况下的跳转链路可能不同。

return_url 只能改善支付流程结束后的页面体验,无法保证用户支付成功后一定返回 App。App 内支付应优先使用原生 SDK。如果继续使用 H5 支付,则需要由 iOS 处理外部跳转和回跳,最终支付状态仍由服务端异步通知或主动查单确认。

备注:内容仅供参考。