Руководство по iOS SDK

Требования к подключению

Требуемая версия iOS SDK: 12.0.
Yandex Pay SDK использует YandexLoginSDK. Настройте его по инструкции.

Установка Yandex Pay SDK с помощью CocoaPods

Выполните следующие действия:

  1. В Podfile для вашего таргета добавьте зависимость вида pod 'YandexPaySDK/Static' или pod 'YandexPaySDK/Dynamic'.
  2. Если в вашем проекте нет Podfile, то проинициализируйте его в соответствии с инструкциями CocoaPods и повторите предыдущий шаг.
  3. Установите зависимости, выполнив pod install --repo-update.
  4. Откройте сгенерированный .xcworkspace и соберите проект.

Инициализация Yandex Pay SDK

Инициализируйте SDK в AppDelegate.swift вашего проекта в методе application(_:didFinishLaunchingWithOptions:):

import YandexPaySDK

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    do {
        // Укажите конфигурацию
        let merchant = YandexPaySDKMerchant(
            // ID продавца в системе Yandex Pay
            id: "MERCHANT_ID",
            // Имя продавца
            name: "MERCHANT_NAME",
            // URL продавца
            url: "https://example.org/"
        )
        let configuration = YandexPaySDKConfiguration(
            // Необходимое окружение
            environment: .sandbox,
            // Информация о мерчанте
            merchant: merchant,
            // Локализация
            locale: .ru
        )
        // Инициализируйте SDK
        try YandexPaySDKApi.initialize(configuration: configuration)
    } catch {
        // Отреагируйте на ошибку должным образом
        assertionFailure("Unable to initialize YandexPaySDKApi.")
    }

    // Инициализируйте UIWindow и ViewController
    let controller = ViewController()
    let window = UIWindow(frame: UIScreen.main.bounds)
    window.rootViewController = controller
    window.makeKeyAndVisible()
    self.window = window

    return true
}
#import <YandexPaySDK/YandexPaySDK-Swift.h>

    // Укажите конфигурацию
    YandexPaySDKMerchant* merchant = [[YandexPaySDKMerchant alloc] initWithMerchantId: @"MERCHANT_ID" // ID мерчанта в системе YandexPay 
                                                                                 name: @"MERCHANT_NAME" // Имя мерчанта
                                                                                  url: @"https://example.org/"]; // URL мерчанта
    // Необходимое окружение
    YandexPaySDKConfiguration* configuration = [[YandexPaySDKConfiguration alloc] initWithEnvironment: YandexPaySDKEnvironmentSandbox
                                                                                             merchant: merchant // Информация о мерчанте
                                                                                               locale: YandexPaySDKLocaleRU]; // Локализация
    // Инициализируйте SDK
    NSError *initializationError;
    [YandexPaySDKApi initializeWithConfiguration: configuration error:&initializationError];

    // Отреагируйте на ошибку должным образом
    if (initializationError != nil) {
        [NSException raise:@"FailedToInitializePay" format:@"Subclasses must implement a valid init method"];
    }

    // Инициализируйте UIWindow и ViewController
    UIViewController *controller = [[ViewController alloc] init];
    UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    [window setRootViewController:controller];
    [window makeKeyAndVisible];
    _window = window;

    return YES;

Также в AppDelegate.swift вашего проекта добавьте нотификацию YandexPaySDK о событиях жизненного цикла приложения:

import YandexPaySDK

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

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    YandexPaySDKApi.instance.applicationDidReceiveOpen(url, sourceApplication: options[.sourceApplication] as? String)
    return true
}

func applicationWillEnterForeground(_ application: UIApplication) {
    YandexPaySDKApi.instance.applicationWillEnterForeground()
}

func applicationDidBecomeActive(_ application: UIApplication) {
    YandexPaySDKApi.instance.applicationDidBecomeActive()
}
#import <YandexPaySDK/YandexPaySDK-Swift.h>

- (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity {
    [[YandexPaySDKApi instance] applicationDidReceiveUserActivity:userActivity];
}

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    [[YandexPaySDKApi instance] applicationDidReceiveOpen:url sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]];
    return YES;
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    [[YandexPaySDKApi instance] applicationWillEnterForeground];
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    [[YandexPaySDKApi instance] applicationDidBecomeActive];
}

Добавление кнопки чекаута Yandex Pay на экран в Swift проекте

Во ViewController экрана, на котором необходимо разместить кнопку, создайте ее с помощью метода createCheckoutButton(configuration:delegate:) класса YandexPaySDKApi:

import YandexPaySDK

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // Проверьте, что SDK проинициализирован
        guard YandexPaySDKApi.isInitialized else {
          assertionFailure("YandexPaySDK is not initialized.")
          return
        }

        // Укажите тему для кнопки
        let theme: YandexPayButtonTheme
        if #available(iOS 13.0, *) {
            // Параметр `dynamic` позволяет указать, нужно ли кнопке
            // менять свою цветовую палитру вместе со сменой системной темы
            theme = YandexPayButtonTheme(appearance: .dark, dynamic: true)
        } else {
            theme = YandexPayButtonTheme(appearance: .dark)
        }

        // Инициализируйте конфигурацию
        let configuration = YandexPayButtonConfiguration(theme: theme)

        // Создайте кнопку
        let button = YandexPaySDKApi.instance.createCheckoutButton(configuration: configuration, delegate: self)

        // Укажите скругления для кнопки (по умолчанию - 8px)
        button.layer.cornerRadius = .zero

        // Добавьте кнопку в иерархию
        view.addSubview(button)

        // Установите layout для кнопки
        button.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            button.widthAnchor.constraint(equalToConstant: 250)
        ])
    }
}

Кнопка для взаимодействия с клиентским приложением использует делегат YandexPayCheckoutButtonDelegate. Реализуйте данный делегат, чтобы передать его кнопке при ее создании:

import YandexPaySDK

extension ViewController: YandexPayCheckoutButtonDelegate {

    // Обработайте результат чекаута
    func yandexPayCheckoutButton(_ button: YandexPayButton, didCompletePaymentWithResult result: YPCheckoutResult) {
        switch result {
        case .succeeded(let checkoutInfo):
            // чекаут была совершена успешно
        case .failed(let checkoutError):
            // В процессе чекаута произошла ошибка
        case .cancelled:
            // Пользователь закрыл/смахнул форму YandexPay
        }
    }

    // Предоставьте UIViewController, с которого необходимо показать форму YandexPay по нажатию на кнопку
    func yandexPayCheckoutButtonDidRequestViewControllerForPresentation(_ button: YandexPayButton) -> UIViewController? {
        return self
    }

    // Предоставьте информацию о заказе
    func yandexPayCheckoutButtonDidRequestPaymentSheet(_ button: YandexPayButton) -> YPCheckoutPaymentSheet? {
        return YPCheckoutPaymentSheet(
            // Код валюты
            currencyCode: .rub,
            // Информация о корзине покупателя
            cart: YPPaymentCart(items: [
                YPProduct(id: "1", total: "434.39", quantity: YPQuantity(count: "1")),
                YPProduct(id: "2", total: "1474.86", quantity: YPQuantity(count: "2")),
            ]),
            orderId: nil,
            metadata: nil
        )
    }
}

Добавление кнопки чекаута YandexPay на экран в Objective-C проекте

Использовать Yandex Pay SDK из Objective-C можно только при инициализации YandexPaySDKApi объекта и сообщении о жизненном цикле приложения SDK. Для создания кнопки, YPCheckoutPaymentSheet объекта и реагирования на события, которые приходят от кнопки, реализуйте прослойку на Swift, доступную для вызова из Objective-C.

Чтобы сделать такую прослойку:

  1. Сделайте ее доступной из Objective-C, унаследовав данный класс от NSObject или предка NSObject.
  2. Пометьте атрибутом @objc свойства и методы данной прослойки, которые должны быть доступны из Objective-C.
  3. Сделайте доступными в Swift классы из Objective-C, добавив их header-файлы в bridging header вашего проекта.
  4. Установите коммуникацию между контейнером и, например, UIViewController объектом, написанным на Objective-C, с помощью объекта-делегата или любым другим способом.

Пример объекта-прослойки:

import YandexPaySDK

// В этом примере мы использует объект-делегат, чтобы сообщать о событиях
// из контейнера в UIViewController, который написан на Objective-C.
// Чтобы данный протокол был доступен в Objective-C, помечаем его @objc
// аттрибутом.
@objc
protocol YandexPayCheckoutButtonContainerDelegate: AnyObject {
    // Данный метод будем использовать для сообщения об успешном 
    // получении токена чекаута.
    func yandexPayCheckoutButtonDidSucceedWithOrderId(_ orderId: String)

    // Данный метод будем использовать для сообщения об ошибке,
    // полученной в процессе.
    func yandexPayCheckoutButtonDidFail()

    // Данный метод будем использовать для сообщения об отмене.
    func yandexPayCheckoutButtonDidCancel()

    // Данный метод будем использовать для получения UIViewController
    // объекта, из которого необходимо показать форму Yandex Pay.
    func yandexPayCheckoutButtonDidRequestViewController() -> UIViewController?

    // Для примера будем использовать отдельный метод для получения
    // информации о корзине покупателя. Модель CardProduct написана
    // на Objective-C и используется здесь, так как ее header-файл 
    // добавлен в bridging header файл проекта.
    func yandexPayCheckoutButtonDidRequestCardProducts() -> [CardProduct]
}

// Объект-прослойка, который будет содержать в себе кнопку Yandex Pay и
// взаимодействовать с ней, а также взаимодействовать с Objective-C кодом.
final class YandexPayCheckoutButtonContainer: UIView {

    // Свойства, которые должны быть доступны из Objective-C помечаем
    // аттрибутом @objc.
    @objc
    weak var delegate: YandexPayCheckoutButtonContainerDelegate?

    private var payButton: YandexPayButton!

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupView()
    }

    // Данный контейнер содержит только кнопку, поэтому переопределим
    // intrinsicContentSize. Если же у вас более сложный контейнер,
    // то его разметка может быть любой на ваше усмотрение.
    override var intrinsicContentSize: CGSize {
        return payButton.intrinsicContentSize
    }

    private func setupView() {
        // Проверяем, проинициализирован ли SDK.
        guard YandexPaySDKApi.isInitialized else {
            assertionFailure("YandexPaySDKApi is not initialized.")
            return
        }

        // Указываем тему для кнопки.
        let payButtonTheme = YandexPayButtonTheme(appearance: .dark, dynamic: true)

        // Указываем конфигурацию кнопки.
        let payButtonConfiguration = YandexPayButtonConfiguration(theme: payButtonTheme)

        // Создаем кнопку с помощью YandexPaySDKApi.
        payButton = YandexPaySDKApi.instance.createCheckoutButton(configuration: payButtonConfiguration, delegate: self)

        // Размещаем кнопку в иерархии.
        payButton.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        payButton.frame = bounds
        addSubview(payButton)
    }
}

// Реализуем делегат кнопки, чтобы получать от нее события.
extension YandexPayCheckoutButtonContainer: YandexPayCheckoutButtonDelegate {
    func yandexPayCheckoutButton(_ button: YandexPayButton, didCompletePaymentWithResult result: YPCheckoutResult) {
        // Получаем события об успеном получении токена или об ошибке.
        switch result {
        case .succeeded(let checkoutInfo):
            // Сообщаем через делегат Objective-C коду о получении.
            // платежного токена
            delegate?.yandexPayCheckoutButtonDidSucceedWithOrderId(checkoutInfo.orderId)
        case .failed:
            // Сообщаем через делегат Objective-C коду об ошибке.
            delegate?.yandexPayCheckoutButtonDidFail()
        case .cancelled:
            // Сообщаем через делегат Objective-C коду об отмене.
            delegate?.yandexPayCheckoutButtonDidCancel()
        @unknown default:
            break
        }
    }

    func yandexPayCheckoutButtonDidRequestViewControllerForPresentation(_ button: YandexPayButton) -> UIViewController? {
        // Запрашиваем через делегат у Objective-C кода UIViewController
        // объект, в котором необходимо показывать форму Yandex Pay.
        return delegate?.yandexPayCheckoutButtonDidRequestViewController()
    }

    func yandexPayCheckoutButtonDidRequestPaymentSheet(_ button: YandexPayButton) -> YPCheckoutPaymentSheet? {
        guard let delegate = delegate else {
            assertionFailure("Delegate is missing.")
            return nil
        }

        // Здесь конструируем YPCheckoutPaymentSheet объект и возвращаем его
        // кнопке. Для создания YPCheckoutPaymentSheet объекта, очевидно,
        // нужно использовать некоторую бизнес-логику, которая написана
        // на Objective-C. Для того, чтобы с данной бизнес-логикой
        // взаимодействовать, вы можете использовать тот же объект
        // делегата или же можете использовать другие Objective-C
        // классы (например, какие-нибудь сервисы или интеракторы), 
        // которые данную лоигку инкапсулируют. Для этого
        // сделайте данные классы доступными в Swift, добавив их
        // header-файлы в bridging header файл вашего проекта.
        // Стоит отметить, что бизнес-логика, реализованная в 
        // Objective-C будет использовать модели, реализованные на
        // Objective-C. Поэтому здесь вы можете реализовать их маппинг
        // в модели YandexPaySDK.

        // В текущем примере мы используем объект делегата.
        let products = delegate.yandexPayCheckoutButtonDidRequestCardProducts()

        // Конструируем YPCheckoutPaymentSheet
        return YPCheckoutPaymentSheet(
            currencyCode: .rub,
            cart: YPPaymentCart(items: products.map { 
                YPProduct(id: $0.productID, total: $0.total, quantity: YPQuantity(count: $0.quantity)) 
            }),
            orderId: nil,
            metadata: nil
        )
    }
}

Пример UIViewController объекта, взаимодействующего с прослойкой:

#import <YandexPaySDK/YandexPaySDK-Swift.h>

// Добавляем соответствие протоколу.
@interface ViewController (ViewControllerYandexPayButtonContainer) <YandexPayCheckoutButtonContainerDelegate>

@end

// Объявляем приватные методы.
@interface ViewController ()

- (void)showAlertWithMessage:(NSString *)message;

@end

// Реализация ViewController класса.
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor systemBackgroundColor];

    // Используем объект-прослойку для показа кнопки Yandex Pay.
    YandexPayCheckoutButtonContainer *container = [[YandexPayCheckoutButtonContainer alloc] init];

    // Указываем объект делегата для прослойки.
    container.delegate = self;

    // Добавляем объект-прослойку в иерархию.
    container.translatesAutoresizingMaskIntoConstraints = false;
    [self.view addSubview:container];
    [[container.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor] setActive:TRUE];
    [[container.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor] setActive:TRUE];
}

- (void)showAlertWithMessage:(NSString *)message {
    UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Message" message:message preferredStyle:UIAlertControllerStyleAlert];
    [controller addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:controller animated:TRUE completion:nil];
}

- (void)yandexPayCheckoutButtonDidSucceedWithOrderId:(NSString *)orderId {
    // Получили токен и можем провести чекаут с помощью 
    // этого токена.
    [self showAlertWithMessage:[NSString stringWithFormat:@"Checkout complete: %@", orderId]];

    // Обращение к платежному шлюзу
    // ...
}

- (void)yandexPayCheckoutButtonDidFail {
    // Получили событие об ошибке.
    [self showAlertWithMessage:@"Checkout failed"];
}

- (void)yandexPayCheckoutButtonDidCancel {
    // Получили событие об отмене.
    [self showAlertWithMessage:@"Checkout was cancelled by the user"];
}

- (UIViewController *)yandexPayCheckoutButtonDidRequestViewController {
    // Возвращаем текущий UIViewController для показа формы.
    return self;
}

- (NSArray<CardProduct *> *)yandexPayCheckoutButtonDidRequestCardProducts {
    // Если необходимо, передавайте информацию о заказе и другую
    // информацию через делегат или любым другим удобным способом.
    NSMutableArray *products = [[NSMutableArray alloc] init];
    [items addObject:[[CardProduct alloc] initWithProductID:@"ID-1" total:@"1000.00" quantity: @"1"]];
    [items addObject:[[CardProduct alloc] initWithProductID:@"ID-2" total:@"500.00" quantity: @"2"]];
    return products;
}

@end

Структура CardProduct может быть любой вашей моделью, реализованной на Objective-C, а здесь она используется в качестве иллюстрации.

Файл CardProduct.h:

@interface Product : NSObject

@property (readonly, copy, nonatomic) NSString *productID;
@property (readonly, copy, nonatomic) NSString *total;
@property (readonly, copy, nonatomic) NSString *quantity;

- (instancetype)initWithProductID:(NSString *)productID
                            total:(NSString *)total
                         quantity:(NSString *)quantity;

@end

Вы можете использовать асинхронный делегат, если не можете моментально предоставить YPCheckoutPaymentSheet:

  1. Создайте кнопку, используя специализированный метод класса YandexPaySDKApi:

    import YandexPaySDK
    
    payButtonAsync = YandexPaySDKApi.instance.createCheckoutButton(configuration: configuration, asyncDelegate: self)
    
  2. Реализуйте делегат YandexPayButtonAsyncDelegate:

    import YandexPaySDK
    
    extension ViewController: YandexPayButtonAsyncDelegate
      func yandexPayCheckoutButton(_ button: YandexPayButton, didCompletePaymentWithResult result: YPCheckoutResult) {
         // ...
      }
    
      func yandexPayCheckoutButtonDidRequestViewControllerForPresentation(_ button: YandexPayButton) -> UIViewController? {
        // ...
      }
    
      func yandexPayCheckoutButtonDidRequestPaymentSheet(_ button: YandexPayButton, completion: @escaping (YPCheckoutPaymentSheet?) -> Void) {
        // Покажите лоадер на кнопке, используя предоставленный метод, или же покажите свой лоадер, если необходимо
        payButtonAsync.setLoaderVisible(true, animated: true)
    
        // Сделайте асинхронный вызов
        DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) { [weak self] in
          // Получите YPCheckoutPaymentSheet
          let paymentSheet = YPCheckoutPaymentSheet(...)
    
          DispatchQueue.main.async {
            // Скройте лоадер
            self?.payButtonAsync.setLoaderVisible(false, animated: true)
    
            // Вызовите completion-блок
            completion(paymentSheet)
          }
        }
      }