Seamless SDK (iOS 支付)

在本页中,您将找到在 iOS 项目中添加、配置和使用 Seamless iOS SDK 进行支付的所有步骤。

👍

推荐的 SDK

我们建议使用iOSSeamless SDK,以获得流畅的集成体验。该选项提供了灵活的支付解决方案,预置了用户界面组件和自定义选项。

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

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

CocoaPods

要在 iOS 项目中添加 Yuno SDK,您需要安装 Yuno SDK。如果您没有 Podfile,请按照CocoaPods 指南创建一个。创建 Podfile 后,在 Podfile 中添加以下一行,即可将 Yuno SDK 与 Cocoapods 整合。

pod "YunoSDK","~> 1.19.0

之后,您需要运行安装程序:

吊舱安装

Swift 软件包管理器

如果使用Swift 软件包管理器,请将 Yuno SDK 添加为依赖项,如下代码块所示:

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

步骤 2:使用公钥Initialize SDK

要开始运行 Yuno iOS 无缝结账,您首先需要获取 Yuno 应用程序 ID 和公共 API 密钥。然后,导入并initialize Yuno,如下代码片段所示:

import YunoSDK

Yuno.initialize(
    apiKey: "PUBLIC_API_KEY",
    config: YunoConfig(),
    callback: { (value: Bool) in }
)
🚧

UISceneDelegate

如果您的应用程序使用 UISceneDelegate,Yuno 的初始化代码被放置在您的 SceneDelegate.

无缝签出可让您配置 SDK 的外观。这是一个可选步骤,您可通过类 YunoConfig.要设置配置,请使用以下代码块配置可用元素:

final class YunoConfig {
    let cardFormType: CardFormType,
    let appearance: Yuno.Appearance,
    let saveCardEnabled: Bool,
    let keepLoader: Bool
}

使用以下选项配置 SDK:

参数说明
cardFormType该字段可用于选择 PaymentEnrollment Card 流量。这是一个可选属性。它使用 .oneStep 选项。
appearance这个可选字段定义了结账的外观。默认情况下,它使用 Yuno 风格。
saveCardEnabled这个可选字段可让您选择是否在卡片流上显示保存卡片复选框。默认为假。
keepLoader该可选字段可控制何时隐藏加载器。如果设置为 true,"...... hideLoader() 函数才能隐藏加载器。默认情况下,它被设置为 false。
hideCardholderName此可选字段允许您在卡片表单中隐藏持卡人姓名字段。当设置为 true持卡人姓名字段未显示。当未指定或设置为 false持卡人姓名字段将显示(默认行为)。隐藏该字段不会影响主卡号、有效期、CVV验证码收集、BIN逻辑或3DS/支付机构验证。当支付机构要求提供持卡人姓名时,商户有责任确保该信息被提交。
📘

访问 API 密钥

您可以从 Yuno 控制面板的 "开发人员 "部分获取您的 API 密钥。

创建结账会话

在开始付款流程之前,您需要创建一个 checkout_session 使用 创建结账会话 endpoint。该会话将初始化支付流程,并将在下一步中使用。

💳

通过发送控制认证与捕获 payment_method.detail.card.capture 在结账环节: false = 仅授权, true = 立即捕获。

关键参数

参数需要说明
amount主交易金额对象包含 currency (ISO 4217 代码)和 value (该货币的数字金额)。
workflow将值设为 SDK_SEAMLESS 以便 SDK 能正确完成支付流程。
alternative_amount没有交易金额的另一种货币表示形式,其结构与 amount (currencyvalue).适用于多币种场景,例如以客户偏好的货币(如美元)显示价格,同时以当地货币(如 COP)处理付款。

步骤3:开始结账和付款流程

只需使用一种方法即可启动无缝结账和付款流程 startPaymentSeamlessLite.在 ViewController在显示 Yuno 的地方,调用 Yuno.startPaymentSeamlessLite() 方法。您可以使用 async/await 或回调来使用该方法:

funcstartPaymentSeamlessLite(
    with params:SeamlessParams、
   paymentSelected:PaymentMethodSelected、
   showPaymentStatus:Bool = true
) async -> 结果
funcstartPaymentSeamlessLite(
    with params:SeamlessParams、
   paymentSelected:PaymentMethodSelected、
   showPaymentStatus:Bool = true、
    callback:@escaping ((Result) -> Void)
)

无缝版本需要其他参数。这些参数包括

  • PaymentMethodSelected:金库token 和/或客户将使用的付款方式。
protocol PaymentMethodSelected {
    var vaultedToken: String? { get }
    var paymentMethodType: String { get }
}
  • SeamlessParams
class SeamlessParams {
    var checkoutSession: String
    var country_code: String
    var language: String?
    var viewController: UIViewController?
}

参数

下表列出了以下每个参数的说明 SeamlessParams:

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

Swift 6 并发要求

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

步骤4:处理支付状态(可选)

❗️

深度链接和 Mercado Pago Checkout Pro

只有在使用依赖深度链接的支付方式或 Mercado Pago Checkout Pro 时,才需要执行此步骤。如果您的付款方式不使用深度链接,您可以跳过这一步。

有些支付方法会让用户离开您的应用程序来完成交易。支付完成后,用户会通过深层链接重定向回到您的应用程序。SDK 会使用此深度链接检查发生了什么,检查支付是否成功、失败或取消,并向用户显示状态屏幕。

为此,您需要更新您的 AppDelegate 将输入的 URL 传回 Yuno SDK。这样,SDK 就能读取结果并显示支付状态。下面的代码片段展示了如何将其添加到您的应用程序中:

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)
}

这段代码会侦听打开应用程序的深度链接。当收到 URL 时,它会检查该方案是否与您在 callback_url 在结账会话设置期间。如果匹配,URL 将通过以下方式传递给 Yuno SDK Yuno.receiveDeeplink(...).然后,SDK 会读取支付结果,如果 showStatusView 设置为 true,向用户显示相应的状态屏幕。

确保 url.scheme 代码中的 callback_url 时提供的 checkout_session.

交易状态

支付完成后,SDK 可以返回不同的交易状态。下表列出了所有可能的状态及其说明:

交易状态说明
success表示交易或payment 流程已成功完成。
fail该状态表示交易或支付流程失败。这意味着在支付过程中出现了错误或问题,导致无法成功完成支付。
processing表示交易或付款流程正在处理中。通常在付款处理出现延迟时使用,如等待第三方服务或金融机构的批准。
reject这种状态表示交易被拒绝。拒绝的原因有多种,如资金不足、欺诈活动或违反特定规则或政策的请求。
internalError这意味着处理支付流程的系统或基础设施出现了意外错误。这种状态表明服务器或后台出现了问题,而不是用户的输入或请求出现了问题。
userCancell这种状态表示用户自愿取消或放弃交易或支付流程。它通常用于用户可以选择取消或放弃付款流程的情况。

付款状态验证

本节说明当用户取消或退出支付流程时,SDK如何处理支付状态,以及在这些场景中SDK状态与后端支付状态之间的关联关系。

同步支付方式(Apple Pay)

对于Apple Pay等同步支付方式,当用户在收到支付服务提供商(PSP)响应前取消或关闭钱包界面时:

  • SDK状态: 返回 userCancell (CANCELLED)
  • 后端支付状态:遗骸 PENDING 直至PSP超时或商户取消
  • 重要SDK 将不返回 rejectprocessing 在此情境下

这确保后端支付保持待处理状态,并能由商户系统妥善处理。

异步支付方式(PIX及基于二维码的支付方式)

对于PIX等异步支付方式,当用户在完成支付前关闭二维码窗口(点击X)时:

  • SDK状态: 返回 PENDING,可选地带有子状态,例如 CLOSED_BY_USER
  • 后端支付状态:遗骸 PENDING 且二维码在到期前始终有效
  • 结账会话复用:重新打开相同的结账会话可显示相同的有效二维码
  • 不自动取消:当用户关闭二维码窗口时,PIX支付不会自动取消

此功能允许用户在二维码失效前返回支付流程,使用同一二维码完成交易。

已过期的异步支付

如果PIX二维码自然过期:

  • 后端状态更新至 EXPIRED
  • SDK状态SDK 回调和轮询endpoints 点返回 EXPIRED 始终如一地

这确保了当支付方式过期时,商户能够收到准确的状态信息。

在使用 startPaymentSeamlessLite 方法:

  • 异步/等待:使用异步/等待方法可使流程更加精简。该方法以异步方式返回结果,使代码更易于阅读和管理。
  • 回调:您可以通过回调函数处理事务状态,以便在结果可用时立即执行。

这两个选项都提供了灵活性,具体取决于您对异步代码的偏好。

枚举结果 {
    case reject, success, fail, processing,internalError,userCancell

}

补充功能

Yuno iOS SDK 提供额外的服务和配置,您可以用来改善客户体验。使用SDK 定制功能可更改 SDK 外观,使其与您的品牌相匹配,或配置加载器。

  • 加载器通过 SDK 配置选项控制加载器的使用。
  • 保存贺卡,以便将来付款:此外,您还可以显示一个复选框,用于使用以下功能保存或注册卡片 cardSaveEnable: true.下面是两种卡片表单复选框的示例。
复选框示例
  • 你还可以为卡片形式选择其中一种渲染选项。下面的截图展示了 cardFormType ONE_STEPSTEP_BY_STEP.
渲染选项
  • SDK 定制:更改 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
        }
    }
}

方案 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) {
    }
}

⚠️ 重要考虑因素

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

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