Offering Apple Pay in Your App

Collect payments with iPhone, Apple Watch, and Siri using Apple Pay.

Overview

This sample shows how to integrate Apple Pay into an eCommerce experience across iOS, watchOS, and Siri. You’ll learn how to display the Apple Pay payment sheet, make payment requests, apply discounts for debit/credit cards, and use the Apple Pay button.

The sample app is a ride-sharing app in which users make payments with Apple Pay in:

  • An iOS application.
  • A watchOS application.
  • A Siri and Maps intents extension.

In each of these applications and extensions, payment is taken and handled by a shared PaymentHandler.

Configure the Sample Code Project

To test Apple Pay payments with this sample, you’ll need to configure a developer account in Xcode to set up the bundle identifiers and Apple Pay configuration items. Complete these three configuration steps to be able to build and run the sample app:

  1. Change the bundle IDs for each of the targets to be unique to your developer account; you can change example in the bundle ID to something more representative of you or your organization.
  2. In the user-defined build settings for the project, update the value for the OFFERING_APPLE_PAY_BUNDLE_PREFIX setting to match the prefix of the bundle IDs changed in step 1. For example, if you changed “example” in each bundle ID to your organization name, change example in the setting to the same organization name. When you build the project, Xcode will configure the required merchant ID for Apple Pay in each target.
  3. Set up the Apple Pay Merchant Identity and Apple Pay Payment Processing certificates as described in Setting Up Apple Pay Requirements.

Once you have completed the setup steps, build and run the iOS app using Xcode’s “Automatically manage signing” option.

If you’re running this application on an iPhone or Apple Watch you need an Apple Pay card. Alternatively use the iOS Simulator, but note that not all Apple Pay features will work in the iOS Simulator, and you cannot test Apple Pay in the watchOS simulator.

For more information about processing an Apple Pay payment using a payment platform or merchant bank, see An easier way to pay within apps and websites. To set up your sandbox environment for testing, see Sandbox Testing.

Add the Apple Pay Button

Apple Pay includes pre-built buttons to start a payment interaction or set up payment methods. In the iOS app, the sample code checks to see whether it should add a payment button or a setup button, depending on whether the device has cards set up. The sample app uses PKPaymentAuthorizationController methods canMakePayments and canMakePayments(usingNetworks:) to determine whether cards are set up and whether the device can make payments.

static let supportedNetworks: [PKPaymentNetwork] = [
    .amex,
    .discover,
    .masterCard,
    .visa
]

class func applePayStatus() -> (canMakePayments: Bool, canSetupCards: Bool) {
    return (PKPaymentAuthorizationController.canMakePayments(),
            PKPaymentAuthorizationController.canMakePayments(usingNetworks: supportedNetworks))
}

If there are cards set up and the device can make payments, the iOS app will add an instance of PKPaymentButton set up as a buy button if the device can accept payments or as a setup button if the device is ready to set up cards. If the device cannot accept payments (due to hardware limitations, parental controls, or other reasons) the sample app does not add a button.

let result = PaymentHandler.applePayStatus()
var button: UIButton?

if result.canMakePayments {
    button = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black)
    button?.addTarget(self, action: #selector(ViewController.payPressed), for: .touchUpInside)
} else if result.canSetupCards {
    button = PKPaymentButton(paymentButtonType: .setUp, paymentButtonStyle: .black)
    button?.addTarget(self, action: #selector(ViewController.setupPressed), for: .touchUpInside)
}

if let applePayButton = button {
    let constraints = [
        applePayButton.centerXAnchor.constraint(equalTo: applePayView.centerXAnchor),
        applePayButton.centerYAnchor.constraint(equalTo: applePayView.centerYAnchor)
    ]
    applePayButton.translatesAutoresizingMaskIntoConstraints = false
    applePayView.addSubview(applePayButton)
    NSLayoutConstraint.activate(constraints)
}

The watchOS app sets up the button in the storyboard as an instance of WKInterfacePaymentButton.

Set up the Apple Pay Siri Intent

The sample configures the handle(intent:completion:) method to initiate the Apple Pay payment process in IntentHandler. Install the iOS or watchOS app, enable Siri, then tell Siri “I’d like a ride.” Siri offers the sample app as an option.

Start the Payment Process

The iOS, watchOS, and Siri implementations all call the shared PaymentHandler method startPayment to start the payment process, each providing a completion handler to perform user-interface updates once the payment process is completed. The startPayment method stores the completion handler because the Apple Pay functionality is asynchronous; and then the method begins the payment process by creating an array of PKPaymentSummaryItem to show what charges the app is requesting the user to pay.

let fare = PKPaymentSummaryItem(label: "Minimum Fare", 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: .pending)
paymentSummaryItems = [fare, tax, total]

The sample code uses the list of payment items and some other details to configure a PKPaymentRequest.

let paymentRequest = PKPaymentRequest()
paymentRequest.paymentSummaryItems = paymentSummaryItems
paymentRequest.merchantIdentifier = Configuration.Merchant.identifier
paymentRequest.merchantCapabilities = .capability3DS
paymentRequest.countryCode = "US"
paymentRequest.currencyCode = "USD"
paymentRequest.requiredShippingContactFields = [.postalAddress, .phoneNumber]
paymentRequest.supportedNetworks = PaymentHandler.supportedNetworks

With the payment request built, the sample app asks PKPaymentAuthorizationController to present the payment sheet. The present(completion:) method provides a common API that iOS, watchOS, and Siri can all use to present the Apple Pay payment sheet. Once presented, it will handle interactions with the user including payment confirmation.

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.completionHandler(false)
    }
})

Respond to Payment Card Selection

The PaymentHandler class implements the PKPaymentAuthorizationControllerDelegate protocol method paymentAuthorizationController(_:didSelectPaymentMethod:handler:), which allows the sample app to add a discount if the user selects a debit card for payment. The method adds a new PKPaymentSummaryItem for the discount, and adjusts the PKPaymentSummaryItem for the new discounted total.

func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didSelectPaymentMethod paymentMethod: PKPaymentMethod, handler completion: @escaping (PKPaymentRequestPaymentMethodUpdate) -> Void) {
    // The didSelectPaymentMethod delegate method allows you to make changes when the user updates their payment card
    // Here we're applying a $2 discount when a debit card is selected
    guard paymentMethod.type == .debit else {
        completion(PKPaymentRequestPaymentMethodUpdate(paymentSummaryItems: paymentSummaryItems))
        return
    }

    var discountedSummaryItems = paymentSummaryItems
    let discount = PKPaymentSummaryItem(label: "Debit Card Discount", amount: NSDecimalNumber(string: "-2.00"))
    discountedSummaryItems.insert(discount, at: paymentSummaryItems.count - 1)
    if let total = paymentSummaryItems.last {
        total.amount = total.amount.subtracting(NSDecimalNumber(string: "2.00"))
    }
    completion(PKPaymentRequestPaymentMethodUpdate(paymentSummaryItems: discountedSummaryItems))
}

Handle Payment Success or Failure

The PaymentHandler class also implements the PKPaymentAuthorizationControllerDelegate protocol method paymentAuthorizationController(_:didAuthorizePayment:handler:), which informs the sample app that the user has authorized a payment. The implementation confirms that the shipping address meets the criteria needed, and then calls the completion handler, reporting success or failure of the payment. The sample app does not implement payment processing and will not create a real charge based on a successful payment result, but shows with a comment where payment processing should be implemented.

func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didAuthorizePayment payment: PKPayment, handler completion: @escaping (PKPaymentAuthorizationResult) -> Void) {
    
    // Perform some very 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 picks up in the United States")
        let countryError = PKPaymentRequest.paymentShippingAddressInvalidError(withKey: CNPostalAddressCountryKey, localizedDescription: "Invalid country")
        errors.append(pickupError)
        errors.append(countryError)
        status = .failure
    } else {
        // Here you would send the payment token to your server or payment provider to process
        // Once processed, return an appropriate status in the completion handler (success, failure, etc)
    }
    
    self.paymentStatus = status
    completion(PKPaymentAuthorizationResult(status: status, errors: errors))
}

Once the sample app calls the completion handler in the paymentAuthorizationController(_:didAuthorizePayment:handler:) method, Apple Pay calls the paymentAuthorizationControllerDidFinish(_:) protocol method to inform the sample app that it can dismiss the payment sheet. iOS, watchOS, and Siri all handle dismissing the sheet as appropriate for their platform. In the iOS app, the completion handler performs a segue to display a new view controller if payment was successful, or remains on the payment sheet if the payment failed, so the user can attempt payment with a different card or correct any issues.

@objc func payPressed(sender: AnyObject) {
    paymentHandler.startPayment() { (success) in
        if success {
            self.performSegue(withIdentifier: "Confirmation", sender: self)
        }
    }
}

GitHub

View Github