Integrating Apple Pay to iOS App

Photo by Julia Kadel on Unsplash
Photo by Julia Kadel on Unsplash
Apple Pay is Apple’s mobile payment service. We can use Apple Pay to make a tap-to-pay in physical stores or purchase goods in apps. In addition, the Wallet app in iPhone can manage credit cards, so we don’t need to carry several credit cards.

Apple Pay is Apple’s mobile payment service. We can use Apple Pay to make a tap-to-pay in physical stores or purchase goods in apps. In addition, the Wallet app in iPhone can manage credit cards, so we don’t need to carry several credit cards. This article will introduce how to integrate Apple Pay into iOS app.

The complete code for this chapter can be found in .

Creating Merchant ID

Before we start writing the code, we need to create a merchant ID. Login to Apple developer and enter the Identifiers page. In the upper right corner of the page, select Merchant IDs. Click Register a Merchant ID.

Merchant IDs.
Merchant IDs.

Select Merchant IDs.

Register a new merchant ID.
Register a new merchant ID.

In the field on the right, enter your merchant ID. Apple recommends using reverse-domain name style for your merchant ID.

Input a merchant ID.
Input a merchant ID.

After confirming them, click Register.

Confirm the merchant ID.
Confirm the merchant ID.

The merchant IDs list now contains the merchant ID we just created. Click on it to see its details.

Created a merchant ID.
Created a merchant ID.

Click Create Certificate under Apple Pay Payment Processing Certificate.

Edit the merchant ID.
Edit the merchant ID.

Select Yes.

Select Yes to exclusive China mainland.
Select Yes to exclusive China mainland.

Here we are asked to upload a Certificate Signing Request.

Create a new certificate.
Create a new certificate.

Open Keychain Access in your Mac OS and select Certificate Assistant -> Request a Certificate From a Certificate Authority….

Keychain Access -> Request a Certificate From a Certificate Authority...
Keychain Access -> Request a Certificate From a Certificate Authority…

Enter your email in User Email Address. CA Email Address can be left blank. After clicking Continue, a CertificateSigningRequest.certSigningRequest will be stored.

Input user email, and leave CA Email Address empty.
Input user email, and leave CA Email Address empty.

Click Choose File and upload the CertificateSigningRequest.certSigningRequest just created.

Upload the CSR.
Upload the CSR.

Finally, the Apple Pay Payment Processing Certificate has been created. Click Download to download apple_pay.cer.

Download the certificate.
Download the certificate.

Returning to the merchant ID details, you can see that the Apple Pay Payment Processing Certificate has been created.

Payment Processing Certificate is created.
Payment Processing Certificate is created.

Adding Apple Pay to Capabilities

Open the project with Xcode and go to Signing & Capabilities.

Xcode -> Capabilities.
Xcode -> Capabilities.

Click the + in the upper right corner to add Apple Pay to Capabilities.

Add Apple Pay capability.
Add Apple Pay capability.

After adding Apple Pay, Xcode will automatically synchronize the merchant IDs. Select the merchant ID we just created.

Choose the merchant ID.
Choose the merchant ID.

Now the project is set up. Open the entitlements file in the project, and you can see that Xcode helps us add the selected merchant ID to the entitlements file.

Project's entitlements.
Project’s entitlements.

Initializing PKPaymentButton

Declare a PKPaymentButton in the ViewController and choose the style you want. Then, put it on the view.

import UIKit
import PassKit

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

    private func initPaymentButton() {
        var button: UIButton? = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .white)
        button?.addTarget(self, action: #selector(ViewController.onPayPressed), for: .touchUpInside)
        
        if let button = button {
            let constraints = [
                button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
            ]
            button.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(button)
            NSLayoutConstraint.activate(constraints)
        }
    }

    @objc private func onPayPressed(sender: AnyObject) {
        // TODO: start payment
    }
}

Checking If Apple Pay is Ready

After setting up the PKPaymentButton, we need to check whether Apple Pay is ready. We only display the PKPaymentButton if Apple Pay is ready.

class ViewController: UIViewController {
    private let viewModel = ViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        initPaymentButton()
    }

    private func initPaymentButton() {
        var button: UIButton?
        
        if viewModel.canMakePayment() {
            button = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .white)
            button?.addTarget(self, action: #selector(ViewController.onPayPressed), for: .touchUpInside)
        }
        
        if let button = button {
            let constraints = [
                button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
            ]
            button.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(button)
            NSLayoutConstraint.activate(constraints)
        }
    }
    
    @objc private func onPayPressed(sender: AnyObject) {
        // TODO: start payment
    }
}

Use PKPaymentAuthorizationController.canMakePayments(usingNetworks:) to check if this device can use Apple Pay.

class ViewModel {
    private static let supportedNetworks: [PKPaymentNetwork] = [
        .amex,
        .discover,
        .masterCard,
        .visa
    ]

    func canMakePayment() -> Bool {
        return PKPaymentAuthorizationController.canMakePayments(usingNetworks: Self.supportedNetworks)
    }
}

Executing Payment

The last step is payment. We must use PKPaymentAuthorizationController to display the Apple Pay payment view. So we tell it the payment amount, currency, shipping information, merchant ID, etc.

In addition, we also need to set PKPaymentAuthorizationControllerDelegate. When the payment is completed, we will receive a PKPayment. We can get the token from PKPayment and send the token to the server.

Finally, when the payment is completed, the Apple Pay payment view will not automatically dismiss. We have to manually call PKPaymentAuthorizationController.dismiss().

import UIKit
import PassKit

class ViewModel: NSObject {
    private static let merchantID = "merchant.com.waynestalk"
    private static let supportedNetworks: [PKPaymentNetwork] = [
        .amex,
        .discover,
        .masterCard,
        .visa
    ]
    
    var paymentController: PKPaymentAuthorizationController?
    var paymentSummaryItems = [PKPaymentSummaryItem]()
    var paymentStatus = PKPaymentAuthorizationStatus.failure
    var completion: ((Bool) -> Void)?

    ...
    
    func startPayment(completion: @escaping (Bool) -> Void) {
        self.completion = completion
        
        let ticket = PKPaymentSummaryItem(label: "Cloth", amount: NSDecimalNumber(string: "9.99"), type: .final)
        let tax = PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(string: "1.00"), type: .final)
        let total = PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(string: "10.99"), type: .final)
        paymentSummaryItems = [ticket, tax, total]
        
        let paymentRequest = PKPaymentRequest()
        paymentRequest.paymentSummaryItems = paymentSummaryItems
        paymentRequest.merchantIdentifier = Self.merchantID
        paymentRequest.merchantCapabilities = .threeDSecure
        paymentRequest.countryCode = "US"
        paymentRequest.currencyCode = "USD"
        paymentRequest.supportedNetworks = Self.supportedNetworks
        paymentRequest.shippingType = .storePickup
        
        paymentController = PKPaymentAuthorizationController(paymentRequest: paymentRequest)
        paymentController?.delegate = self
        paymentController?.present(completion: { (presented: Bool) in
            if presented {
                debugPrint("Presented payment controller")
            } else {
                debugPrint("Failed to present payment controller")
                self.completion?(false)
            }
        })
    }
}

extension ViewModel: PKPaymentAuthorizationControllerDelegate {
    func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController,
                                        didAuthorizePayment payment: PKPayment,
                                        handler completion: @escaping (PKPaymentAuthorizationResult) -> Void) {
        // Perform basic validation on the provided contact information.
        var errors = [Error]()
        var status = PKPaymentAuthorizationStatus.success
        if payment.shippingContact?.postalAddress?.isoCountryCode != "US" {
            let pickupError = PKPaymentRequest.paymentShippingAddressUnserviceableError(withLocalizedDescription: "Sample App only available in the United States")
            let countryError = PKPaymentRequest.paymentShippingAddressInvalidError(withKey: CNPostalAddressCountryKey, localizedDescription: "Invalid country")
            errors.append(pickupError)
            errors.append(countryError)
            status = .failure
        } else if let token = String(data: payment.token.paymentData, encoding: .utf8) {
            // Send the payment token to your server or payment provider to process here.
            // Once processed, return an appropriate status in the completion handler (success, failure, and so on).
            debugPrint(token)
        }
        
        self.paymentStatus = status
        completion(PKPaymentAuthorizationResult(status: status, errors: errors))
    }
    
    func paymentAuthorizationControllerDidFinish(_ controller: PKPaymentAuthorizationController) {
        controller.dismiss {
            DispatchQueue.main.async {
                if self.paymentStatus == .success {
                    self.completion?(true)
                } else {
                    self.completion?(false)
                }
            }
        }
    }
}
import UIKit
import PassKit

class ViewController: UIViewController {
    private let viewModel = ViewModel()

    ...
    
    @objc private func onPayPressed(sender: AnyObject) {
        viewModel.startPayment {
            if $0 {
                debugPrint("payment has succeeded")
            }
        }
    }
}

Conclusion

Apple Pay integration isn’t difficult. On the contrary, the steps to create a merchant ID are more troublesome. Also, we cannot directly add PKPaymentButton in Storyboard. When the view is complex, it is not so easy to manually add PKPaymentButton to the view.

Reference

Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like
Photo by Alex Alvarez on Unsplash
Read More

Dispatch Queue Tutorial

Grand Central Dispatch (GCD) provides efficient concurrent processing so that we don’t need to directly manage multiple threads. Its dispatch queues can execute tasks serially or concurrently.
Read More