Apple Pay

Apple Pay

Apple Pay is easy to set up and gives your customers a simple and secure way to pay using their iPhone or iPad.
This guide explains how to process Apple Pay payments with our SDK.

Configuration

In order to use Apple Pay on a real device, you will need to create keys and a certificate. Here are the steps:

  1. Add an Apple Pay Merchant ID in Apple Developer Center.
  2. In the Open Payment Platform, navigate to Administration -> Mobile Payment, select Generate keys and download CSR (certificate signing request).
  3. In the Apple Developer Center, select the merchant ID and choose Create Certificate. Upload the CSR from the previous step, and download the certificate. See the Apple Pay guide for more detailed instructions.
  4. Upload the certificate in the Open Payment Platform
  5. Turn on Apply Pay in Xcode: enable Apple Pay under Capabilities in your Project Setting, then select the merchant ID you want the app to use.

Adding Apple Pay to your app must be done in one of two ways, depending on whether you are using the Ready-to-Use UI or the SDK & Your Own UI. These two ways are covered in the sections below. Please follow the instructions relevant to the approach you have chosen.


Ready-to-Use UI

  1. Add PKPaymentButton to your view.
  2. Add a button action method to your view controller. In code sample you can see main steps that should be implemented in this method.
  3. - (IBAction) applePayTapped:(id)sender {
        // request checkout ID from your server
        // configure OPPCheckoutProvider
        // present checkout for specific payment brand and implement callbacks
    }
    
    @IBAction func applePayTapped(_ sender: Any) {    
        // request checkout ID from your server
        // configure OPPCheckoutProvider
        // present checkout for specific payment brand and implement callbacks
    }
    

Request checkout ID

To initiate payment checkout ID should be created. Your app should request a checkout ID from your server. This example uses our sample integration server; please adapt it to use your own backend API.

NSURLRequest *merchantServerRequest = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://YOUR_URL/?amount=100&currency=EUR&paymentType=DB"]];
[[[NSURLSession sharedSession] dataTaskWithRequest:merchantServerRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // TODO: Handle errors
    NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
    self.checkoutID = JSON[@"checkoutId"];
}] resume];
let merchantServerRequest = NSURLRequest(url: URL(string: "https://YOUR_URL/?amount=100&currency=EUR&paymentType=DB")!)
URLSession.shared.dataTask(with: merchantServerRequest as URLRequest) { (data, response, error) in
    // TODO: Handle errors
    if let json = try? JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any] {
        let checkoutID = json?["checkoutId"] as? String
    }
}.resume()

Create and configure checkout provider

Create a PKPaymentRequest object and add it to OPPCheckoutSettings.

We provide a helper method in OPPPaymentProvider to construct a PKPaymentRequest with the given merchantIdentifier, countryCode, and the following default values:

If you do not use the helper method, please make sure that the merchantCapabilities always contain threeDSecure. The emv value is not supported.

OPPPaymentProvider *provider = [OPPPaymentProvider paymentProviderWithMode:OPPProviderModeTest];

OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
PKPaymentRequest *paymentRequest = [OPPPaymentProvider paymentRequestWithMerchantIdentifier:@"YOUR MERCHANT ID" countryCode:@"YOUR MERCHANT COUNTRY CODE"];
paymentRequest.supportedNetworks = ... // set up supported payment networks
checkoutSettings.applePayPaymentRequest = paymentRequest;
OPPCheckoutProvider *checkoutProvider = [OPPCheckoutProvider checkoutProviderWithPaymentProvider:provider
                                                                                      checkoutID:checkoutID
                                                                                        settings:checkoutSettings];
let provider = OPPPaymentProvider(mode: OPPProviderMode.test)

let checkoutSettings = OPPCheckoutSettings()
let paymentRequest = OPPPaymentProvider.paymentRequest(withMerchantIdentifier: "YOUR MERCHANT ID", countryCode: "YOUR MERCHANT COUNTRY CODE")
paymentRequest.supportedNetworks = ... // set up supported payment networks
checkoutSettings.applePayPaymentRequest = paymentRequest
let checkoutProvider = OPPCheckoutProvider(paymentProvider: provider, checkoutID: checkoutID!, settings: checkoutSettings)

(Optional) Set button type

Choose a button type that best fits with the terminology and flow of your purchase or payment experience. For design guidance, see Apple Pay button guidelines.

// Set button type to Buy
checkoutSettings.applePayType = PKPaymentButtonTypeBuy;
// Set button type to Buy
checkoutSettings.applePayType = PKPaymentButtonType.buy

See PKPaymentButtonType documentation for all available button types. The default value is plain.

Present checkout and implement callbacks.

[checkoutProvider presentCheckoutWithPaymentBrand:@"APPLEPAY" 
  loadingHandler:^(BOOL inProgress) {
    // Executed whenever SDK sends request to the server or receives the response. 
    // You can start or stop loading animation based on inProgress parameter.
} completionHandler:^(OPPTransaction * _Nullable transaction, NSError * _Nullable error) {
    if (error) {
        // See code attribute (OPPErrorCode) and NSLocalizedDescription to identify the reason of failure.
    } else {
        if (transaction.redirectURL) {
            // Shopper was redirected to the issuer web page. 
            // Request payment status when shopper returns to the app using transaction.resourcePath or just checkout id.
        } else {
            // Request payment status for the synchronous transaction from your server using transactionPath.resourcePath or just checkout id.
        }
    }
} cancelHandler:^{
    // Executed if the shopper closes the payment page prematurely.
}];
checkoutProvider?.presentCheckout(withPaymentBrand: "APPLEPAY", 
   loadingHandler: { (inProgress) in
    // Executed whenever SDK sends request to the server or receives the answer. 
    // You can start or stop loading animation based on inProgress parameter.
}, completionHandler: { (transaction, error) in
    if error != nil {
        // See code attribute (OPPErrorCode) and NSLocalizedDescription to identify the reason of failure.
    } else {
        if transaction?.redirectURL != nil {
            // Shopper was redirected to the issuer web page.
            // Request payment status when shopper returns to the app using transaction.resourcePath or just checkout id.
        } else {
            // Request payment status for the synchronous transaction from your server using transactionPath.resourcePath or just checkout id.
        }
    }
}, cancelHandler: { 
    // Executed if the shopper closes the payment page prematurely.
})

Note: use the brand APPLEPAYTKN when your acquirer should handle the decryption. Please contact the acquirer directly for more information.

Collecting Apple Pay Shipping and Billing Information

Configure the payment request

First of all use the requiredShippingContactFields and requiredBillingContactFields properties of the payment request to indicate what billing and shipping information is needed. Then Apple Pay form will prompt shoppers to supply the requested billing and shipping information.

Note that API differs for iOS 9 and 10.

// Enable requesting shipping information
if (@available(iOS 11.0, *)) {
    paymentRequest.requiredShippingContactFields = [NSSet setWithObject:PKContactFieldPostalAddress];
} else {
    paymentRequest.requiredShippingAddressFields = PKAddressFieldPostalAddress;
}
if #available(iOS 11.0, *) {
    paymentRequest.requiredShippingContactFields = Set([PKContactField.postalAddress])
} else {
    paymentRequest.requiredShippingAddressFields = .postalAddress
}
// Enable requesting billing information
if (@available(iOS 11.0, *)) {
    paymentRequest.requiredBillingContactFields = [NSSet setWithObject:PKContactFieldPostalAddress];
} else {
    paymentRequest.requiredBillingAddressFields = PKAddressFieldPostalAddress;
}
if #available(iOS 11.0, *) {
    paymentRequest.requiredBillingContactFields = Set([PKContactField.postalAddress])
} else {
    paymentRequest.requiredBillingAddressFields = .postalAddress
}

Adopt the OPPCheckoutProviderDelegate protocol

To receive callbacks from the Apple Pay form, make sure your view controller adopts OPPCheckoutProviderDelegate protocol and you set the checkoutProvider.delegate property.

// Adopt the OPPCheckoutProviderDelegate protocol
@interface SomeViewController () <OPPCheckoutProviderDelegate>
@end

@implementation SomeViewController
...
- (IBAction)applePayTapped:(id)sender {
    // Set a delegate property for the OPPCheckoutProvider instance
    self.checkoutProvider.delegate = self;
    ...
}

@end
// Adopt the OPPCheckoutProviderDelegate protocol
class SomeViewController: UIViewController, OPPCheckoutProviderDelegate {
    ...
    @IBAction func applePayTapped(_ sender: UIButton) {
        // Set a delegate property for the OPPCheckoutProvider instance
        self.checkoutProvider.delegate = self
        ...
    }
}

Collecting Shipping Information

Apple Pay form calls two delegates when shopper provides shipping information:

  • Shipping contact: checkoutProvider:applePayDidSelectShippingContact:handler:
  • Shipping method: checkoutProvider:applePayDidSelectShippingMethod:handler:

Some shipping methods aren’t available in all areas or have different costs for different addresses. You can update this information by returning new list of shipping methods and summary items in completion block.

Shipping contact
- (void)checkoutProvider:(OPPCheckoutProvider *)checkoutProvider 
    applePayDidSelectShippingContact:(PKContact *)contact 
                            handler:(void (^)(OPPApplePayRequestShippingContactUpdate * _Nonnull))completion {

    // You may want to provide different shipping methods based on shipping information
    NSArray *shippingMethods = [self shippingMethodsForContact:contact];
    
    // You may want to change amount of transaction (e.g. by adding tax) based on shipping information
    self.contact = contact;
    [self updateSummaryItems];
    
    OPPApplePayRequestShippingContactUpdate *update = [[OPPApplePayRequestShippingContactUpdate alloc] initWithErrors:nil paymentSummaryItems:self.summaryItems shippingMethods:shippingMethods];
    completion(update);
}
func checkoutProvider(_ checkoutProvider: OPPCheckoutProvider, applePayDidSelectShippingContact contact: PKContact, handler completion: @escaping (OPPApplePayRequestShippingContactUpdate) -> Void) {
    // You may want to provide different shipping methods based on shipping information
    let shippingMethods = self.shippingMethods(contact: contact)

    // You may want to change amount of transaction (e.g. by adding tax) based on shipping information
    self.contact = contact
    updateSummaryItems()

    let update = OPPApplePayRequestShippingContactUpdate.init(errors: nil, paymentSummaryItems: self.summaryItems, shippingMethods: shippingMethods)
    completion(update)
}

NOTE: To maintain privacy, the shipping information provided in this delegate is anonymized. The returned contact contains enough information to calculate shipping costs, without revealing sensitive information about the shopper. Full shipping information will be available after shopper authorizes the payment in the checkoutProvider:applePayDidAuthorizePayment:handler delegate.

Shipping method
- (void)checkoutProvider:(OPPCheckoutProvider *)checkoutProvider 
    applePayDidSelectShippingMethod:(PKShippingMethod *)shippingMethod 
                            handler:(void (^)(OPPApplePayRequestShippingMethodUpdate * _Nonnull))completion {
    
    // You may want to change amount of transaction based on shipping method cost
    self.shippingMethod = shippingMethod;
    [self updateSummaryItems];
    
    OPPApplePayRequestShippingMethodUpdate *update = [[OPPApplePayRequestShippingMethodUpdate alloc] initWithPaymentSummaryItems:self.summaryItems];
    completion(update);
}
func checkoutProvider(_ checkoutProvider: OPPCheckoutProvider, applePayDidSelect shippingMethod: PKShippingMethod, handler completion: @escaping (OPPApplePayRequestShippingMethodUpdate) -> Void) {
    // You may want to change amount of transaction based on shipping method cost
    self.shippingMethod = shippingMethod
    updateSummaryItems()

    let update = OPPApplePayRequestShippingMethodUpdate.init(paymentSummaryItems: self.summaryItems)
    completion(update)
}

Collecting billing information

Billing information and full shipping information is available only after shopper authorizes the payment. Implement the checkoutProvider:applePayDidAuthorizePayment:handler delegate to get this data.

- (void)checkoutProvider:(OPPCheckoutProvider *)checkoutProvider
    applePayDidAuthorizePayment:(PKPayment *)payment
                        handler:(void (^)(OPPApplePayRequestAuthorizationResult * _Nonnull))completion {
                        
    OPPApplePayRequestAuthorizationResult *result = nil;
    
    // You may want to validate shipping/billing info
    if ([self isValidBillingContact:payment.billingContact]) {
        // Return success to continue the payment
        result = [[OPPApplePayRequestAuthorizationResult alloc] initWithStatus:PKPaymentAuthorizationStatusSuccess 
                                                                        errors:nil];
    } else {
        NSArray *errors = nil;
        if (@available(iOS 11.0, *)) {
            // Since iOS 11 you can pass list of errors in billing/shipping info
            NSError *error = [PKPaymentRequest paymentBillingAddressInvalidErrorWithKey:CNPostalAddressCountryKey
                                                                   localizedDescription:@"Some country error message"];
            errors = [NSArray arrayWithObject:error];
        }
        result = [[OPPApplePayRequestAuthorizationResult alloc] initWithStatus:PKPaymentAuthorizationStatusFailure
                                                                        errors:errors];
    }
    completion(result);
}
func checkoutProvider(_ checkoutProvider: OPPCheckoutProvider, applePayDidAuthorizePayment payment: PKPayment, handler completion: @escaping (OPPApplePayRequestAuthorizationResult) -> Void) {
    var result: OPPApplePayRequestAuthorizationResult

    // You may want to validate shipping/billing info
    if isValidBillingContact(contact: payment.billingContact) {
        // Return success to continue the payment
        result = OPPApplePayRequestAuthorizationResult.init(status: .success, errors: nil)
    } else {
        var errors: [Error] = []
        if #available(iOS 11.0, *) {
            // Since iOS 11 you can pass list of errors in billing/shipping info
            let error = PKPaymentRequest.paymentBillingAddressInvalidError(withKey: CNPostalAddressCountryKey, localizedDescription: "Some country error mesage")
            errors = [error]
        }
        result = OPPApplePayRequestAuthorizationResult.init(status: .failure, errors: errors)
    }

    completion(result)
}

Updating Apple Pay transaction amount

To update Apple Pay transaction amount, you need to

  • Send updated list of PKPaymentSummaryItems in the shipping info delegates,
  • Update checkout session with the new amount by a server-to-server call. You can do it asynchronously in the checkoutProvider:continueSubmitting:completion: delegate. It is the last checkout delegate that is called right before the submitting transaction.

  • - (void)checkoutProvider:(OPPCheckoutProvider *)checkoutProvider 
        continueSubmitting:(OPPTransaction *)transaction 
                completion:(void (^)(NSString * _Nullable checkoutID, BOOL abort))completion {
                
        // Update checkout session with the new amount
        completion(transaction.paymentParams.checkoutID, NO);
    }
    
    func checkoutProvider(_ checkoutProvider: OPPCheckoutProvider, continueSubmitting transaction: OPPTransaction, completion: @escaping (String?, Bool) -> Void) {
        // Update checkout session with the new amount
        completion(transaction.paymentParams.checkoutID, false)
    }
    

    Submitting shopper information

    Instead of collecting information by yourself as explained above, you can use the mSDK to collect and submit it. Set in the CheckoutSettings what information should be submitted.

    Configure CheckoutSettings

    Set applePayContactTypes property in the CheckoutSettings what information should be submitted.

    • OPPCheckoutApplePayContactTypeBillingAddress- The shopper billing address.
    • OPPCheckoutApplePayContactTypeCustomer - The shopper name, surname, phone and email.

    One or both options can be used.

    checkoutSettings.applePayContactTypes = OPPCheckoutApplePayContactTypeBillingAddress | OPPCheckoutApplePayContactTypeCustomer;
    checkoutSettings.applePayContactTypes = [.billingAddress, .customer]

    SDK & Your Own UI

    If you are using our SDK & Your Own UI then there is a little more work to be done, but it is still easy to add. Accepting Apple Pay is quite similar to working with cards. When the shopper approves a payment, your application will receive a PKPayment object that contains encrypted card details. Create OPPApplePayPaymentParams with the received payment data and submit a transaction. See below the full Apple Pay flow.

    Set up your Apple Pay button

    Apple Pay is only available to a subset of iOS users. Before presenting the Apple Pay option to the current user, you should determine whether Apple Pay is available. You can do it using OPPPaymentProvider helper method.

    - (void)viewDidLoad {
        [super viewDidLoad];
        self.applePayButton.enabled = [OPPPaymentProvider deviceSupportsApplePay];
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        applePayButton.isEnabled = OPPPaymentProvider.deviceSupportsApplePay()
    }
    

    Create a PKPaymentRequest

    Next, when the shopper taps "pay by Apple Pay", create a PKPaymentRequest object. We provide a helper method in OPPPaymentProvider to create one with the given merchantIdentifier, countryCode, and the following default values:

    If you do not use the helper method, please make sure that the merchantCapabilities always contain threeDSecure. The emv value is not supported.

    NOTE: See how to register a merchant ID in Apple Pay documentation.
    PKPaymentRequest *request = [OPPPaymentProvider paymentRequestWithMerchantIdentifier:@"YOUR MERCHANT ID" countryCode:@"YOUR COUNTRY CODE"];
    
    // Set currency. 
    request.currencyCode = "USD";
     
    // Create total item. Label should represent your company.  
    // It will be prepended with the word "Pay" (i.e. "Pay Sportswear $100.00")
    NSDecimalNumber *amount = [NSDecimalNumber decimalNumberWithMantissa:10000 exponent:-2 isNegative:NO];
    request.paymentSummaryItems = @[[PKPaymentSummaryItem summaryItemWithLabel:@"Sportswear" amount:amount]]; 
    
    let request = OPPPaymentProvider.paymentRequest(withMerchantIdentifier: "YOUR MERCHANT ID", countryCode: "YOUR COUNTRY CODE")
    
    // Set currency. 
    request.currencyCode = "USD"
    
    // Create total item. Label should represent your company.  
    // It will be prepended with the word "Pay" (i.e. "Pay Sportswear $100.00")
    let amount = NSDecimalNumber(mantissa: 10000, exponent: -2, isNegative: false)
    request.paymentSummaryItems = [PKPaymentSummaryItem(label: "Sportswear", amount: amount)]
    

    Present a PKPaymentAuthorizationViewController

    Next, create and present a PKPaymentAuthorizationViewController with your payment request. Use OPPPaymentProvider method to validate the final PKPaymentRequest object.

    NOTE: Your view controller should implement PKPaymentAuthorizationViewControllerDelegate protocol to respond to user interaction with that view controller.
    - (void)applePayTapped { 
        PKPaymentRequest *request = ...; // See above 
        if ([OPPPaymentProvider canSubmitPaymentRequest:request]) { 
            PKPaymentAuthorizationViewController *vc = [[PKPaymentAuthorizationViewController alloc] initWithPaymentRequest:request]; 
            vc.delegate = self; 
            [self presentViewController:vc animated:YES completion:nil]; 
        } else { 
            NSLog(@"Apple Pay not supported."); 
        } 
    }
    
    func applePayTapped() {
        let request = PKPaymentRequest() // See above
        if OPPPaymentProvider.canSubmitPaymentRequest(request) {
            if let vc = PKPaymentAuthorizationViewController(paymentRequest: request) as PKPaymentAuthorizationViewController? {
                vc.delegate = self
                present(vc, animated: true, completion: nil)
            } else {
                NSLog(@"Apple Pay not supported."); 
            }
        }
    }
    

    Finally, implement the necessary PKPaymentAuthorizationViewControllerDelegate methods. In your implementation of paymentAuthorizationViewController:didAuthorizePayment:completion:, create OPPApplePayPaymentParams and submit a debit transaction.

    - (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller  
                           didAuthorizePayment:(PKPayment *)payment  
                                    completion:(void (^)(PKPaymentAuthorizationStatus))completion { 
        OPPApplePayPaymentParams *params = [OPPApplePayPaymentParams applePayPaymentParamsWithCheckoutID:checkoutID  
                                                                                               tokenData:payment.token.paymentData]; 
        // Check if parameters are valid and submit transaction.
        [self.provider submitTransaction:[OPPTransaction transactionWithPaymentParams:params] completionHandler:^(OPPTransaction *transaction, NSError *error) {
            if (error) {
                // See code attribute (OPPErrorCode) and NSLocalizedDescription to identify the reason of failure.
            } else {
                // Send request to your server to obtain transaction status.
        }];
    } 
     
    - (void)paymentAuthorizationViewControllerDidFinish:(PKPaymentAuthorizationViewController *)controller { 
        [controller dismissViewControllerAnimated:YES completion:nil]; 
    } 
    
    func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, completion: @escaping (PKPaymentAuthorizationStatus) -> Void) {
        if let params = try? OPPApplePayPaymentParams(checkoutID: checkoutID, tokenData: payment.token.paymentData) as OPPApplePayPaymentParams? {
            provider.submitTransaction(OPPTransaction(paymentParams: params!), completionHandler: { (transaction, error) in
                if (error != nil) {
                    // See code attribute (OPPErrorCode) and NSLocalizedDescription to identify the reason of failure.
                } else {
                    // Send request to your server to obtain transaction status.
                }
             })
        }
    }
    
    func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
        controller.dismiss(animated: true, completion: nil)
    }