Lite SDK (Enrollment iOS)

本指南将指导您将 Yuno 用于注册的 Lite iOS SDK 集成到您的项目中。

要求

在实施 Yuno iOS SDK 之前,请确保满足这些要求:

第 1 步:在项目中加入程序库

您可以使用 CocoaPods 或 Swift 软件包管理器添加该库。

CocoaPods

使用 CocoaPods 将 Yuno SDK 添加到您的 iOS 项目中。如果您没有 Podfile,请按照CocoaPods 指南创建一个。然后在 Podfile 中添加以下一行:

pod "YunoSDK","~> 1.1.22

然后运行安装程序:

吊舱安装

Swift 软件包管理器

使用 Swift 包管理器添加 Yuno SDK。添加 YunoSDK 作为您 Package.swift 文件中的依赖项:

依赖项:[
    .package(url:"https://github.com/yuno-payments/yuno-sdk-ios.git", .upToNextMajor(from: "1.1.17"))
]

步骤 2:注册新的付款方式

📘

呼叫前 Yuno.enrollPayment(),确保您已经用以下命令初始化了 SDK Yuno.initialize().

Yuno 的 iOS SDK 为支付方法提供了注册功能。要显示注册流程,请实现委托并调用注册方法:

protocol YunoEnrollmentDelegate: AnyObject {
    var customerSession: String { get }
    var countryCode: String { get }
    var language: String? { get }
    var viewController: UIViewController? { get }

    func yunoEnrollmentResult(_ result: Yuno.Result)
}

class ViewController: YunoEnrollmentDelegate {

    func startEnrollment() {
        Yuno.enrollPayment(with: self, showPaymentStatus: Bool)
    }
}

Yuno.enrollPayment() 全屏显示 UIViewController 使用 viewController 在您的 delegate.这仅适用于 UIKit。在 SwiftUI 中,将一个 UIViewController 并通过 viewController 财产该 delegate 必须暴露一个可见控制器,以便 SDK 展示用户界面。

参数

参数说明
customerSession指当前付款的客户会话。
countryCode该参数决定了正在配置支付流程的国家。支持国家及其国家代码的完整列表可在 "国家覆盖范围"页面查看。
language定义付款表单中使用的语言。您可以将其设置为可用语言选项之一:
  • es (西班牙文)
  • en (英语)
  • pt (葡萄牙语)
  • 菲力
  • id(印尼语)
  • 马来文
  • th(泰语)
  • zh-TW(繁体中文,台湾
  • zh-CN (简体中文,中国
  • vi(越南语)
  • 法文
  • pl (波兰语)
  • it(意大利语)
  • 德文
  • ru(俄文)
  • tr (土耳其语)
  • nl(荷兰语)
  • sv(瑞典语)
  • 韩语
  • ja (日语)
viewController该属性表示 UIViewController 用于显示注册流程。尽管为了实现向后兼容性,该属性仍然是可选的,但您必须提供一个可见控制器,以便 SDK 能正确显示其用户界面。
yunoEnrollmentResult(\_ result: Yuno.Result)注册过程完成后,该方法将被调用,并将注册结果作为类型为 Yuno.Result.

"(《世界人权宣言》) showPaymentStatus 参数决定是否显示付款状态。通过 true 显示付款状态,而通过 false 藏起来。

参数

"(《世界人权宣言》) enrollPayment 方法参数说明如下:

参数类型说明
delegateYunoEnrollmentDelegate处理注册回调的委托对象。
showPaymentStatusBool布尔标志,用于确定是否在付款注册过程中显示状态视图。

方法 enrollPayment 启动支付注册流程。应在用户交互(如按下按钮)时调用该方法。该方法利用提供的 delegate 管理注册事件,并根据参数 showPaymentStatus决定是否显示有关注册状态的视觉反馈。

步骤 3:注册情况

❗️

深度链接注册

此功能只有在注册了可执行深度链接的付款方式时才会使用。如果没有注册执行深度链接的付款方式,可以忽略步骤 3。

如果您使用的支付方法需要返回应用程序的深度链接,请使用以下代码块中描述的方法在您的 AppDelegate.......。 url.scheme 应与 callback_url 时使用的 customer_session.

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {

  guard url.scheme == "yunoexample" else { return false }
  return Yuno.receiveDeeplink(url, showStatusView: true)
}
🚧

Swift 6 并发要求

如果使用 Swift 6,则需要执行 YunoPaymentDelegate 协议的具体并发考虑因素。Swift 6 引入了更严格的线程安全要求,这将影响您实现委托的方式。请参见 实施 YunoPaymentDelegate 使用 Swift 6 并发 部分,了解详细的实施方案和最佳做法。

理解 Yuno.Result

注册过程结束后,SDK 会调用:

func yunoEnrollmentResult(_ result:Yuno.Result)

这一结果可能反映了入学的不同最终状态。这 Yuno.Result 枚举包括

枚举结果 {
    成功
    失败
    拒绝
    处理中
   internalError

   userCancell

}

您可以使用开关来处理每种结果情况:

func yunoEnrollmentResult(_ result: Yuno.Result) {
    switch result {
    case .success:
        print("Enrollment successful")
    case .fail:
        print("Enrollment failed")
    case .processing:
        print("Enrollment still processing")
    case .reject:
        print("Enrollment rejected")
    case .userCancell:
        print("User canceled")
    case .internalError:
        print("Internal error")
    }
}
📘

Yuno.Result 不包括tokens 或错误信息。您只会得到一个高级状态,以帮助您指导用户体验。

渲染模式集成(报名)

渲染模式为注册提供了更大的用户界面灵活性,让您可以在使用 SDK 验证和逻辑的同时,将注册流程集成到自己的视图中。

主要功能 startEnrollmentRenderFlow

@MainActor static func startEnrollmentRenderFlow(
    with delegate:YunoEnrollmentDelegate
) async -> 某 YunoEnrollmentRenderFlowProtocol

YunoEnrollmentRenderFlowProtocol

func formView(
    with delegate: YunoEnrollmentDelegate
) async -> AnyView?

func submitForm()

var needSubmit: Bool { get }
  • formView:返回一个 AnyView 如果需要 UI,则与注册表(卡/APM)一起提交;如果不需要 UI,则与注册表(卡/APM)一起提交 nil.
  • 提交表格:触发验证,并在适用时继续注册。
  • needSubmit:表示是否应显示提交按钮。

实施流程

步骤 1:创建注册流程实例

let enrollmentFlow = awaitYuno.startEnrollmentRenderFlow(with: self)

步骤 2:获取并显示表单

let formView = await enrollmentFlow.formView(with: self)

if let formView = formView {
    VStack {
        Text("Enroll Payment Method")
        formView

        if enrollmentFlow.needSubmit {
            Button("Enroll") {
                enrollmentFlow.submitForm()
            }
        }
    }
}

步骤 3:处理注册结果

extension MyViewController: YunoEnrollmentDelegate {
    var customerSession: String { "your_customer_session" }
    var countryCode: String { "CO" }
    var language: String? { "es" }
    var viewController: UIViewController? { self }

    func yunoEnrollmentResult(_ result: Yuno.Result) {
        // Handle enrollment result
    }
}

补充功能

Yuno iOS SDK 提供额外的服务和配置,可用于改善客户体验。

渲染选项

在提交注册时,您还可以选择其中一种卡片形式的渲染选项。您有以下选项:

  • ONE_STEP
  • STEP_BY_STEP

要更改渲染选项,请设置 cardFormType 等于其中一个可用选项。下文介绍了每种选项。

装载机

通过 SDK 配置选项控制加载器的使用。

SDK 定制

使用SDK 定制功能更改 SDK 外观,使其与您的品牌相匹配。

📘

演示应用程序

除提供的代码示例外,您还可以访问Yuno 存储库,获取 Yuno iOS SDK 的完整实现。

实施 YunoPaymentDelegate 使用 Swift 6 并发

Swift 6 引入了更严格的并发性要求,这会影响您如何实现 YunoPaymentDelegate 协议。本节将解释这些挑战,并针对不同的实施方案提供解决方案。

📘

了解 Swift 6 中的并发性

并发性是指应用程序同时管理多个任务的能力。在 Swift 6 中,并发规则变得更加严格,以增强应用程序的稳定性并防止崩溃。这意味着您的代码结构必须更加严谨,以确保线程安全和适当的任务管理。

问题

在 Swift 6 中,继承自 Sendable 要求其所有实现都是线程安全的。当在标记为 @MainActor.

线程安全意味着你的代码可以被多个线程安全调用,而不会导致崩溃或意外行为。 @MainActor 确保代码在主线程(UI 线程)上运行。

我们的设计决定

我们不会将协议标记为 @MainActor 因为

  • 这将迫使所有实现 MainActor 兼容的
  • 这将降低不使用以下功能的商家的灵活性 MainActor
  • 每种执行方式都有不同的并发需求

商家的责任

商家有责任根据自己的实施情况处理并发问题。以下是三种不同的方法,您可以根据自己的具体需求加以使用。

方案 1:不可变属性

这种方法使用不可变属性,可自动保证线程安全,是简单配置的理想选择。它最适用于具有固定配置值且在运行时不会改变的简单应用程序。

@MainActor
class MyViewController: UIViewController, YunoPaymentDelegate {

    private let _countryCode = "CO"
    private let _language = "EN"

    nonisolated var countryCode: String { _countryCode }
    nonisolated var language: String? { _language }
    nonisolated var checkoutSession: String { _checkoutSession }

    nonisolated func yunoPaymentResult(_ result: Yuno.Result) {
        Task { @MainActor in
            // Handle result
        }
    }
}

方案 2:可变属性 MainActor.assumeIsolated

这种方法最适用于配置值可能在运行时发生变化的应用程序(如用户偏好设置),通过使用 MainActor.assumeIsolated.

@MainActor
class MyViewController: UIViewController, YunoPaymentDelegate {

    @Published var configLanguage: String = "EN"
    @Published var configCountryCode: String = "CO"

    nonisolated var language: String? {
        MainActor.assumeIsolated { configLanguage }
    }

    nonisolated var countryCode: String {
        MainActor.assumeIsolated { configCountryCode }
    }
}

方案 3:对于非 MainActor 班级

这种方法适用于不需要 MainActor 隔离,因此最适合不与用户界面交互的后台服务或实用程序类。

class MyService: YunoPaymentDelegate {

    let countryCode: String
    let language: String?
    let checkoutSession: String
    let viewController: UIViewController?

    init(countryCode: String, language: String?, checkoutSession: String, viewController: UIViewController?) {
        self.countryCode = countryCode
        self.language = language
        self.checkoutSession = checkoutSession
        self.viewController = viewController
    }

    func yunoPaymentResult(_ result: Yuno.Result) {
        // Handle result
    }
}

⚠️ 重要考虑因素

在委托中实现并发时,请牢记以下要点:

  • MainActor.assumeIsolated:仅在保证从 MainActor.这是一种安全机制,它告诉 Swift "相信我,我知道这是在主线程上运行的"。
  • nonisolated:表示可以从任何线程访问,因此必须是线程安全的。当您的属性或方法不依赖于用户界面状态时,请使用此属性或方法。
  • viewController:仍为 @MainActor 因为它应始终由主线程访问。用户界面组件必须始终在主线程上运行,以防止崩溃。