iOS SDK guide

Requirements for connecting

Required iOS SDK version: 12.0.

Yandex Pay SDK uses YandexLoginSDK. Set it up by following the instructions.

Once you configure YandexLoginSDK, make sure that your app has access to Yandex Pay added in Yandex OAuth. To do this, go to the app editing page and under Which data do you need?, select Yandex PayPay by Yandex Pay.

Yandex Pay SDK installation

  1. Add a pod 'YandexPaySDK/Static' or pod 'YandexPaySDK/Dynamic' dependency to the Podfile for your target.
  2. If there's no Podfile in your project, initialize it according to the CocoaPods instructions and repeat the previous step.
  3. Set up the dependencies by executing pod install --repo-update.
  4. Open the generated .xcworkspace and assemble the project.

Add to Package.swift:

... = Package(
  ...
  targets: [
      .binaryTarget(name: "YandexPaySDK",
                    url: "https://yandexpay-ios-sdk.s3.yandex.net/1.2.1/YandexPaySDK_121222_7524285.xcframework.zip",
                    checksum: "714698b6e5ff407a76304ce8cf7801fcfc143e432157a1c6624abb04c51bfc2e"),
  ]
  ...
)

Only a dynamic library with all the necessary dependencies is currently supported. The source code can't be shared because of PCI DSS restrictions.

Yandex Pay SDK initialization

Initialize the SDK in your project's AppDelegate.swift in the application(_:didFinishLaunchingWithOptions:):

import YandexPaySDK

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    do {
        // Specify the configuration
        let merchant = YandexPaySDKMerchant(
            // Merchant ID in Yandex Pay
            id: "MERCHANT_ID",
            // Merchant name
            name: "MERCHANT_NAME",
            // Merchant URL
            url: "https://example.org/"
        )
        let configuration = YandexPaySDKConfiguration(
            // Required environment
            environment: .sandbox,
            // Merchant information
            merchant: merchant,
            // Localization
            locale: .ru
        )
        // Initialize the SDK
        try YandexPaySDKApi.initialize(configuration: configuration)
    } catch {
        // Handle the error appropriately
        assertionFailure("Unable to initialize YandexPaySDKApi.")
    }

    // Initialize UIWindow and 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>

    // Specify the configuration
    YandexPaySDKMerchant* merchant = [[YandexPaySDKMerchant alloc] initWithMerchantId: @"MERCHANT_ID" // Merchant ID in YandexPay
                                                                                 name: @"MERCHANT_NAME" // Merchant name
                                                                                  url: @"https://example.org/"]; // Merchant URL
    // Required environment
    YandexPaySDKConfiguration* configuration = [[YandexPaySDKConfiguration alloc] initWithEnvironment: YandexPaySDKEnvironmentSandbox
                                                                                             merchant: merchant // Merchant information
                                                                                               locale: YandexPaySDKLocaleRU]; // Localization
    // Initialize the SDK
    NSError *initializationError;
    [YandexPaySDKApi initializeWithConfiguration: configuration error:&initializationError];

    // Handle the error appropriately
    if (initializationError != nil) {
        [NSException raise:@"FailedToInitializePay" format:@"Subclasses must implement a valid init method"];
    }

    // Initialize UIWindow and ViewController
    UIViewController *controller = [[ViewController alloc] init];
    UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    [window setRootViewController:controller];
    [window makeKeyAndVisible];
    _window = window;

    return YES;

Add a YandexPaySDK notification about the app lifecycle events in your project's AppDelegate.swift.

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];
}

Adding Yandex Pay checkout button on the screen in a Swift project

In the ViewController of the screen where you need to add the button, create the button using the createCheckoutButton(configuration:delegate:) method of the YandexPaySDKApi class:

import YandexPaySDK

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

        // Check that the SDK is initialized
        guard YandexPaySDKApi.isInitialized else {
          assertionFailure("YandexPaySDK is not initialized.")
          return
        }

        // Specify the theme for the button
        let theme: YandexPayButtonTheme
        if #available(iOS 13.0, *) {
            // Using the `dynamic` parameter, you can specify whether the button needs
            // to change its color palette when the system theme changes
            theme = YandexPayButtonTheme(appearance: .dark, dynamic: true)
        } else {
            theme = YandexPayButtonTheme(appearance: .dark)
        }

        // Initialize the configuration
        let configuration = YandexPayButtonConfiguration(theme: theme)

        // Create the button
        let button = YandexPaySDKApi.instance.createCheckoutButton(configuration: configuration, delegate: self)

        // Specify the corner radius for the button (the default value is 8px)
        button.layer.cornerRadius = .zero

        // Add the button to the hierarchy
        view.addSubview(button)

        // Add the layout for the button
        button.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            button.widthAnchor.constraint(equalToConstant: 250)
        ])
    }
}

The button used for interacting with the client app uses the delegate YandexPayCheckoutButtonDelegate. Implement this delegate to pass it to the button at creation:

import YandexPaySDK

extension ViewController: YandexPayCheckoutButtonDelegate {

    // Process the checkout result
    func yandexPayCheckoutButton(_ button: YandexPayButton, didCompletePaymentWithResult result: YPCheckoutResult) {
        switch result {
        case .succeeded(let checkoutInfo):
            // The checkout was successful
        case .failed(let checkoutError):
            // There was an error during checkout
        case .cancelled:
            // The user closed/swiped the YandexPay form
        }
    }

    // Provide UIViewController from which you need to show the YandexPay form by clicking a button
    func yandexPayCheckoutButtonDidRequestViewControllerForPresentation(_ button: YandexPayButton) -> UIViewController? {
        return self
    }

    // Provide information about the order
    func yandexPayCheckoutButtonDidRequestPaymentSheet(_ button: YandexPayButton) -> YPCheckoutPaymentSheet? {
        return YPCheckoutPaymentSheet(
            // Currency code
            currencyCode: .rub,
            // information about the user cart
            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
        )
    }
}

Adding a YandexPay checkout button to the screen in the Objective-C project

You can only use Yandex Pay SDK from Objective-C when initializing a YandexPaySDKApi object and reporting the application lifecycle to the SDK. To create a button, YPCheckoutPaymentSheet object, and to start responding to the events emitted by the button, implement a layer in Swift that can be called from Objective-C.

To create such a layer:

  1. Make this layer available in Objective-C by inheriting this class from NSObject or an ancestor of NSObject.
  2. Use the @objc attribute to label the properties and methods of this layer that should be available from Objective-C.
  3. Make classes from Objective-C available in Swift, adding their header files to the bridging header of your project.
  4. Establish communication between the container and, for example, the UIViewController object written in Objective-C by using a delegate object or otherwise.

Example of the layer object:

import YandexPaySDK

// In this example, we use a delegate object to report events
// from the UIViewController container written in Objective-C.
// To make this protocol available in Objective-C, we label it using the @objc
// attribute.
@objc
protocol YandexPayCheckoutButtonContainerDelegate: AnyObject {
    // We will use this method to notify  
    // that the checkout token was received successfully.
    func yandexPayCheckoutButtonDidSucceedWithOrderId(_ orderId: String)

    // we will use this method to report an error
    // that arose in the process.
    func yandexPayCheckoutButtonDidFail()

    // We'll use this method to notify about cancellation.
    func yandexPayCheckoutButtonDidCancel()

    // We'll use this method to get UIViewController
    // of the object for which you need to show the Yandex Pay form.
    func yandexPayCheckoutButtonDidRequestViewController() -> UIViewController?

    // In the example, we'll use a separate method to get
    // information about user cart. The CardProduct model is written
    // in Objective-C and we use it here because it's header file
    // has been added to the bridging header file of the project.
    func yandexPayCheckoutButtonDidRequestCardProducts() -> [CardProduct]
}

// A layer object that will include the Yandex Pay button
// and interact with the button and with the Objective-C code.
final class YandexPayCheckoutButtonContainer: UIView {

    // We label the properties that should be available from Objective-C
    // with the @objc attribute.
    @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()
    }

    // This container only includes a button, so let's override
    // intrinsicContentSize. If your container is more sophisticated,
    // you can use any view layout for it at your discretion.
    override var intrinsicContentSize: CGSize {
        return payButton.intrinsicContentSize
    }

    private func setupView() {
        // Check whether the SDK has been initialized.
        guard YandexPaySDKApi.isInitialized else {
            assertionFailure("YandexPaySDKApi is not initialized.")
            return
        }

        // Specify a theme for the button.
        let payButtonTheme = YandexPayButtonTheme(appearance: .dark, dynamic: true)

        // Specify the button layout.
        let payButtonConfiguration = YandexPayButtonConfiguration(theme: payButtonTheme)

        // Create a button using YandexPaySDKApi.
        payButton = YandexPaySDKApi.instance.createCheckoutButton(configuration: payButtonConfiguration, delegate: self)

        // Add the button to the hierarchy.
        payButton.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        payButton.frame = bounds
        addSubview(payButton)
    }
}

// Implement the button's delegate to get events from it.
extension YandexPayCheckoutButtonContainer: YandexPayCheckoutButtonDelegate {
    func yandexPayCheckoutButton(_ button: YandexPayButton, didCompletePaymentWithResult result: YPCheckoutResult) {
        // Get events about a successfully received token or an error.
        switch result {
        case .succeeded(let checkoutInfo):
            // Use the delegate object to notify the Objective-C code that the token was received.
            // payment token
            delegate?.yandexPayCheckoutButtonDidSucceedWithOrderId(checkoutInfo.orderId)
        case .failed:
            // Use the delegate object to report the error to the Objective-C code.
            delegate?.yandexPayCheckoutButtonDidFail()
        case .cancelled:
            // Use the delegate object to notify the Objective-C code about a cancellation.
            delegate?.yandexPayCheckoutButtonDidCancel()
        @unknown default:
            break
        }
    }

    func yandexPayCheckoutButtonDidRequestViewControllerForPresentation(_ button: YandexPayButton) -> UIViewController? {
        // Use the delegate object to request, from the Objective-C code, the UIViewController
        // object in which the Yandex Pay form should be shown.
        return delegate?.yandexPayCheckoutButtonDidRequestViewController()
    }

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

        // Construct the YPCheckoutPaymentSheet object here and return it to
        // the button. Naturally, when creating the YPCheckoutPaymentSheet object,
        // you need to use a certain business logic written in
        // Objective-C. To interact with this business logic,
        // you can use the same delegate object
        // or other Objective-C
        // classes (for example, some services or interactors)
        // that encapsulate this logic. To do this,
        // make these classes available in Swift by adding their
        // header files to the bridging header file of your project.
        // Note that if you implement your business logic in
        // Objective-C, it will use models implemented in
        // Objective-C. That's why here you can implement their mapping
        // to YandexPaySDK models.

        // In this example, we use a delegate object.
        let products = delegate.yandexPayCheckoutButtonDidRequestCardProducts()

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

Here's an example of a UIViewController object interacting with the layer:

#import <YandexPaySDK/YandexPaySDK-Swift.h>

// Add conformance to the protocol.
@interface ViewController (ViewControllerYandexPayButtonContainer) <YandexPayCheckoutButtonContainerDelegate>

@end

// Declare private methods.
@interface ViewController ()

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

@end

// Implement the ViewController class.
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor systemBackgroundColor];

    // Use the layer object to show the Yandex Pay button.
    YandexPayCheckoutButtonContainer *container = [[YandexPayCheckoutButtonContainer alloc] init];

    // Specify the delegate object for the layer.
    container.delegate = self;

    // Add the layer object to the hierarchy.
    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 {
    // We've got a token and can now check out
    // using this token.
    [self showAlertWithMessage:[NSString stringWithFormat:@"Checkout complete: %@", orderId]];

    // Access the payment gateway
    // ...
}

- (void)yandexPayCheckoutButtonDidFail {
    // We received an error event.
    [self showAlertWithMessage:@"Checkout failed"];
}

- (void)yandexPayCheckoutButtonDidCancel {
    // We received a cancellation event.
    [self showAlertWithMessage:@"Checkout was cancelled by the user"];
}

- (UIViewController *)yandexPayCheckoutButtonDidRequestViewController {
    // Return the current UIViewController to render the form.
    return self;
}

- (NSArray<CardProduct *> *)yandexPayCheckoutButtonDidRequestCardProducts {
    // If needed, transmit the order data and other
    // data using the delegate or by any other convenient method.
    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

As the CardProduct structure, you can use any of your models implemented in Objective-C, and here it is used as an illustration.

CardProduct.h file:

@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

You can use an asynchronous delegate object, if you cannot instantly provide YPCheckoutPaymentSheet:

  1. Create a button using a specialized method of theYandexPaySDKApi class:

    import YandexPaySDK
    
    payButtonAsync = YandexPaySDKApi.instance.createCheckoutButton(configuration: configuration, asyncDelegate: self)
    
  2. Implement the delegate 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) {
        // Show a loader on the button using the provided method or use your custom loader if needed
        payButtonAsync.setLoaderVisible(true, animated: true)
    
        // Make an asynchronous call
        DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) { [weak self] in
          // Get YPCheckoutPaymentSheet
          let paymentSheet = YPCheckoutPaymentSheet(...)
    
          DispatchQueue.main.async {
            // Hide the loader
            self?.payButtonAsync.setLoaderVisible(false, animated: true)
    
            // Call the completion block
            completion(paymentSheet)
          }
        }
      }