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

Overview

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)
        }
    }
}
You might also like...
Fast, non-deadlocking parallel object cache for iOS, tvOS and OS X

PINCache Fast, non-deadlocking parallel object cache for iOS and OS X. PINCache is a fork of TMCache re-architected to fix issues with deadlocking cau

Delightful framework for iOS to easily persist structs, images, and data
Delightful framework for iOS to easily persist structs, images, and data

Installation • Usage • Debugging • A Word • Documentation • Apps Using Disk • License • Contribute • Questions? Disk is a powerful and simple file man

🏈 Cache CocoaPods for faster rebuild and indexing Xcode project.
🏈 Cache CocoaPods for faster rebuild and indexing Xcode project.

Motivation Working on a project with a huge amount of pods I had some troubles: - Slow and unnecessary indexing of pods targets, which implementation

A repository for showcasing my knowledge of the Objective-C++ programming language, and continuing to learn the language.

Learning Objective-C-Plus-Plus I hardly know anything about the Objective-C++ programming language. This document will go over all of my knowledge of

A repository for showcasing my knowledge of the Objective-C programming language, and continuing to learn the language.
A repository for showcasing my knowledge of the Objective-C programming language, and continuing to learn the language.

Learning Objective-C I hardly know anything about the Objective-C programming language. This document will go over all of my knowledge of the Objectiv

Rock - Paper - Scissors game. CPU gives you a sign and asks to win or lose your move. Than you have to decide witch sign do you choose to score a point
Rock - Paper - Scissors game. CPU gives you a sign and asks to win or lose your move. Than you have to decide witch sign do you choose to score a point

RockPaperScissors 2nd challange from HackingWithSwift.com. The CPU gives you a sign (rock, paper or scissors) and asks you either to win or to lose th

WireGuard for iOS and macOS

WireGuard for iOS and macOS This project contains an application for iOS and for macOS, as well as many components shared between the two of them. You

A comprehensive SDK for scanning Digimarc digital watermarks and the most common retail 1D barcodes & QR codes.

##Digimarc Mobile SDK for Apple Platforms The Digimarc Mobile SDK (DM SDK) is a comprehensive and robust scanning software for Digimarc Barcode (Produ

Find and save your favorite places!

Reqs Recommendation app to search for, organize, and save the places you love. TODOs: Implement UserDefaults for settings Complete annotation screen w

Owner
Edgar Papyan
When not drinking coffee or debating serious topics with my cat, you'll find me coding.
Edgar Papyan
MrCode is a simple GitHub iPhone App that can cache Markdown content (include images in HTML) for read it later.

MrCode is a simple GitHub iPhone App that can cache Markdown content (include images in HTML) for read it later.

hao 448 Dec 19, 2022
Periodum-apple - iPad and macOS client for periodum.com

Periodum  iPad and macOS client for periodum.com

Umur Gedik 1 Jan 31, 2022
Apple Asset Cache (Content Cache) Tools

AssetCacheTool A library and tool for interacting with both the local and remote asset caches. This is based on research I did a few years ago on the

Kenneth Endfinger 21 Jan 5, 2023
A library and tool for interacting with both the local and remote asset caches.

Asset Cache Tool A library and tool for interacting with both the local and remote asset caches. This is based on research I did a few years ago on th

Kenneth Endfinger 20 Dec 23, 2022
CachyKit - A Caching Library is written in Swift that can cache JSON, Image, Zip or AnyObject with expiry date/TTYL and force refresh.

Nice threadsafe expirable cache management that can cache any object. Supports fetching from server, single object expire date, UIImageView loading etc.

Sadman Samee 122 Dec 28, 2022
Cachyr A typesafe key-value data cache for iOS, macOS, tvOS and watchOS written in Swift.

Cachyr A typesafe key-value data cache for iOS, macOS, tvOS and watchOS written in Swift. There already exists plenty of cache solutions, so why creat

Norsk rikskringkasting (NRK) 124 Nov 24, 2022
Carlos - A simple but flexible cache, written in Swift for iOS 13+ and WatchOS 6 apps.

Carlos A simple but flexible cache, written in Swift for iOS 13+ and WatchOS 6 apps. Breaking Changes Carlos 1.0.0 has been migrated from PiedPiper de

National Media & Tech 628 Dec 3, 2022
Everyone tries to implement a cache at some point in their iOS app’s lifecycle, and this is ours.

Everyone tries to implement a cache at some point in their app’s lifecycle, and this is ours. This is a library that allows people to cache NSData wit

Spotify 1.2k Dec 28, 2022
Track is a thread safe cache write by Swift. Composed of DiskCache and MemoryCache which support LRU.

Track is a thread safe cache write by Swift. Composed of DiskCache and MemoryCache which support LRU. Features Thread safe: Implement by dispatch_sema

Cheer 268 Nov 21, 2022
A caching and consistency solution for immutable models.

?? Data Rocket Data is a model management system with persistence for immutable models. Motivation Immutability has many benefits, but keeping models

Peter Livesey 652 Nov 11, 2022