如何在 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 参数可能被修改,也可能因页面刷新、历史记录或重复跳转而多次触发。
更可靠的处理流程如下:
- 从返回 URL 中取得订单号或交易号。
- 将订单号发送到自己的服务端。
- 由服务端调用支付网关的订单查询接口,或者检查已经验签的服务端异步通知。
- 服务端核对金额、币种、订单号和支付状态后,再把最终结果返回给应用。
支付网关的密钥、签名密钥和服务端凭据都不应放在 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,准确识别回调地址并停止导航。最终的支付结果仍需由服务端向支付网关核验。
备注:内容仅供参考。
