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.
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.
- 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.
- Use of the Wallet Extension requires the same entitlement file used for In-App Provisioning:
com.apple.developer.payment-pass-provisioning
. - 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
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:
- 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. - 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.
- 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
- Activate the created scheme, if asked.
- Add
PassKit.framework
toFrameworks and Libraries
of theIssuerNonUIExtension
andIssuerUIExtension
targets, and removeIntents.framework
which is not needed. - Remove
Intents.framework
of theIssuerUIExtension
target.
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
toIssuerNonUIExtension
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 toPKIssuerProvisioningExtensionAuthorizationProviding
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.
- swift
- objective-c .h
- objective-c .m
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)
}
}
#import <PassKit/PassKit.h>
#import <UIKit/UIKit.h>
API_AVAILABLE(ios(14.0))
@interface IssuerAuthorizationExtensionHandler : UIViewController <PKIssuerProvisioningExtensionAuthorizationProviding>
@end
#import "IssuerAuthorizationExtensionHandler.h"
@implementation IssuerAuthorizationExtensionHandler
@synthesize completionHandler;
- (void)viewDidLoad
{
[super viewDidLoad];
// Set up view and authenticate user.
}
- (void)authenticateUser
{
BOOL userAuthenticated = YES; // User authentication outcome.
PKIssuerProvisioningExtensionAuthorizationResult authorizationResult = (userAuthenticated) ? PKIssuerProvisioningExtensionAuthorizationResultAuthorized : PKIssuerProvisioningExtensionAuthorizationResultCanceled;
self.completionHandler(authorizationResult);
}
@end
4. Implementing Issuer Extension Handler
IssuerExtensionHandler
non-UI class must be a subclass ofPKIssuerProvisioningExtensionHandler
. 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.
- swift
- objective-c .h
- objective-c .m
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.
}
}
#import <PassKit/PassKit.h>
API_AVAILABLE(ios(14.0))
@interface IssuerExtensionHandler : PKIssuerProvisioningExtensionHandler
@end
#import <MeaPushProvisioning/MeaPushProvisioning.h>
#import "IssuerExtensionHandler.h"
@implementation IssuerExtensionHandler
- (void)statusWithCompletion:(void(^)(PKIssuerProvisioningExtensionStatus *status))completion
{
// 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:
// BOOL requiresAuthentication - authorization required before passes can be added.
// BOOL passEntriesAvailable - passes will be available to add (at least one).
// BOOL remotePassEntriesAvailable - 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.
}
- (void)passEntriesWithCompletion:(void(^)(NSArray<PKIssuerProvisioningExtensionPassEntry *> *entries))completion
{
// Finds the list of passes available to add to an iPhone.
// The completion handler takes a parameter entries of type NSArray<PKIssuerProvisioningExtensionPassEntry *> representing
// the passes that are available to add to Wallet.
// Call [MeaPushProvisioning initializeOemTokenization:cardParams completionHandler:^(MppInitializeOemTokenizationResponseData *data, NSError *error) {...}] 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:
// CGImageRef art - 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.
// NSString *title - a name for the pass that the system displays to the user when they add or select the card.
// NSString *identifier - an internal value the issuer uses to identify the card. This identifier must be stable.
// PKAddPaymentPassRequestConfiguration *addRequestConfiguration - 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.
}
- (void)remotePassEntriesWithCompletion:(void(^)(NSArray<PKIssuerProvisioningExtensionPassEntry *> *entries))completion
{
// Finds the list of passes available to add to an Apple Watch.
// The completion handler takes a parameter entries of type NSArray<PKIssuerProvisioningExtensionPassEntry *> representing
// the passes that are available to add to Apple Watch.
// Call [MeaPushProvisioning initializeOemTokenization:cardParams completionHandler:^(MppInitializeOemTokenizationResponseData *data, NSError *error) {...}] 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:
// CGImageRef art - 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.
// NSString *title - a name for the pass that the system displays to the user when they add or select the card.
// NSString *identifier - an internal value the issuer uses to identify the card. This identifier must be stable.
// PKAddPaymentPassRequestConfiguration *addRequestConfiguration - 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.
}
- (void)generateAddPaymentPassRequestForPassEntryWithIdentifier:(NSString *)identifier
configuration:(PKAddPaymentPassRequestConfiguration *)configuration
certificateChain:(NSArray<NSData *> *)certificates
nonce:(NSData *)nonce
nonceSignature:(NSData *)nonceSignature
completionHandler:(void(^)(PKAddPaymentPassRequest * _Nullable request))completion
NS_SWIFT_NAME(generateAddPaymentPassRequestForPassEntryWithIdentifier(_:configuration:certificateChain:nonce:nonceSignature:completionHandler:))
{
// Creates an object with the data the system needs to add a card to Apple Pay.
// NSString *identifier - an internal value the issuer uses to identify the card.
// PKAddPaymentPassRequestConfiguration *configuration - 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:^(MppCompleteOemTokenizationResponseData *data, NSError *error) {...}], 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.
}
@end
5. Updating Extension's Info.plist
- Modify
NSExtension
dictionary in extension'sInfo.plist
, deleteNSExtensionAttributes
entry.
NSExtensionPointIdentifier and NSExtensionPrincipalClass should be specified in the extension Info.plist
properties dictionary:
Non-UI App Extension
Key | Type | Value |
---|---|---|
NSExtensionPointIdentifier | String | com.apple.PassKit.issuer-provisioning |
NSExtensionPrincipalClass | String | IssuerExtensionHandler |
Use $(PRODUCT_MODULE_NAME).IssuerExtensionHandler
for App Extension in Swift.
UI App Extension
Key | Type | Value |
---|---|---|
NSExtensionPointIdentifier | String | com.apple.PassKit.issuer-provisioning.authorization |
NSExtensionPrincipalClass | String | IssuerAuthorizationExtensionHandler |
Use $(PRODUCT_MODULE_NAME).IssuerAuthorizationExtensionHandler
for App Extension in Swift.
6. Setting Code Signing Entitlements
- Issuer Extensions use the same entitlement file used for issuer app In-App Provisioning.