如何在 Xamarin.iOS 和 Xamarin.Android 中集成 Square In-App Payments
结论
可以集成,但 Square 没有为 Xamarin.iOS 或 Xamarin.Android 提供官方托管 SDK。你可以为 Square 原生 iOS 和 Android SDK 创建 Xamarin Binding Library,也可以编写一层原生桥接库,再通过 C# 调用。
如果采用 Square 的线上银行卡支付流程,就不能绕过 nonce。较新的接口或文档也可能将其称为 payment token 或 source token。原始卡号不能直接提交给 Square Payments API。
所谓“不使用 nonce”,只能是改用 Square 托管的结账页面、读卡器 SDK 等其他产品。这些方案仍会在内部生成安全的支付来源,只是不需要 Xamarin 应用直接处理。
为什么 Xamarin 不能直接调用
Xamarin.iOS 和 Xamarin.Android 可以调用原生 SDK,但需要对应的 C# 绑定。Square 提供的组件包括:
- iOS:Objective-C/Swift SDK
- Android:Java/Kotlin SDK
- 服务端:Payments API
Square 支持团队所说的“不支持通过 Xamarin 生成 nonce”,通常是指 Square 没有发布或维护 Xamarin 绑定,并非技术上禁止 Xamarin 调用原生 SDK。
自行绑定 SDK 后,项目团队需要负责兼容性测试、SDK 升级和绑定代码维护。Square 支持团队也未必会协助排查 Xamarin 绑定层的问题。
推荐架构
支付流程应分为客户端和服务端两部分:
Xamarin.iOS / Xamarin.Android
│
│ 调用绑定后的 Square 原生 SDK
▼
生成 nonce / payment token
│
│ 通过 HTTPS 发送给自己的服务端
▼
应用服务端
│
│ 调用 Square Payments API
▼
创建支付并返回结果
不要在移动应用中保存 Square access token,也不要将银行卡号发送到自己的服务端。
方案一:绑定 Square 原生 SDK
如果需要保留原生的应用内银行卡输入体验,通常要采用这种方案。
Xamarin.iOS
创建 Xamarin.iOS Binding Library,将 Square 提供的 iOS framework 或 xcframework 加入绑定项目,然后为需要调用的 Objective-C API 编写绑定定义。
下面的代码只展示绑定结构。实际类名、选择器和方法签名必须以项目所用 Square SDK 的头文件为准:
using Foundation;
using ObjCRuntime;
using UIKit;
namespace SquareBinding
{
[BaseType(typeof(NSObject))]
interface CardDetails
{
[Export("nonce")]
string Nonce { get; }
}
[BaseType(typeof(UIViewController))]
interface CardEntryViewController
{
}
}
完成绑定后,由 Xamarin.iOS 项目展示 Square 的银行卡输入界面,并在原生 SDK 的成功回调中取得 nonce:
void HandleCardEntryCompleted(CardDetails cardDetails)
{
string nonce = cardDetails.Nonce;
// 只把 nonce 和订单信息发送到自己的服务端。
SubmitPaymentAsync(nonce);
}
Square SDK 的回调接口可能随版本变化,不能直接照搬示例声明绑定。应根据 SDK 自带的 Objective-C headers 检查以下内容:
Export中的 selector 是否完全一致;- 回调使用 delegate、protocol 还是 completion block;
- 是否需要
[Protocol]、[Model]或 block delegate; - xcframework 是否包含模拟器和真机所需的架构;
- SDK 依赖的系统 framework 是否已经加入链接配置。
Xamarin.Android
创建 Xamarin.Android Binding Library,将 Square 的 AAR 及其依赖加入绑定项目。绑定成功后,就可以像使用普通 C# 类型一样调用 Java/Kotlin API。
下面的代码表示一种典型调用流程,具体类型名和方法签名需要按照所绑定的 SDK 版本调整:
const int CardEntryRequestCode = 1001;
// 启动绑定后的 Square 原生银行卡输入页面。
CardEntry.StartCardEntryActivity(this, CardEntryRequestCode);
在 Activity 结果回调中读取卡片令牌:
protected override void OnActivityResult(
int requestCode,
Result resultCode,
Android.Content.Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode != CardEntryRequestCode)
return;
if (resultCode == Result.Ok)
{
CardDetails cardDetails = CardEntry.HandleActivityResult(data);
string nonce = cardDetails.Nonce;
_ = SubmitPaymentAsync(nonce);
}
}
这段代码同样只是流程示例。不同版本的 Square SDK 可能使用 Activity Result API、listener、callback 或其他返回方式,应以对应版本的 Android SDK 文档和 AAR 公共 API 为准。
如果生成绑定时失败,通常需要在绑定项目中处理以下问题:
- Java/Kotlin 类型映射;
- 重复依赖;
- 不受支持的泛型签名;
- Kotlin metadata;
Metadata.xml中的类型重命名或节点删除;- AndroidX 依赖版本冲突。
对于复杂 SDK,可以先编写一个很薄的 Kotlin/Java wrapper,只暴露“启动银行卡输入”和“返回 token”两个简单接口,再绑定这个 wrapper。
服务端创建支付
客户端生成 nonce 后,应将其发送给可信服务端,再由服务端调用 Square Payments API。下面用原始 HTTP 请求说明主要字段,避免依赖某个特定版本的 Square .NET SDK:
using System.Net.Http.Headers;
using System.Net.Http.Json;
public async Task<string> CreatePaymentAsync(
string sourceId,
long amount,
string currency,
CancellationToken cancellationToken = default)
{
using var client = new HttpClient
{
BaseAddress = new Uri("https://connect.squareup.com/")
};
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", _squareAccessToken);
// 应设置为项目实际验证过的 Square API 版本。
client.DefaultRequestHeaders.Add(
"Square-Version",
_squareApiVersion);
var request = new
{
source_id = sourceId,
idempotency_key = Guid.NewGuid().ToString(),
amount_money = new
{
amount,
currency
}
};
using HttpResponseMessage response = await client.PostAsJsonAsync(
"v2/payments",
request,
cancellationToken);
string body = await response.Content.ReadAsStringAsync(cancellationToken);
if (!response.IsSuccessStatusCode)
throw new InvalidOperationException($"Square payment failed: {body}");
return body;
}
其中:
source_id是客户端生成的 nonce 或 payment token;amount使用货币的最小单位,例如美元使用美分;idempotency_key必须在同一次支付尝试中保持不变,网络重试时不能随意更换;_squareAccessToken只能保存在服务端;_squareApiVersion应由项目明确配置并完成验证,不要直接复制示例版本号。
正式项目还需要在服务端校验订单金额、币种、商户和用户身份,不能信任客户端上传的金额。
方案二:使用 Square 托管的结账流程
如果不想维护原生绑定,可以让服务端创建 Square 托管的支付链接或结账会话,然后通过系统浏览器打开。
这种方式有以下特点:
- 应用不直接采集银行卡信息;
- Xamarin 端不需要生成 nonce;
- 银行卡安全输入和令牌化由 Square 托管页面完成;
- 支付完成后,需要结合重定向和服务端 webhook 确认最终状态。
概念代码如下:
var uri = Android.Net.Uri.Parse(checkoutUrl);
var intent = new Android.Content.Intent(
Android.Content.Intent.ActionView,
uri);
StartActivity(intent);
在 iOS 中,可以通过 UIApplication.OpenUrl 或对应的现代 API 打开结账地址。
不能仅凭浏览器跳转回应用就将订单标记为已支付。用户可能关闭页面或伪造回调参数,支付也可能处于延迟状态。服务端应查询 Payments API,或通过验证过的 Square webhook 更新最终结果。
方案三:改用读卡器支付
如果业务需要面对面刷卡、插卡或感应支付,应考虑 Square 面向线下读卡器的 SDK,而不是 In-App Payments 的手工输卡流程。
这类方案通常需要:
- Square 支持的读卡设备;
- 商户登录或授权;
- 对应地区和账户资格;
- 原生 iOS/Android SDK;
- Xamarin 自定义绑定或原生桥接层。
读卡器支付不是绕过支付令牌的通用方法,只是使用了不同的支付来源和认证方式。
是否可以直接发送银行卡号
不建议这样做,Payments API 通常也不接受直接提交原始银行卡数据:
{
"card_number": "4111111111111111",
"cvv": "123"
}
正确的 API 输入应使用 Square SDK 或 Square 托管页面生成的安全支付来源:
{
"source_id": "客户端生成的 nonce 或 payment token",
"idempotency_key": "一次支付尝试对应的唯一键",
"amount_money": {
"amount": 1000,
"currency": "USD"
}
}
自行采集和传输原始银行卡数据会大幅扩大 PCI DSS 合规范围,也可能违反 Square 的接口约束和使用要求。
实施时的注意事项
- Square 未提供官方 Xamarin 包时,自定义绑定需要由项目自行维护。
- Xamarin 已进入后续平台迁移阶段。新项目还应评估 .NET for iOS 和 .NET for Android,但迁移平台本身不会自动带来 Square 官方绑定。
- iOS 和 Android 应分别创建绑定项目。共享的 C# 代码只处理订单、网络请求和业务状态。
- 不要把 access token、webhook signature key 等服务端凭据写入应用。
- nonce/payment token 通常受使用次数、有效期或环境限制,不能作为永久卡号保存。
- Sandbox 和 Production 的应用 ID、凭据及支付来源不能混用。
- 网络超时后,不要立即创建一笔全新的支付。应使用相同的幂等键查询或安全重试,以免重复扣款。
- Square SDK 的产品名称、支持平台和 API 可能发生调整。实施前,应根据目标地区、账户类型和当前 SDK 文档确认是否可用。
如果必须保留应用内银行卡输入,就需要绑定 Square 的原生 iOS 和 Android SDK。如果不想承担绑定的维护成本,可以使用 Square 托管的结账页面。对于线上银行卡支付,不能简单删除 nonce 后继续直接调用支付接口。
备注:内容仅供参考。
