如何在 Xamarin 跨平台应用中集成 payment gateway?
结论
Xamarin 跨平台应用可以接入 payment gateway,但银行卡信息、商户密钥和支付签名不应交给 XAML 页面处理。
XAML 负责展示支付入口和支付状态。实际支付一般按以下流程完成:
- 应用向自己的后端请求创建订单和支付会话。
- 后端调用 payment gateway 的服务端 API。
- 应用通过系统浏览器或支付服务商提供的原生 SDK 打开支付页面。
- 支付完成后,通过 Universal Links、Android App Links 或自定义 URL Scheme 返回应用。
- 后端根据 webhook 或支付网关的查询结果确认支付状态。
如果使用托管支付页面,建议优先通过系统浏览器打开。只有支付服务商明确支持嵌入式 WebView,才考虑在 XAML 页面中使用 WebView。
推荐的支付流程
Xamarin App
│
│ 创建订单
▼
应用后端
│
│ 使用商户密钥调用支付网关
▼
Payment Gateway
│
│ 返回 checkout URL / session ID
▼
Xamarin App 打开托管支付页
│
│ 用户完成支付
▼
Payment Gateway 通知应用后端(webhook)
│
│ 后端验证签名并更新订单
▼
Xamarin App 查询并显示最终结果
在这套设计中,客户端只保存订单编号、支付会话标识和托管页面 URL,不保存 gateway secret,也不自行判断支付是否成功。
从 XAML 页面发起支付
XAML 页面可以直接展示支付按钮:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Views.CheckoutPage">
<StackLayout Padding="24" Spacing="16">
<Label Text="订单金额:¥99.00"
FontSize="20" />
<Button Text="立即支付"
Clicked="OnPayClicked" />
<ActivityIndicator
x:Name="PaymentIndicator"
IsVisible="False"
IsRunning="False" />
<Label x:Name="PaymentStatusLabel" />
</StackLayout>
</ContentPage>
用户点击按钮后,应用先向后端申请支付会话,再打开后端返回的托管支付地址:
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Xamarin.Essentials;
using Xamarin.Forms;
public partial class CheckoutPage : ContentPage
{
private readonly HttpClient _httpClient = new HttpClient
{
BaseAddress = new Uri("https://api.example.com/")
};
public CheckoutPage()
{
InitializeComponent();
}
private async void OnPayClicked(object sender, EventArgs e)
{
try
{
SetLoading(true);
var request = new
{
orderId = "ORDER-12345"
};
using var content = new StringContent(
JsonSerializer.Serialize(request),
Encoding.UTF8,
"application/json");
using var response = await _httpClient.PostAsync(
"payments/checkout-sessions",
content);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var session = JsonSerializer.Deserialize<CheckoutSession>(json);
if (session == null ||
!Uri.TryCreate(session.CheckoutUrl, UriKind.Absolute, out var checkoutUri))
{
throw new InvalidOperationException("后端未返回有效的支付地址。");
}
await Browser.OpenAsync(
checkoutUri,
BrowserLaunchMode.SystemPreferred);
PaymentStatusLabel.Text = "请在支付页面完成付款";
}
catch (Exception)
{
PaymentStatusLabel.Text = "暂时无法发起支付,请稍后重试";
}
finally
{
SetLoading(false);
}
}
private void SetLoading(bool isLoading)
{
PaymentIndicator.IsVisible = isLoading;
PaymentIndicator.IsRunning = isLoading;
}
private sealed class CheckoutSession
{
public string CheckoutUrl { get; set; }
}
}
示例中的接口地址和返回模型仅用于说明结构,实际使用时要根据后端和 payment gateway API 进行调整。生产环境还应在请求中附带登录凭据,订单金额也不能硬编码在客户端。
后端负责创建支付会话
后端应从自己的订单数据中读取金额,不能直接相信客户端提交的金额。下面是一个简化的 ASP.NET Core 示例:
[ApiController]
[Route("payments")]
public class PaymentsController : ControllerBase
{
private readonly IOrderService _orders;
private readonly IPaymentGateway _gateway;
public PaymentsController(
IOrderService orders,
IPaymentGateway gateway)
{
_orders = orders;
_gateway = gateway;
}
[HttpPost("checkout-sessions")]
public async Task<IActionResult> CreateCheckoutSession(
[FromBody] CreateCheckoutRequest request)
{
var order = await _orders.GetPendingOrderAsync(
request.OrderId,
User);
if (order == null)
{
return NotFound();
}
var session = await _gateway.CreateCheckoutSessionAsync(
orderId: order.Id,
amount: order.Amount,
currency: order.Currency,
successUrl: "https://example.com/payment/return",
cancelUrl: "https://example.com/payment/cancel");
return Ok(new
{
checkoutUrl = session.CheckoutUrl
});
}
}
public sealed class CreateCheckoutRequest
{
public string OrderId { get; set; }
}
IPaymentGateway 的实现方式取决于所选的支付服务商。API secret、签名私钥和 webhook secret 必须保存在后端或安全的密钥管理系统中,不能打包到 Xamarin 应用里。
处理支付完成后的返回
支付网关通常会将浏览器重定向到一个 HTTPS 地址。可以把该地址配置为 Android App Link 或 iOS Universal Link,由系统重新打开应用。
应用收到链接后,可以提取订单标识,再向后端查询支付状态:
protected override void OnAppLinkRequestReceived(Uri uri)
{
base.OnAppLinkRequestReceived(uri);
if (uri.Host != "payment-return")
{
return;
}
var orderId = GetQueryParameter(uri, "orderId");
if (!string.IsNullOrWhiteSpace(orderId))
{
MessagingCenter.Send(
this,
"PaymentReturned",
orderId);
}
}
private static string GetQueryParameter(Uri uri, string name)
{
var query = uri.Query.TrimStart('?').Split('&');
foreach (var part in query)
{
var pair = part.Split(new[] { '=' }, 2);
if (pair.Length == 2 &&
string.Equals(
Uri.UnescapeDataString(pair[0]),
name,
StringComparison.OrdinalIgnoreCase))
{
return Uri.UnescapeDataString(pair[1]);
}
}
return null;
}
页面收到返回事件后,再向后端查询:
protected override void OnAppearing()
{
base.OnAppearing();
MessagingCenter.Subscribe<App, string>(
this,
"PaymentReturned",
async (_, orderId) => await RefreshPaymentStatusAsync(orderId));
}
protected override void OnDisappearing()
{
MessagingCenter.Unsubscribe<App, string>(
this,
"PaymentReturned");
base.OnDisappearing();
}
private async Task RefreshPaymentStatusAsync(string orderId)
{
var response = await _httpClient.GetAsync(
$"payments/orders/{Uri.EscapeDataString(orderId)}/status");
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<PaymentStatusResult>(json);
PaymentStatusLabel.Text = result?.Status switch
{
"paid" => "支付成功",
"failed" => "支付失败",
_ => "支付结果确认中"
};
}
private sealed class PaymentStatusResult
{
public string Status { get; set; }
}
Android App Links、iOS Universal Links 以及 Xamarin 中的链接分发配置,会因 Xamarin.Forms 版本和项目结构而异。需要根据目标平台配置相应的 intent filter、associated domains 和域名验证文件。
为什么不能只依赖跳转结果
支付页面返回 success,只能说明浏览器完成了跳转,不能证明资金已经到账。用户可能伪造回调 URL,支付也可能还在异步处理中。
后端至少应通过以下一种方式确认最终结果:
- 验证 payment gateway 发来的 webhook 签名。
- 使用后端凭据调用 gateway API,查询交易状态。
- 核对订单号、金额、币种、商户账户和交易标识。
- 对 webhook 进行幂等处理,避免重复通知导致重复发货。
客户端收到返回链接后,最多只能显示“正在确认支付”,不能直接把订单状态改为已支付。
是否可以使用 XAML WebView
技术上可以这样做:
<WebView
x:Name="PaymentWebView"
Navigating="OnPaymentNavigating" />
PaymentWebView.Source = checkoutUrl;
private async void OnPaymentNavigating(
object sender,
WebNavigatingEventArgs e)
{
if (e.Url.StartsWith(
"https://example.com/payment/return",
StringComparison.OrdinalIgnoreCase))
{
e.Cancel = true;
await RefreshPaymentStatusAsync("ORDER-12345");
}
}
不过,WebView 未必适合真实的支付环境。部分 gateway、银行验证页、OAuth 流程或 3-D Secure 页面会限制嵌入式浏览器,也可能依赖新窗口、外部银行应用、Cookie 或跨域跳转。使用前必须确认服务商文档明确支持 Xamarin WebView 或一般的 embedded WebView。
不要向 WebView 注入 JavaScript 来获取卡号、CVV、密码或验证码,也不能绕过证书校验。如果服务商要求使用原生体验,应接入其 Android/iOS SDK,并通过 Xamarin binding project 或平台服务抽象进行统一调用。
还需要注意的事项
- 全程使用 HTTPS,并由后端验证支付服务商的证书和 webhook 签名。
- 商户 secret、私钥和 webhook secret 只能保存在服务端。
- 金额、币种和商品信息应以服务端的订单记录为准。
- 创建支付会话、处理回调和接收 webhook 时都要设计幂等机制。
- 不要在日志、异常信息或分析平台中记录银行卡信息、token、验证码或完整回调参数。
- 在沙箱环境中测试支付成功、失败、取消、超时、重复回调、网络中断和应用被系统终止等情况。
- 如果销售的是应用内数字内容或数字功能,需要先核对 Apple App Store 和 Google Play 当前的应用内购买政策。这类交易不一定允许直接使用第三方 payment gateway。
- Xamarin 已经属于较旧的技术栈。仍在维护的项目可以按上述架构接入支付;新项目或准备升级的项目可以评估迁移到 .NET MAUI,但完成支付接入并不以迁移为前提。
备注:内容仅供参考。
