PAYATHON 2026

在 iOS 中集成 Siri 实现 Payment transfer

支付阿杰

结论

可以通过银行后台 API 查询收款人,但 Siri 不会自动展示任意的 beneficiary list,也不能代替银行应用中的收款人选择页。通常,用户先说出姓名或别名,Intents Extension 再调用 API 匹配收款人。匹配到多个结果时,由 Siri 请用户进一步选择。如果需要浏览完整列表、登录、选择付款账户或完成强授权,则应跳转到主应用。

付款人通常不从 Siri 参数中获取,而是当前已在银行应用中登录并通过身份认证的客户。Intent Extension 可以读取共享的登录状态或短期凭据,再调用银行 API 获取客户信息和付款账户。不能仅凭 Siri 识别到的声音确认付款人身份。

没有 Touch ID 的设备可以使用设备密码,或者银行应用自己的 PIN、密码、OTP 等方式授权。对于高风险转账,更适合在主应用中通过 LocalAuthentication 或银行自身的认证流程完成授权。

SiriKit 没有可供业务依赖的公开、稳定的固定超时时间。Intent Extension 应只处理快速解析和确认,不要在 Siri 会话中等待耗时的最终清算。如果发生超时或交易状态无法确定,应提示用户打开应用,并通过服务端幂等键和交易状态查询防止重复扣款。

以下方案适用于 INSendPaymentIntent / INRequestPaymentIntent 等传统 SiriKit Payment Intents。它们在不同 SDK 和系统版本中的可用性及弃用状态可能不同,实施前应检查目标 Xcode SDK 中的 availability 标记。新项目还需要评估 App Intents 是否具备所需能力。

1. beneficiary list 如何接入

在 INSendPaymentIntent 中,收款人通常由 payee 表示,类型是 INPerson。这个对象可以来自系统通讯录,也可以由应用根据后台 beneficiary 数据构造。

Siri 的交互方式并不是先下载一份列表,再显示完整的选择器。更合适的流程如下:

  1. 用户说:“给张三转 500 元。”
  2. Siri 将”张三”作为待解析的 payee 传给 Intents Extension。
  3. Extension 使用姓名、昵称或别名调用银行 beneficiary 查询 API。
  4. 只有一个匹配结果时,返回 .success。
  5. 有少量同名结果时,返回 .disambiguation,让 Siri 询问用户选择哪一个。
  6. 没有匹配结果时,要求用户补充收款人,或者跳转到应用中新增、选择收款人。
  7. 如果用户只说”我要转账”,没有提供收款人,则返回 .needsValue(),由 Siri 继续询问。

示例:

import Intents

final class SendPaymentIntentHandler: NSObject, INSendPaymentIntentHandling {
    func resolvePayee(
        for intent: INSendPaymentIntent,
        with completion: @escaping (INSendPaymentPayeeResolutionResult) -> Void
    ) {
        guard let spokenName = intent.payee?.displayName,
              !spokenName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
            completion(.needsValue())
            return
        }

        BeneficiaryAPI.search(name: spokenName) { result in
            switch result {
            case .success(let beneficiaries):
                let people = beneficiaries.map { beneficiary in
                    INPerson(
                        personHandle: INPersonHandle(
                            value: beneficiary.id,
                            type: .unknown
                        ),
                        nameComponents: beneficiary.nameComponents,
                        displayName: beneficiary.displayName,
                        image: nil,
                        contactIdentifier: nil,
                        customIdentifier: beneficiary.id
                    )
                }

                switch people.count {
                case 0:
                    completion(.unsupported())
                case 1:
                    completion(.success(with: people[0]))
                default:
                    // 只返回少量、真正可能匹配的候选项。
                    completion(.disambiguation(with: Array(people.prefix(5))))
                }

            case .failure:
                completion(.unsupported())
            }
        }
    }
}

可以用 customIdentifier 或 personHandle.value 保存银行后台的 beneficiary ID。后续确认交易时,应根据该 ID 再次从服务端读取可信数据,不能依赖 displayName、用户口述的账号或 Siri 返回的展示文本。

如果用户需要浏览几十或几百个 beneficiary,应让 Siri 打开应用:

let activity = NSUserActivity(activityType: "com.example.bank.select-beneficiary")
activity.userInfo = [
    "intent": "send-payment"
]
activity.becomeCurrent()

完整列表、搜索、账号尾号和风险提示都由银行应用展示。不要把大量收款人放进 Siri 的消歧结果。

为了让 Siri 更容易识别应用特有的联系人名称或别名,可以向系统提供应用词汇。不过,应用词汇只是用于提高识别率,并不是供用户浏览的动态列表。具体支持方式仍取决于目标 SiriKit 版本。

2. Payer 信息从哪里获取

付款人身份不能由语音内容决定。对于银行应用,付款人应是服务端认证会话所对应的客户。付款账户可以根据客户的默认账户、用户口述的账户或应用内选择来确定。

可以分为以下几层处理:

  • 主应用处理登录、设备绑定,并建立银行会话。
  • 主应用和 Intents Extension 通过 Keychain Access Group 或 App Group 共享必要且尽量少的会话信息。
  • Extension 使用短期令牌调用 API,获取客户可用账户、默认付款账户和交易权限。
  • 如果令牌失效、没有默认账户、账户不唯一或需要重新认证,则要求用户打开主应用。
  • 服务端重新校验付款账户归属、余额、限额、收款人状态和风险规则。

不要将长期密码、完整银行卡信息或长期有效令牌写入普通 UserDefaults。UserDefaults(suiteName:) 可以共享非敏感配置,认证凭据则应保存在已配置 Access Group 的 Keychain 中。

示意代码:

func confirm(
    intent: INSendPaymentIntent,
    completion: @escaping (INSendPaymentIntentResponse) -> Void
) {
    guard let accessToken = SharedKeychain.loadShortLivedAccessToken() else {
        let activity = NSUserActivity(
            activityType: "com.example.bank.authenticate-payment"
        )

        completion(
            INSendPaymentIntentResponse(
                code: .failureRequiringAppLaunch,
                userActivity: activity
            )
        )
        return
    }

    AccountAPI.fetchPaymentContext(accessToken: accessToken) { result in
        switch result {
        case .success(let context):
            guard context.hasUsableFundingAccount else {
                completion(
                    INSendPaymentIntentResponse(
                        code: .failureRequiringAppLaunch,
                        userActivity: nil
                    )
                )
                return
            }

            completion(
                INSendPaymentIntentResponse(
                    code: .ready,
                    userActivity: nil
                )
            )

        case .failure:
            completion(
                INSendPaymentIntentResponse(
                    code: .failureRequiringAppLaunch,
                    userActivity: nil
                )
            )
        }
    }
}

confirm 阶段适合检查当前是否具备执行条件,包括:

  • 登录会话是否有效;
  • 付款账户是否明确;
  • 收款人是否有效;
  • 金额、币种和限额是否符合要求;
  • 该笔交易是否需要额外授权。

真正执行转账前,服务端仍须完成同样的必要校验,不能将 confirm 的结果当作永久授权。

3. Touch ID 不可用时如何授权

Siri 对意图的口头确认和银行级身份认证是两回事。即使用户对 Siri 说了”确认”,服务端也不能只根据这个动作完成高风险转账。

对于支持 LocalAuthentication 的系统,可以使用:

import LocalAuthentication

let context = LAContext()
var error: NSError?

if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
    context.evaluatePolicy(
        .deviceOwnerAuthentication,
        localizedReason: "授权本次转账"
    ) { success, authenticationError in
        DispatchQueue.main.async {
            if success {
                // 请求服务端创建或确认交易。
            } else {
                // 显示失败原因,或进入银行应用的备用认证流程。
            }
        }
    }
} else {
    // 使用银行 PIN、登录密码、OTP 或其他合规认证方式。
}

.deviceOwnerAuthentication 会根据设备能力使用生物识别或设备密码。如果业务只接受生物识别,可以使用 .deviceOwnerAuthenticationWithBiometrics,但该策略在没有 Touch ID 或 Face ID 的设备上不可用。

还需要注意以下情况:

  • iPhone 4 无法运行支持 SiriKit Intents Extension 的最低系统版本,因此不在这项能力的目标设备范围内。
  • iPhone 5 可以运行部分支持 SiriKit 的系统版本,但没有 Touch ID,需要使用设备密码或应用内认证。
  • 银行 PIN、短信 OTP、动态令牌等认证应在主应用的受控界面中完成。
  • Intent Extension 不适合处理复杂登录、多步 OTP、协议确认或风险问询。
  • 银行是否允许使用设备密码授权转账,要根据自身安全策略和适用的监管要求决定,不能只看系统 API 是否支持。

需要打开应用完成授权时,可以携带服务端生成的短期 payment draft ID:

let activity = NSUserActivity(
    activityType: "com.example.bank.authorize-payment"
)

activity.userInfo = [
    "paymentDraftID": paymentDraftID
]

let response = INSendPaymentIntentResponse(
    code: .failureRequiringAppLaunch,
    userActivity: activity
)

completion(response)

应用打开后,再从服务端读取草稿详情,向用户展示收款人、金额、手续费和付款账户,并完成最终授权。不要将完整账号或其他敏感信息写入 userInfo。

4. Siri timeout 和 payment processing

Apple 没有提供一个可供应用设计业务流程的固定 Siri payment timeout。设备性能、网络状况、内存压力或用户主动结束会话,都可能让系统提前终止 Extension。因此,应用不能假设只要在某个固定秒数内完成,操作就一定成功。

Siri 流程可以拆成两个阶段。

快速阶段

在 Intent Extension 内完成以下工作:

  • 解析收款人;
  • 解析金额和币种;
  • 获取默认付款账户;
  • 检查登录状态;
  • 创建服务端 payment draft;
  • 返回是否可以继续,或者要求用户打开应用。

API 应设置较短的客户端超时时间,并保证 completion 只调用一次。如果网络长时间没有响应,应尽快结束 Siri 流程,不能一直等待。

最终交易阶段

最终扣款由服务端处理,并需要具备:

  • 幂等键;
  • 唯一的 payment draft ID;
  • 明确的状态机;
  • 可查询的交易状态;
  • 审计记录;
  • 超时后的状态恢复机制。

例如:

CREATED
  -> AUTHORIZATION_REQUIRED
  -> AUTHORIZED
  -> PROCESSING
  -> SUCCEEDED

PROCESSING
  -> FAILED
  -> UNKNOWN

创建或提交支付时,可以使用客户端生成的幂等键:

struct PaymentSubmission: Encodable {
    let draftID: String
    let idempotencyKey: String
}

let submission = PaymentSubmission(
    draftID: paymentDraftID,
    idempotencyKey: UUID().uuidString
)

如果 Siri 会话结束时,API 返回的结果仍不明确,绝不能马上发起一笔新的转账。客户端应使用原来的 draft ID 或 idempotency key 查询状态:

GET /payments/{paymentDraftID}

只有当服务端明确返回未提交或失败,并且允许重试时,客户端才能重试。交易仍处于 PROCESSING 或 UNKNOWN 状态时,应在应用中显示”正在处理”,随后通过轮询、推送通知或后台刷新更新结果。

推荐的完整流程

一套较稳妥的银行转账流程如下:

  1. Siri 获取收款人、金额和可选备注。
  2. Extension 调用 beneficiary API 解析收款人。
  3. Extension 读取短期会话,查询当前客户和默认付款账户。
  4. 服务端预先检查收款人、限额、账户状态和风险。
  5. 服务端创建 payment draft,此时暂不扣款。
  6. Siri 向用户复述收款人和金额。
  7. 对于低风险交易,如果银行策略允许,则通过既定授权机制提交。
  8. 需要强认证时,跳转到主应用,通过设备密码、生物识别、银行 PIN 或 OTP 授权。
  9. 主应用携带 draft ID 提交交易。
  10. 服务端通过幂等控制处理扣款,客户端根据交易状态显示结果。

Siri 负责接收自然语言指令和进行简单确认,银行应用负责可信的身份认证,服务端则负责验证交易、保证幂等并维护最终状态。

备注:内容仅供参考。