Подключение QR‑код от Яндекс Пэй iOS SDK

Шаг 1. Добавьте зависимость

Через Xcode (рекомендуется):

  1. FileAdd Package Dependencies
  2. URL: https://github.com/yandexmobile/yandex-quickpay-sdk-ios
  3. Выберите актуальную версию и добавьте к таргету.

Через Package.swift:

.package(
    url: "https://github.com/yandexmobile/yandex-quickpay-sdk-ios",
    from: "LATEST_VERSION"
)

Актуальную версию смотрите в releases.

Шаг 2. Настройте Info.plist и Entitlements

Info.plist

Добавьте OAuth Client ID (инструкция):

<key>YandexClientID</key>
<string>YOUR_CLIENT_ID</string>

URL Scheme для OAuth-редиректа (замените YOUR_CLIENT_ID на ваш Client ID):

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>yx.YOUR_CLIENT_ID</string>
        </array>
    </dict>
</array>

Список приложений для проверки SDK:

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>yandexauth</string>
    <string>yandexauth2</string>
</array>

Entitlements

Настройте Associated Domains и Keychain Sharing. Подробная инструкция.

Шаг 3. Инициализируйте SDK

Вызовите YandexQuickPay.initialize() один раз при запуске. SDK использует синглтон — доступ через YandexQuickPay.instance.

Важно

Инициализацию выполняйте только один раз. Повторный вызов initialize() заменяет предыдущий экземпляр.

AppDelegate

import YandexQuickPaySDK

class AppDelegate: UIResponder, UIApplicationDelegate {

    lazy var quickPayListener = MyQuickPayListener()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let config = QuickPayConfig(
            merchantId: "YOUR_MERCHANT_ID",
            environment: .sandbox,  // Используйте .production перед релизом
            locale: .system,
            theme: .system
        )
        YandexQuickPay.initialize(
            configuration: config,
            presenterViewController: nil,
            quickPaymentStateListener: quickPayListener
        )
        return true
    }

    func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
        return YandexQuickPay.instance.handleOpenURL(url)
    }

    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
        return YandexQuickPay.instance.handleUserActivity(userActivity)
    }
}

Важно

handleOpenURL(_ url: URL) и handleUserActivity(_:) необходимы для OAuth-авторизации в процессе enableQuickPayment(). Без них авторизация через Яндекс ID не завершится.

SceneDelegate

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else { return }
    _ = YandexQuickPay.instance.handleOpenURL(url)
}

func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    _ = YandexQuickPay.instance.handleUserActivity(userActivity)
}

SwiftUI App

@main
struct MyApp: App {

    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    _ = YandexQuickPay.instance.handleOpenURL(url)
                }
        }
    }
}

Шаг 4. Жизненный цикл листенера

QuickPaymentStateListener — протокол с AnyObject. SDK хранит листенер как weak-ссылку.

Если у листенера нет другого владельца, ARC освободит объект и колбэки не будут приходить.

// Неправильно — объект немедленно освобождается
YandexQuickPay.initialize(
    configuration: config,
    presenterViewController: nil,
    quickPaymentStateListener: MyListener()  // ← временный объект
)

// Правильно — сохраняем сильную ссылку
class AppDelegate: UIApplicationDelegate {
    lazy var quickPayListener = MyQuickPayListener()

    func application(...) -> Bool {
        YandexQuickPay.initialize(
            configuration: config,
            presenterViewController: nil,
            quickPaymentStateListener: quickPayListener
        )
        return true
    }
}

Реализация листенера

Колбэки могут вызываться не на Main thread. Обновления UI — через DispatchQueue.main.async или await MainActor.run.

final class QuickPayListener: QuickPaymentStateListener {

    weak var delegate: QuickPayListenerDelegate?

    func onPaymentEnabledStateChanged(isEnabled: Bool) {
        DispatchQueue.main.async {
            self.delegate?.quickPayEnabledStateChanged(isEnabled: isEnabled)
        }
    }

    func onSessionExpired() {
        DispatchQueue.main.async { self.delegate?.quickPaySessionExpired() }
    }

    func onPaymentResult(quickpayResult: QuickPayResult) {
        DispatchQueue.main.async {
            self.delegate?.quickPayPaymentResult(quickpayResult)
        }
    }
}

initialize() можно вызывать повторно — для смены конфигурации или обновления листенера.

Шаг 5. Добавьте виджет методов оплаты

createPaymentMethodsWidget() не бросает исключений. Два варианта — UIKit и SwiftUI.

UIKit

let widget: UIView = YandexQuickPay.instance.createPaymentMethodsWidget()
widget.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(widget)
// Настройте constraints

SwiftUI

AnyView(YandexQuickPay.instance.createPaymentMethodsWidget())
    .frame(maxWidth: .infinity)

Шаг 6. Управляйте состоянием быстрой оплаты

  • Включение: try await YandexQuickPay.instance.enableQuickPayment() — может показать OAuth или биометрию.
  • Отключение: try await YandexQuickPay.instance.disableQuickPayment()
  • Выход: try await YandexQuickPay.instance.logout() — очищает данные, требуется повторная авторизация.

Шаг 7. Работайте с платёжной сессией и QR-кодом

do {
    let sessionId = try await YandexQuickPay.instance.getPaymentSessionId()
    let qrImage = generateQrImage(from: sessionId)
    await MainActor.run { qrImageView.image = qrImage }
} catch FintechQuickPaymentError.noAuth {
    showEnablePaymentButton()
} catch FintechQuickPaymentError.noPresentationContext {
    // Вызов до отображения ViewController — переместите в viewDidAppear
} catch FintechQuickPaymentError.cannotCreateNewSession {
    // Ошибка сети/сервера — повторить запрос
} catch {
    showError(error)
}

Когда обновлять QR: первое отображение экрана, onSessionExpired(), onPaymentResult(), onPaymentEnabledStateChanged(true).

Обработка ошибок

Ошибка Когда Действие
.noAuth Пользователь не авторизован enableQuickPayment()
.cannotAuth Ошибка авторизации Проверить Info.plist, Entitlements, URL Scheme
.cannotCreateNewSession(underlying:) Ошибка сети/сервера Повторить запрос
.noPresentationContext Нет активного ViewController Вызов после viewDidAppear / .onAppear

Безопасность

  • merchantId — публичный, не секрет.
  • sessionId — не логировать в production, не передавать третьим.
  • SDK не хранит данные карт.
  • Биометрия — системные средства iOS.

Тестирование

Используйте .sandbox для разработки. Перед релизом — .production.

Сценарий Ожидаемое поведение
Первая авторизация Показ экрана авторизации Яндекс ID
Успешный платёж onPaymentResult(.success)
Истечение сессии onSessionExpired(), затем новый QR
Выход logout() — очистка данных
Предыдущая