Skip to main content

Wallet Extensions Guide

Overview

Wallet Extensions (In-App Provisioning or Issuer Extensions) make it easier for users to know that they can add a payment pass to Apple Pay by improving discoverability from right within Apple Wallet.

info

Support for Wallet Extensions (In-App Provisioning Extensions) is mandatory.

This functionality provides users the ability to have the in-app experience of adding a payment pass, but it is initiated directly inside of Apple Wallet. The process starts and finishes within Apple Wallet and is a convenient method for users to provision their payment passes. Apple introduced Wallet Extensions in iOS 14. Issuer application needs to provide special App Extensions to Apple Wallet to be able to request and provision available cards.

Apple Pay Wallet Extensions

info
  1. Issuer app needs to be installed and the user needs to have opened the app at least once so that Apple Wallet knows that there are payment passes available. Apple Wallet can also prompt for user authentication when adding a payment pass.
  2. Use of the Wallet Extension requires the same entitlement file used for In-App Provisioning: com.apple.developer.payment-pass-provisioning.
  3. In order for Wallet to hide passes that user already has added to the device, passes should include the extension's bundle identifier in associatedApplicationIdentifiers on token service provider side.

References

  • Refer to section "Wallet Extensions" in "Getting Started with Apple Pay In-App Provisioning, Verification, and Security v4" guide shared by Apple to Issuer when signing the Apple Pay contract.
  • See Apple WWDC 2020 - Adding Cards to Apple Pay to learn about Apple introducing In-App Provisioning Extensions.
  • See Creating an App Extension to learn about App Extensions.

Issuer Provisioning Extensions Sequence Diagram

Apple Pay Wallet Extensions Flow

Adding Wallet Extensions

Wallet Extensions feature is app extension based and lets the issuer app extend custom functionality and content, and make it available in Apple Wallet. All of the methods are triggered by Apple Wallet and the app extensions ability to enable behavior is solely based on the completion handlers and return values. This feature relies on two extensions:

  1. Issuer app should provide a non-UI extension (subclass of PKIssuerProvisioningExtensionHandler) to report on the status of the extension and the payment passes available for provisioning like when adding payment passes to Apple Pay from within the issuer app.
  2. Issuer app should provide a UI extension (UIViewController that conforms to PKIssuerProvisioningExtensionAuthorizationProviding) to perform authentication of the user if the non-UI extension reports in its status that authentication is required. The UI extension is a separate screen that uses the same issuer app login credentials. UI extension is not a redirect to the issuer app.

Wallet Extensions do not use a specific extension type template.

1. Creating App Extensions

Select Intents Extension as the base. Create two app extensions IssuerNonUIExtension and IssuerUIExtension following the steps below.

  • Add an app extension to Xcode app project, choose File > New > Target, select iOS > Application Extension > Intents Extension.

Issuer Extensions Target

  • Set options for the new target. Create a unique bundle ID for the extension and add App IDs to associatedApplicationIdentifiers in PNO metadata.

For example,

A1B2C3D4E5.com.meawallet.app
A1B2C3D4E5.com.meawallet.app.IssuerNonUIExtension
A1B2C3D4E5.com.meawallet.app.IssuerUIExtension

Issuer Extensions Target

  • Activate the created scheme, if asked.
  • Add PassKit.framework and MeaPushProvisioning.xcframework to Frameworks and Libraries of the IssuerNonUIExtension and IssuerUIExtension targets, and remove Intents.framework which is not needed.
  • Remove Intents.framework of the IssuerUIExtension target.

Issuer Extensions Target Frameworks

2. App Extensions Configuration

  • Register both app extension bundle identifiers in Apple Developer portal Identifiers section. Create provisioning profiles accordingly.
  • Create a new App Group and add the main app and both app extension bundle identifiers to the app group, so data can be shared between them.
  • Add mea_config to IssuerNonUIExtension which is consuming MPP SDK. App extension is run separately from the main app process, so SDK is initialized separately.

3. Implementing Issuer Authorization Provider Extension

  • IssuerAuthorizationExtensionHandler UI extension should conform to PKIssuerProvisioningExtensionAuthorizationProviding protocol. Apple Wallet interrogates the issuer app to determine the user's authorization status, and the authorization UI extension performs user authentication.
  • App UI extension has a memory limit of 60 MB, developers are responsible to optimize the code and libraries to fit this requirement.
IssuerAuthorizationExtensionHandler.swift
import PassKit
import UIKit

@available(iOS 14.0, *)
class IssuerAuthorizationExtensionHandler: UIViewController, PKIssuerProvisioningExtensionAuthorizationProviding {
var completionHandler: ((PKIssuerProvisioningExtensionAuthorizationResult) -> Void)?

override func viewDidLoad() {
super.viewDidLoad()
// Set up view and authenticate user.
}

func authenticateUser() {
let userAuthenticated = true // User authentication outcome.

let authorizationResult: PKIssuerProvisioningExtensionAuthorizationResult = userAuthenticated ? .authorized : .canceled

self.completionHandler?(authorizationResult)
}
}

4. Implementing Issuer Extension Handler

  • IssuerExtensionHandler non-UI class must be a subclass of PKIssuerProvisioningExtensionHandler. Issuer app must be installed and the user must open the issuer app at least once for the system to call the issuer extension handler.
  • App Non-UI extension has a memory limit of 55 MB, developers are responsible to optimize the code and libraries to fit this requirement.
IssuerExtensionHandler.swift
import PassKit
import MeaPushProvisioning

@available(iOS 14.0, *)
class IssuerExtensionHandler: PKIssuerProvisioningExtensionHandler {

override func status(completion: @escaping (PKIssuerProvisioningExtensionStatus) -> Void) {
// Determines if there is a pass available and if adding the pass requires authentication.
// The completion handler takes a parameter status of type PKIssuerProvisioningExtensionStatus that indicates
// whether there are any payment cards available to add as Wallet passes.

// PKIssuerProvisioningExtensionStatus has the following properties:
// requiresAuthentication: Bool - authorization required before passes can be added.
// passEntriesAvailable: Bool - passes will be available to add (at least one).
// remotePassEntriesAvailable: Bool - passes will be available to add on the remote device (at least one).

// The handler should be invoked within 100ms. The extension is not displayed to the user in Wallet if this criteria is not met.
}

override func passEntries(completion: @escaping ([PKIssuerProvisioningExtensionPassEntry]) -> Void) {
// Finds the list of passes available to add to an iPhone.
// The completion handler takes a parameter entries of type Array<PKIssuerProvisioningExtensionPassEntry> representing
// the passes that are available to add to Wallet.

// Call MeaPushProvisioning.initializeOemTokenization(cardParams, completionHandler: { (data: MppInitializeOemTokenizationResponseData, error: Error?) in ... }) and initialize PKIssuerProvisioningExtensionPaymentPassEntry for each card that can be added to Wallet and add to the array.
// Use addPaymentPassRequestConfiguration of MppInitializeOemTokenizationResponseData object to set addRequestConfiguration.

// PKIssuerProvisioningExtensionPaymentPassEntry has the following properties:
// art: CGImage - image representing the card displayed to the user. The image must have square corners and should not include personally identifiable information like user name or account number.
// title: String - a name for the pass that the system displays to the user when they add or select the card.
// identifier: String - an internal value the issuer uses to identify the card. This identifier must be stable.
// addRequestConfiguration: PKAddPaymentPassRequestConfiguration - the configuration data used for setting up and displaying a view controller that lets the user add a payment pass.

// Do not return payment passes that are already present in the user’s pass library.
// The handler should be invoked within 20 seconds or will be treated as a failure and the attempt halted.
}

override func remotePassEntries(completion: @escaping ([PKIssuerProvisioningExtensionPassEntry]) -> Void) {
// Finds the list of passes available to add to an Apple Watch.
// The completion handler takes a parameter entries of type Array<PKIssuerProvisioningExtensionPassEntry> representing
// the passes that are available to add to Apple Watch.

// Call MeaPushProvisioning.initializeOemTokenization(cardParams, completionHandler: { (data: MppInitializeOemTokenizationResponseData, error: Error?) in ... }) and initialize PKIssuerProvisioningExtensionPaymentPassEntry for each card that can be added to Wallet and add to the array.
// Use addPaymentPassRequestConfiguration of MppInitializeOemTokenizationResponseData object to set addRequestConfiguration.

// PKIssuerProvisioningExtensionPaymentPassEntry has the following properties:
// art: CGImage - image representing the card displayed to the user. The image must have square corners and should not include personally identifiable information like user name or account number.
// title: String - a name for the pass that the system displays to the user when they add or select the card.
// identifier: String - an internal value the issuer uses to identify the card. This identifier must be stable.
// addRequestConfiguration: PKAddPaymentPassRequestConfiguration - the configuration data used for setting up and displaying a view controller that lets the user add a payment pass.

// Do not return payment passes that are already present in the user’s pass library.
// The handler should be invoked within 20 seconds or will be treated as a failure and the attempt halted.
}

override func generateAddPaymentPassRequestForPassEntryWithIdentifier(
_ identifier: String,
configuration: PKAddPaymentPassRequestConfiguration,
certificateChain certificates: [Data],
nonce: Data,
nonceSignature: Data,
completionHandler completion: @escaping (PKAddPaymentPassRequest?) -> Void) {

// Creates an object with the data the system needs to add a card to Apple Pay.

// identifier: String - an internal value the issuer uses to identify the card.
// configuration: PKAddPaymentPassRequestConfiguration - the configuration the system uses to add a secure pass. This configuration is prepared in methods passEntriesWithCompletion: and remotePassEntriesWithCompletion:.
// certificates, nonce, nonceSignature - parameters are generated by Apple Pay identically to PKAddPaymentPassViewControllerDelegate methods.

// The completion handler is called by the system for the data needed to add a card to Apple Pay.
// This handler takes a parameter request of type PKAddPaymentPassRequestConfiguration that contains the card data the system needs to add a card to Apple Pay.

// Call MeaPushProvisioning.completeOemTokenization(tokenizationData, completionHandler: { (data: MppCompleteOemTokenizationResponseData, error: Error?) in ... }), and
// use addPaymentPassRequest of MppCompleteOemTokenizationResponseData to set request in completion handler.

// The continuation handler must be called within 20 seconds or an error is displayed.
// Subsequent to timeout, the continuation handler is invalid and invocations is ignored.
}
}

5. Updating Extension's Info.plist

  • Modify NSExtension dictionary in extension's Info.plist, delete NSExtensionAttributes entry.

NSExtensionPointIdentifier and NSExtensionPrincipalClass should be specified in the extension Info.plist properties dictionary:

Non-UI App Extension
KeyTypeValue
NSExtensionPointIdentifierStringcom.apple.PassKit.issuer-provisioning
NSExtensionPrincipalClassStringIssuerExtensionHandler

Use $(PRODUCT_MODULE_NAME).IssuerExtensionHandler for App Extension in Swift.

Issuer Extensions Target

UI App Extension
KeyTypeValue
NSExtensionPointIdentifierStringcom.apple.PassKit.issuer-provisioning.authorization
NSExtensionPrincipalClassStringIssuerAuthorizationExtensionHandler

Use $(PRODUCT_MODULE_NAME).IssuerAuthorizationExtensionHandler for App Extension in Swift.

Issuer Extensions Target

6. Setting Code Signing Entitlements

  • Issuer Extensions use the same entitlement file used for issuer app In-App Provisioning.

Issuer Extensions Target