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 Business Intelligence 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 Business Intelligence 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

If you are using our ready-to-use checkout screens, configure Apple Pay in OPPCheckoutSettings along with other customizations.

Set your Apple Pay payment request:

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

Make sure Apple Pay is included to the payment brand list. If you integrate Apple Pay using our drop-in buttons, you don't have to do this part.

checkoutSettings.paymentBrands = @[@"APPLEPAY", ... ];
checkoutSettings.paymentBrands = ["APPLEPAY", ...]
And you are done!

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)checkoutButtonAction:(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 checkoutButtonAction(_ 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)
    }
    

    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 that describes the payment you want to make. SDK provides a helper method that does much of the configuration for you.

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