PAYATHON 2026

如何通过 SiriKit 将 INSendPaymentIntent 传递给 App?

支付阿杰

结论

INSendPaymentIntent 没有 .continueInApp,不能像通话类 Intent 那样让 SiriKit 把原始 Intent 直接交给 App 处理。

如果付款必须在 App 内完成,标准做法是返回 .failureRequiringAppLaunch。响应中可以附带 NSUserActivity,把订单号、收款人标识或一次性交易令牌交给 App。不要尝试跨进程传递自定义对象,也不建议序列化整个 INSendPaymentIntent。

为什么不能直接传递

Intent Extension 和主 App 运行在不同进程中。每种 Intent 都有自己的响应代码,不能自行添加 .continueInApp,也不能使用其他 Intent 的响应代码。

INSendPaymentIntentResponseCode 没有 .continueInApp。系统希望扩展尽量完成解析、确认和支付处理。如果必须启动 App 才能继续,就应返回 .failureRequiringAppLaunch。

这个名称虽然带有“失败”,但在这里表达的是:当前请求无法在扩展中完成,需要进入 App。

在响应中携带交接信息

在 Intent Extension 中创建 NSUserActivity,其中只保存 App 恢复支付流程所需的信息:

import Intents

final class SendPaymentIntentHandler: NSObject, INSendPaymentIntentHandling {
    func handle(
        intent: INSendPaymentIntent,
        completion: @escaping (INSendPaymentIntentResponse) -> Void
    ) {
        let activity = NSUserActivity(
            activityType: "com.example.app.continue-payment"
        )

        activity.title = "Continue Payment"
        activity.userInfo = [
            "paymentRequestID": "request-12345"
        ]

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

        completion(response)
    }
}

更稳妥的方式是先在共享存储或服务端创建一条待处理支付记录,再将不可预测的一次性令牌放入 userInfo:

activity.userInfo = [
    "handoffToken": handoffToken
]

主 App 可以通过令牌重新加载并验证支付信息,不必依赖 INSendPaymentIntent 的完整对象结构。

在 App 中接收

采用 Scene 生命周期的 App 可以在 SceneDelegate 中接收:

func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    guard userActivity.activityType == "com.example.app.continue-payment",
          let token = userActivity.userInfo?["handoffToken"] as? String else {
        return
    }

    continuePayment(with: token)
}

如果没有采用 Scene 生命周期,可以在 AppDelegate 中处理:

func application(
    _ application: UIApplication,
    continue userActivity: NSUserActivity,
    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
    guard userActivity.activityType == "com.example.app.continue-payment",
          let token = userActivity.userInfo?["handoffToken"] as? String else {
        return false
    }

    continuePayment(with: token)
    return true
}

具体回调位置取决于 App 的生命周期配置和系统版本。测试时应覆盖 App 未运行、后台运行和已经打开这三种状态。

共享数据的替代方案

如果需要交接的数据较多,可以让 App 和 Intent Extension 共用一个 App Group:

let defaults = UserDefaults(
    suiteName: "group.com.example.app"
)

defaults?.set(paymentRequestData, forKey: handoffToken)

NSUserActivity 只需携带 handoffToken,主 App 启动后再用该令牌读取数据。正式支付系统通常更适合把待处理记录保存在服务端,以便执行过期控制、幂等检查和状态验证。

注意事项

  • 不要把银行卡号、验证码、访问令牌等敏感信息直接写入 NSUserActivity.userInfo。
  • App 启动后必须重新确认金额、币种、收款人和用户身份,不能直接信任扩展传来的数据。
  • 交接令牌应设置有效期并限制为一次性使用,避免重复支付。
  • 支付接口应具备幂等性,防止 Siri 重试、App 重启或网络重连导致重复扣款。
  • SiriKit 不保证能把任意内存对象从 Extension 原样传给 App。跨进程交接应使用可序列化数据、App Group、Keychain Access Group 或服务端记录。
  • 如果完整支付可以在 Intent Extension 中安全完成,就应直接在那里处理并返回 .success。只有支付依赖 App 界面、额外认证或 App 专属状态时,才使用 .failureRequiringAppLaunch。

这个场景没有其他更合适的隐藏响应代码。应使用 .failureRequiringAppLaunch 启动 App,并通过 NSUserActivity 和令牌恢复支付流程。

备注:内容仅供参考。