Xamarin binding 在 iOS SDK 7.1 中调用支付 API 超时
结论
日志显示,initiate Session 从 08:13:11 持续到 08:14:11,约 60 秒后返回 DF39 = 4900。这种情况更像是支付 SDK 在等待网络响应或异步回调时超时。仅凭现有日志,无法认定问题来自 Xamarin 或 iOS 7.1。
建议先在同一设备和同一网络下,使用相同版本的支付 SDK 运行厂商提供的原生 Objective-C 示例:
- 如果原生示例也超时,重点检查支付 SDK 与 iOS 7.1 的兼容性,以及服务器、TLS、证书、代理和设备配置。
- 如果只有 Xamarin binding 超时,重点检查绑定签名、回调对象的生命周期、主线程要求、静态库架构和链接器裁剪。
- 如果 Debug 正常而 Release 超时,应优先检查 Xamarin linker 是否裁剪了 Objective-C 回调入口。
日志中的 syncdefaultsd、wifid、定位扫描和屏幕亮度信息大多与这次调用无关。最后一条日志:
HID: The 'Rate Controlled' connection 'Sample' access to protected services is denied.
说明应用尝试访问某种受保护的 HID 服务,但现有信息不足以证明它导致了支付超时。除非支付 SDK 明确依赖刷卡器、PIN Pad 或其他外设,否则不能将它视为唯一线索。
建议的定位顺序
1. 先确认超时发生在哪一层
分别记录以下时间点:
C# 调用 binding
进入 Objective-C SDK
开始建立网络连接
收到 HTTP 状态码
进入 Objective-C callback
进入 C# callback
如果厂商 SDK 支持详细日志,还应记录:
- 最终请求 URL 的主机名,敏感路径和参数需要脱敏;
- DNS、TCP、TLS 和 HTTP 各阶段的耗时;
- HTTP 状态码和底层
NSError; - SDK 返回码
DF39 = 4900的官方含义。
4900 看起来是厂商定义的业务错误码,不是标准的 iOS 错误码。必须查阅对应支付 SDK 版本的错误码文档,不能直接将其解释为某种系统错误。
2. 用原生 Objective-C 示例做对照
在同一台 iOS 7.1 设备上运行厂商提供的原生示例,并确保以下条件完全相同:
- 支付 SDK 二进制文件;
- activation URL;
- 商户或终端配置;
- 请求参数;
- Wi-Fi 或蜂窝网络;
- 服务器环境。
如果原生程序同样在 60 秒后超时,binding 通常不是主要原因。此时应请支付 SDK 供应商确认该版本是否正式支持 iOS 7.1,同时检查服务端是否实际收到了请求。
3. 检查 binding 的方法签名
Objective-C 的参数类型、返回类型和 selector 必须逐项匹配。例如,原生接口为:
- (void)initiateSession:(NSDictionary *)parameters
delegate:(id<PaymentDelegate>)delegate;
对应的绑定可以写成:
[BaseType(typeof(NSObject))]
interface PaymentClient
{
[Export("initiateSession:delegate:")]
void InitiateSession(
NSDictionary parameters,
[NullAllowed] IPaymentDelegate callback);
}
[Protocol, Model]
[BaseType(typeof(NSObject))]
interface PaymentDelegate
{
[Abstract]
[Export("paymentClient:didCompleteWithParameters:")]
void DidComplete(
PaymentClient client,
NSDictionary parameters);
[Abstract]
[Export("paymentClient:didFailWithError:")]
void DidFail(
PaymentClient client,
NSError error);
}
需要重点核对:
- selector 中冒号的数量和位置;
NSDictionary、NSString、NSData等类型是否正确;- delegate 是参数、属性还是 block;
- 原生声明中的 delegate 属性是
assign、weak、retain还是strong; - 回调是否为可选方法;
- block 是否需要
[BlockCallback]; - C 结构体和枚举的底层宽度是否正确。
不要根据方法名称猜测签名,应以当前支付 SDK 版本的头文件为准。
4. 强引用 delegate 和 SDK 实例
支付 SDK 经常使用 weak 或旧式的 assign delegate。如果回调对象只保存在局部变量中,托管对象可能在异步操作结束前被垃圾回收。此时原生 SDK 即使执行了回调,C# 层也可能无法收到,最终表现为等待超时。
public sealed class PaymentService : IDisposable
{
private readonly PaymentClient client;
private readonly PaymentCallback callback;
public PaymentService()
{
client = new PaymentClient();
callback = new PaymentCallback();
}
public void Start(NSDictionary parameters)
{
client.InitiateSession(parameters, callback);
GC.KeepAlive(callback);
}
public void Dispose()
{
callback.Dispose();
client.Dispose();
}
}
public sealed class PaymentCallback : PaymentDelegate
{
public override void DidComplete(
PaymentClient client,
NSDictionary parameters)
{
Console.WriteLine("Payment callback completed");
}
public override void DidFail(
PaymentClient client,
NSError error)
{
Console.WriteLine(
$"Payment callback failed: Domain={error.Domain}, " +
$"Code={error.Code}, Description={error.LocalizedDescription}");
}
}
GC.KeepAlive 只能保证对象存活到当前方法结束,不能代替字段级强引用。在异步支付流程中,应由生命周期足够长的对象持有 delegate 和 SDK client,直到收到成功、失败或取消回调。
5. 确保在主线程调用
如果支付 SDK 会创建 UI、访问外设、启动 run loop source,或者要求在主线程初始化,就应显式切换到主线程:
InvokeOnMainThread(() =>
{
paymentService.Start(parameters);
});
还应避免以下写法:
// 不建议:可能阻塞负责派发原生回调的线程
var result = paymentTask.Result;
// 不建议:同步等待可能造成死锁
paymentTask.Wait();
应优先使用 await,并确保主线程没有被同步等待占用:
try
{
PaymentResult result = await paymentService.StartAsync(parameters);
}
catch (NSErrorException ex)
{
Console.WriteLine(
$"Domain={ex.Error.Domain}, Code={ex.Error.Code}, " +
$"Description={ex.Error.LocalizedDescription}");
}
6. 暂时关闭链接器进行验证
Xamarin linker 可能会移除那些只由 Objective-C runtime 调用的类型或成员。可以先在测试构建中将 Linker Behavior 设置为“不链接”,然后重新部署。
如果关闭链接后恢复正常,就需要保留回调类型或相关成员。例如:
using Foundation;
[Preserve(AllMembers = true)]
public sealed class PaymentCallback : PaymentDelegate
{
public override void DidComplete(
PaymentClient client,
NSDictionary parameters)
{
}
public override void DidFail(
PaymentClient client,
NSError error)
{
}
}
绑定项目也可以根据实际 API 使用:
[Protocol, Model]
[BaseType(typeof(NSObject))]
interface PaymentDelegate
{
// 回调声明
}
需要保留哪些类型取决于 SDK 的调用方式。确认原因后,应将保留范围缩小到必要的类型和成员,不建议长期完全关闭链接器来掩盖问题。
7. 检查静态库及其依赖
确认支付 SDK 的 .a 或 framework 包含目标设备所需的架构。iPhone 5s 引入了 arm64,但缺少 arm64 通常会造成构建、安装或加载错误,而不仅仅是网络超时。即便如此,升级工具链时仍应检查:
lipo -info libPaymentSDK.a
还要核对厂商文档要求的系统 framework,例如:
Security.framework
SystemConfiguration.framework
CoreLocation.framework
ExternalAccessory.framework
如果 SDK 通过 Objective-C category 提供功能,静态链接时可能需要 -ObjC。不要盲目添加链接参数,应以 SDK 文档或链接日志为依据。
8. 独立检查网络和服务端
日志显示请求目标使用 HTTPS,因此还需要检查:
- 设备时间和时区是否正确;
- 证书链是否完整;
- 服务器是否支持该设备和 SDK 使用的 TLS 版本及加密套件;
- activation URL 是否发生重定向;
- 企业代理、防火墙或 VPN 是否拦截请求;
- iOS 7.1 设备是否能直接访问同一主机;
- 服务端是否在
08:13:11左右收到请求; - 服务端是否已经返回响应,但客户端没有正确处理。
在支付 SDK 允许的前提下,可以通过服务端 access log 或受控代理获取连接各阶段的信息。支付数据可能包含卡号、令牌和设备标识,因此调试日志及抓包文件必须脱敏,并限制访问范围。
对现有日志的判断
与业务直接相关的日志只有:
08:13:11 ----->Called initiate Session
08:14:11 Key - DF39 & Value - 4900
08:14:11 ----->DONE initiate Session
这些日志只能证明调用在约一分钟后结束,无法说明:
- 请求是否真正发出;
- DNS 和 TLS 是否成功;
- 服务端是否收到请求;
- Objective-C 回调是否执行;
4900表示客户端超时、服务端超时,还是其他厂商错误。
因此,先不要根据 wifid 或 HID 日志推测原因。应先完成原生示例和 Xamarin binding 的对照测试,再分别在 Objective-C 入口与 C# 回调处增加日志。
如果确认只有 binding 失败,应优先检查并修正 delegate 生命周期、绑定签名、主线程调用和 linker 配置。如果原生示例也失败,则应升级到厂商明确支持 iOS 7.1 的支付 SDK,或请供应商结合 DF39 = 4900 和服务端日志继续定位。
备注:内容仅供参考。
