Yandex Pay iOS SDK integration

Yandex Pay iOS SDK is a library that enables you to embed a Yandex Pay payment button into your application and start accepting payments from users.

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 a Yandex Pay button to the screen in a Swift project

In the ViewController of the screen where you need to add the button, create the button using the createButton(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 has
            // 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.createButton(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 YandexPayButtonDelegate. Implement this delegate to pass it to the button at creation:

import YandexPaySDK

extension ViewController: YandexPayButtonDelegate {
    // Process the payment result
    func yandexPayButton(_ button: YandexPayButton, didCompletePaymentWithResult result: YPPaymentResult) {
        switch result {
        case .succeeded(let paymentInfo):
            // Payment was successful
        case .failed(let paymentError):
            // There was an error during payment
        case .cancelled:
            // The user closed/swiped away the YandexPay form?
        }
    }

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

    // Provide information about the merchant and the cart
    func yandexPayButtonDidRequestPaymentSheet(_ button: YandexPayButton) -> YPPaymentSheet? {
        return YPPaymentSheet(
            // Country code
            countryCode: .ru,
            // Currency code
            currencyCode: .rub,
            // Order details
            order: YPOrder(
                // Order ID
                id: "ORDER-ID",
                // Order cost
                amount: "10000.00"
            ),
            // Available payment methods
            paymentMethods: [
                // Only payment by card is currently available
                .card(
                    YPCardPaymentMethod(
                        // ID of the payment service provider
                        gateway: "test-gateway",
                        // ID of the merchant in the payment service provider system
                        gatewayMerchantId: "test-gateway-merchant-id",
                        // What will be contained in the payment token: encrypted bank card data or a tokenized card
                        allowedAuthMethods: [
                            .panOnly
                        ],
                        // List of supported payment systems
                        allowedCardNetworks: [
                            .mastercard,
                            .visa,
                            .mir
                        ]
                    )
                )
            ]
        )
    }
}

Adding a YandexPay 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, YPPaymentSheet 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 YandexPayButtonContainerDelegate: AnyObject {
    // We will use this method to notify
      // that the payment token was received successfully.
    func yandexPayButtonDidSucceedWithPaymentToken(_ paymentToken: String)

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

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

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

    // In the example, we'll use a separate method to get
    // the order data. The Order model is written in Objective-C and
    // we use it here because its header file     // has been added to the bridging
 header file of the project.
    func yandexPayButtonDidRequestOrder() -> Order
}

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

    // We label the properties that should be available from Objective-C
    // with the @objc attribute.
    @objc
    weak var delegate: YandexPayButtonContainerDelegate?

    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.createButton(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 YandexPayButtonContainer: YandexPayButtonDelegate {
    func yandexPayButton(_ button: YandexPayButton, didCompletePaymentWithResult result: YPPaymentResult) {
        // Get events about a successfully received token or about an error.
        switch result {
        case .succeeded(let paymentInfo):
            // Use the delegate object to notify the Objective-C code that we received the...
            // payment token
            delegate?.yandexPayButtonDidSucceedWithPaymentToken(paymentInfo.paymentToken)
        case .failed:
            // Use the delegate object to report the error to the Objective-C code.
            delegate?.yandexPayButtonDidFail()
        case .cancelled:
            // Use the delegate object to notify the Objective-C code about the cancellation.
            delegate?.yandexPayButtonDidCancel()
        @unknown default:
            break
        }
    }

    func yandexPayButtonDidRequestViewControllerForPresentation(_ 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?.yandexPayButtonDidRequestViewController()
    }

    func yandexPayButtonDidRequestPaymentSheet(_ button: YandexPayButton) -> YPPaymentSheet? {
        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 YPPaymentSheet 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 order = delegate.yandexPayButtonDidRequestOrder()

        // Construct YPPaymentSheet
        return YPPaymentSheet(
            countryCode: .ru,
            currencyCode: .rub,
            // Map the model written in Objective-C to the model in Swift.
            order: YPOrder(
                id: order.orderId!,
                label: order.label,
                amount: order.amount!,
                items: order.items?.compactMap({ $0 as? OrderItem }).map({ YPOrderItem(label: $0.label, amount: $0.amount) })
            ),
            paymentMethods: [
                .card(
                    YPCardPaymentMethod(
                        gateway: "yandex-trust",
                        gatewayMerchantId: "MerchantGW1",
                        allowedAuthMethods: [
                            .panOnly
                        ],
                        allowedCardNetworks: [
                            .visa,
                            .mastercard,
                            .mir
                        ]
                    )
                )
            ]
        )
    }
}

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

@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.
    YandexPayButtonContainer *container = [[YandexPayButtonContainer 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)yandexPayButtonDidSucceedWithPaymentToken:(NSString *)paymentToken {
    // We've got a token and can now check out
    // using this token.
    [self showAlertWithMessage:[NSString stringWithFormat:@"Payment complete: %@", paymentToken]];

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

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

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

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

- (Order *)yandexPayButtonDidRequestOrder {
    //  If needed, transmit the order data and other
    // data using the delegate or by any other convenient method.
    NSMutableArray *items = [[NSMutableArray alloc] init];
    [items addObject:[[OrderItem alloc] initWithLabel:@"Wellington boots" amount:@"7000"]];
    [items addObject:[[OrderItem alloc] initWithLabel:@"Belt" amount:@"3000"]];
    return [[Order alloc] initWithOrderId:@"ORDER1" label:@"ORDER1" amount:@"10000" items:[NSArray arrayWithArray:items]];
}

@end

As the Order and OrderItem structures, you can use any of your models implemented in Objective-C. Here we use sample objects for illustrative purposes.

Order.h file:

@interface Order : NSObject

@property (readonly, copy, nonatomic) NSString *orderId;
@property (readonly, copy, nonatomic) NSString *label;
@property (readonly, copy, nonatomic) NSString *amount;
@property (readonly, copy, nonatomic) NSArray *items;

- (instancetype)initWithOrderId:(NSString *)orderId
                          label:(NSString *)label
                         amount:(NSString *)amount
                          items:(NSArray *)items;

@end

OrderItem.h file:

@interface OrderItem : NSObject

@property (readonly, copy, nonatomic) NSString *label;
@property (readonly, copy, nonatomic) NSString *amount;

- (instancetype)initWithLabel:(NSString *)label
                       amount:(NSString *)amount;

@end

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

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

    import YandexPaySDK
    
    payButtonAsync = YandexPaySDKApi.instance.createButton(configuration: configuration, asyncDelegate: self)
    
  2. Implement the delegate YandexPayButtonAsyncDelegate:

    import YandexPaySDK
    
    extension ViewController: YandexPayButtonAsyncDelegate
      func yandexPayButton(_ button: YandexPayButton, didCompletePaymentWithResult result: YPPaymentResult) {
        // ...
      }
    
      func yandexPayButtonDidRequestViewControllerForPresentation(_ button: YandexPayButton) -> UIViewController? {
        // ...
      }
    
      func yandexPayButtonDidRequestPaymentSheet(_ button: YandexPayButton, completion: @escaping (YPPaymentSheet?) -> Void) {
        // Show a loader on the button using the provided method or show 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 YPPaymentSheet
          let paymentSheet = YPPaymentSheet(...)
    
          DispatchQueue.main.async {
            // Hide the loader
            self?.payButtonAsync.setLoaderVisible(false, animated: true)
    
            // Call the completion block
            completion(paymentSheet)
          }
        }
      }