Siri IntentHandler 中 LocalAuthentication 无法正常工作
结论
LocalAuthentication 本身没有问题。真正的问题是 handleSendPayment:completion: 的异步流程没有调用 completion。
创建 INSendPaymentIntentResponse 只会得到一个响应对象,不会自动把结果交给 Siri。无论 Touch ID 成功、失败、不可用,还是支付失败,处理结束后都必须调用 completion,而且只能调用一次:
completion(response);
Siri 只有收到 INSendPaymentIntentResponseCodeSuccess,才会展示“付款已发送”的成功界面。
为什么当前代码没有响应
原代码有以下问题:
siriResponse赋值后没有传给completion。- Touch ID 成功后只执行了支付代码,没有返回最终结果。
canEvaluatePolicy:error:返回NO时,方法直接结束,Siri 收不到任何响应。- Touch ID 失败时却创建了
INSendPaymentIntentResponseCodeSuccess,状态与实际结果不符。 evaluatePolicy:localizedReason:reply:是异步调用。Intent Extension 必须等异步任务完成后再结束响应流程。- 支付逻辑通常不需要在主线程执行。只有明确依赖主线程的代码才需要切换到主队列。
推荐实现
把 Touch ID 验证、支付处理和 Siri 回调串成完整的异步流程,确保每条路径都会调用 completion。
#import <Intents/Intents.h>
#import <LocalAuthentication/LocalAuthentication.h>
- (void)handleSendPayment:(INSendPaymentIntent *)intent
completion:(void (^)(INSendPaymentIntentResponse *response))completion {
LAContext *context = [[LAContext alloc] init];
NSError *authenticationError = nil;
BOOL canAuthenticate =
[context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
error:&authenticationError];
if (!canAuthenticate) {
INSendPaymentIntentResponse *response =
[[INSendPaymentIntentResponse alloc]
initWithCode:INSendPaymentIntentResponseCodeFailureRequiringAppLaunch
userActivity:[self paymentUserActivityForIntent:intent]];
completion(response);
return;
}
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:@"使用 Touch ID 确认付款"
reply:^(BOOL success, NSError *error) {
if (!success) {
INSendPaymentIntentResponseCode code =
INSendPaymentIntentResponseCodeFailure;
/*
* 如果需要让用户进入主 App 重试,或者当前扩展环境无法显示
* 生物识别界面,可以改用 FailureRequiringAppLaunch。
*/
if (error.code == LAErrorNotInteractive) {
code = INSendPaymentIntentResponseCodeFailureRequiringAppLaunch;
}
INSendPaymentIntentResponse *response =
[[INSendPaymentIntentResponse alloc]
initWithCode:code
userActivity:(code == INSendPaymentIntentResponseCodeFailureRequiringAppLaunch
? [self paymentUserActivityForIntent:intent]
: nil)];
completion(response);
return;
}
[self sendPaymentForIntent:intent
completion:^(BOOL paymentSucceeded, NSError *paymentError) {
INSendPaymentIntentResponseCode code =
paymentSucceeded
? INSendPaymentIntentResponseCodeSuccess
: INSendPaymentIntentResponseCodeFailure;
INSendPaymentIntentResponse *response =
[[INSendPaymentIntentResponse alloc]
initWithCode:code
userActivity:nil];
completion(response);
}];
}];
}
辅助方法需要根据应用的实际支付流程实现:
- (void)sendPaymentForIntent:(INSendPaymentIntent *)intent
completion:(void (^)(BOOL succeeded, NSError *error))completion {
// 校验收款人、金额、账户状态,并异步调用支付服务。
// 支付服务返回后调用 completion,不能遗漏任何失败路径。
[self.paymentService sendPaymentWithIntent:intent
completion:^(NSError *error) {
completion(error == nil, error);
}];
}
- (NSUserActivity *)paymentUserActivityForIntent:(INSendPaymentIntent *)intent {
NSUserActivity *activity =
[[NSUserActivity alloc] initWithActivityType:@"com.example.payment.authentication"];
activity.title = @"确认付款";
activity.userInfo = @{
@"intentIdentifier": intent.identifier ?: @""
};
return activity;
}
是否需要切换到主线程
不必为了调用 completion 强制切换到主线程。LocalAuthentication 的 reply block 不保证在主线程执行,但 Siri Intent 的 completion handler 通常可以直接从当前回调队列调用。
如果支付 SDK 明确要求从主线程调用,只需把对应操作放进主队列:
dispatch_async(dispatch_get_main_queue(), ^{
[self startPaymentWithCompletion:^(BOOL succeeded, NSError *error) {
INSendPaymentIntentResponse *response =
[[INSendPaymentIntentResponse alloc]
initWithCode:(succeeded
? INSendPaymentIntentResponseCodeSuccess
: INSendPaymentIntentResponseCodeFailure)
userActivity:nil];
completion(response);
}];
});
dispatch_async 不会同步执行。如果业务需要等待支付结果,就必须在支付完成的回调中向 Siri 返回响应,不能在提交异步任务后立即结束方法。
生物识别无法在扩展中显示时
Intent Extension 的运行时间和界面展示能力有限。在某些系统版本、设备状态或扩展运行环境下,evaluatePolicy: 可能返回 LAErrorNotInteractive。遇到这种情况,不应继续静默等待。
可以按以下方式处理:
- 返回
INSendPaymentIntentResponseCodeFailureRequiringAppLaunch。 - 附带
NSUserActivity。 - 让用户进入主 App。
- 在主 App 前台完成 Touch ID 或 Face ID 验证。
- 验证成功后再提交支付。
不要先在 Siri 中返回成功,再打开 App 验证。Success 只能表示付款已经真正完成。
还需要注意的细节
completion必须调用一次,既不能遗漏,也不能重复。- Touch ID 取消、锁定、验证失败或系统取消都不能当作支付成功处理。
- 只有支付成功后才能返回
INSendPaymentIntentResponseCodeSuccess,否则 Siri 可能显示错误的最终状态。 - Intent Extension 有执行时间限制,不适合长时间等待支付服务。如果操作无法及时完成,应考虑返回需要打开 App 的响应。
LAPolicyDeviceOwnerAuthenticationWithBiometrics只允许生物识别。如果业务允许使用设备密码作为后备验证,可以评估LAPolicyDeviceOwnerAuthentication,具体取决于应用的安全策略和目标系统版本。- 应保留并检查
LAError,特别是LAErrorUserCancel、LAErrorBiometryLockout和LAErrorNotInteractive。 - 如果其他方法还需要取消或管理
LAContext,可以将其保存为IntentHandler的强引用属性。不要用临时局部状态记录尚未完成的支付结果。
原代码最直接的修复方式,是在支付真正完成后创建响应并调用:
INSendPaymentIntentResponse *response =
[[INSendPaymentIntentResponse alloc]
initWithCode:INSendPaymentIntentResponseCodeSuccess
userActivity:nil];
completion(response);
要完整解决问题,还需要覆盖生物识别不可用、验证失败、支付失败和需要打开主 App 的情况。
备注:内容仅供参考。
