PAYATHON 2026

如何在 Xamarin.Forms 中获取 WebView 支付返回 URL?

支付小周

结论

在 Xamarin.Forms 中,监听 WebView.Navigating 事件后,可以通过 WebNavigatingEventArgs.Url 获取 WebView 即将访问的 URL。识别出支付网关配置的返回地址后,解析其中的状态码,再根据支付结果跳转页面或提示用户。

支付回调一般放在 Navigating 中处理,而不是 Navigated。这样可以在返回页面加载前设置 e.Cancel = true,停止后续导航。

实现步骤

1. 配置固定的支付返回地址

在支付网关后台或创建支付订单时,先配置一个明确的返回地址,例如:

https://example.com/payment/callback

支付完成后,网关可能跳转到:

https://example.com/payment/callback?status=success&transactionId=123456

参数名称取决于支付网关,不能假定状态参数一定叫 status。请以网关的实际文档和返回数据为准。

2. 监听 WebView 的 Navigating 事件

可以在 XAML 页面中这样定义:

<WebView
    x:Name="PaymentWebView"
    Navigating="PaymentWebView_Navigating"
    Navigated="PaymentWebView_Navigated"
    VerticalOptions="FillAndExpand"
    HorizontalOptions="FillAndExpand" />

然后打开支付页面:

PaymentWebView.Source = paymentUrl;

3. 识别并处理返回 URL

using System;
using System.Collections.Generic;
using Xamarin.Forms;

private bool _paymentResultHandled;

private async void PaymentWebView_Navigating(
    object sender,
    WebNavigatingEventArgs e)
{
    if (_paymentResultHandled || string.IsNullOrWhiteSpace(e.Url))
        return;

    if (!Uri.TryCreate(e.Url, UriKind.Absolute, out Uri uri))
        return;

    bool isPaymentCallback =
        uri.Scheme == Uri.UriSchemeHttps &&
        string.Equals(uri.Host, "example.com",
            StringComparison.OrdinalIgnoreCase) &&
        string.Equals(uri.AbsolutePath, "/payment/callback",
            StringComparison.OrdinalIgnoreCase);

    if (!isPaymentCallback)
        return;

    // 阻止 WebView 继续打开回调页面
    e.Cancel = true;
    _paymentResultHandled = true;

    IDictionary<string, string> parameters = ParseQueryString(uri.Query);

    parameters.TryGetValue("status", out string status);
    parameters.TryGetValue("transactionId", out string transactionId);

    switch (status?.ToLowerInvariant())
    {
        case "success":
            await DisplayAlert(
                "支付成功",
                $"交易编号:{transactionId}",
                "确定");
            break;

        case "failed":
        case "failure":
            await DisplayAlert("支付失败", "付款未完成,请重试。", "确定");
            break;

        case "cancelled":
        case "canceled":
            await DisplayAlert("支付已取消", "用户取消了付款。", "确定");
            break;

        default:
            await DisplayAlert(
                "支付状态未知",
                "暂时无法确认支付结果,请稍后查询订单状态。",
                "确定");
            break;
    }
}

查询参数可以使用下面的方法解析:

private static IDictionary<string, string> ParseQueryString(string query)
{
    var result = new Dictionary<string, string>(
        StringComparer.OrdinalIgnoreCase);

    if (string.IsNullOrWhiteSpace(query))
        return result;

    string value = query.TrimStart('?');

    foreach (string item in value.Split('&'))
    {
        if (string.IsNullOrWhiteSpace(item))
            continue;

        string[] parts = item.Split(new[] { '=' }, 2);

        string key = Uri.UnescapeDataString(parts[0].Replace("+", " "));
        string parameterValue = parts.Length > 1
            ? Uri.UnescapeDataString(parts[1].Replace("+", " "))
            : string.Empty;

        result[key] = parameterValue;
    }

    return result;
}

Navigated 事件也可以读取最终加载的 URL:

private void PaymentWebView_Navigated(
    object sender,
    WebNavigatedEventArgs e)
{
    string loadedUrl = e.Url;

    // 可在这里记录页面是否加载成功,
    // 但支付回调一般优先在 Navigating 中拦截。
}

不要只依赖 URL 中的支付状态

回调 URL 中的 status=success 只能用于控制前端流程,不能作为最终到账依据。URL 参数可能被修改,也可能因页面刷新、历史记录或重复跳转而多次触发。

更可靠的处理流程如下:

  1. 从返回 URL 中取得订单号或交易号。
  2. 将订单号发送到自己的服务端。
  3. 由服务端调用支付网关的订单查询接口,或者检查已经验签的服务端异步通知。
  4. 服务端核对金额、币种、订单号和支付状态后,再把最终结果返回给应用。

支付网关的密钥、签名密钥和服务端凭据都不应放在 Xamarin.Forms 客户端中。

常见注意事项

精确匹配回调地址

不要使用下面这种宽松的判断方式:

if (e.Url.Contains("success"))

普通页面的路径或参数同样可能包含这个字符串。识别回调地址时,至少要验证以下内容:

  • URL Scheme,例如 https
  • Host,例如 example.com
  • Path,例如 /payment/callback

如果支付网关支持,应配置 HTTPS 返回地址。

防止重复处理

WebView 可能经历多次重定向,回调页面也可能重复加载。示例中的 _paymentResultHandled 可以避免重复提示、重复提交订单查询或重复跳转。

开始新的支付流程前,需要将它重置为 false:

_paymentResultHandled = false;
PaymentWebView.Source = paymentUrl;

返回信息不一定在查询字符串中

有些支付网关可能采用以下方式返回信息:

  • 使用 URL Fragment,例如 #status=success
  • 通过 HTTP POST 提交返回数据
  • 在页面内部执行 JavaScript,不触发顶层页面导航
  • 只发送服务端异步通知

Navigating 可以直接捕获常规 URL 跳转,但不一定能读取 POST 请求正文,也无法捕获页面内部的所有网络请求。在这些情况下,应按照支付网关支持的方式使用服务端回调或订单查询接口。如果明确了解页面协议,也可以通过 JavaScript 与原生代码通信。

处理加载失败和用户退出

可以在 Navigated 事件中检查 e.Result。页面关闭时,应将结果视为“用户退出”或“状态待确认”,不要直接判定支付失败。支付可能已经完成,只是客户端没有成功收到跳转。

private void PaymentWebView_Navigated(
    object sender,
    WebNavigatedEventArgs e)
{
    if (e.Result != WebNavigationResult.Success)
    {
        // 页面加载失败不等于支付失败。
        // 必要时通过服务端查询订单状态。
    }
}

处理时,应在 WebView.Navigating 中读取 e.Url,准确识别回调地址并停止导航。最终的支付结果仍需由服务端向支付网关核验。

备注:内容仅供参考。