Apple Pay In-App Provisioning enables cardholders to add their payment cards to Apple Wallet directly from within your app. With this feature, cardholders can quickly and securely add their payment information to their Apple Wallet, without having to manually enter their card details.
Adyen Apple Pay Provisioning SDK is available on GitHub. Installation instructions can be found there.
Before you start implementing the Adyen SDK, make sure that your system follows the requirements:
With Apple Pay in-app provisioning, your cardholder can start add their card directly from your app. During the in-app flow, the cardholder taps Add to Apple Wallet and the provisioning process starts and finishes within your app providing a seamless flow.
The following diagram walks you through the in-app provisioning flow. Green labels correspond to the steps described further on the page:
Before you start card provisioning, you must get activation data for the specified payment instrument.
From your server, make a GET /paymentInstruments/{id}/networkTokenActivationData
request and specify the id
of the payment instrument in the path. To make this request, your API credential needs the following role:
curl https://balanceplatform-api-test.adyen.com/bcl/v2/paymentInstruments/{id}/networkTokenActivationData \
-H 'x-api-key: YOUR_BALANCE_PLATFORM_API_KEY' \
-H 'content-type: application/json' \
The response contains the sdkInput
object that you need to initialize the provisioning service in the next step.
Pass the sdkInput
to your app.
Initialize the ProvisioningService
class that is used to check if the cardholder can add a payment card to their Apple Wallet.
import AdyenApplePayProvisioning
let provisioningService = ProvisioningService(sdkInput: sdkInput)
let canAddCard = await provisioningService.canAddCard()
// Show or hide the Add card to Apple Wallet button
Use the value returned by the canAddCard
method to show or hide the Add card to Apple Wallet button.
[!NOTE] If available, you can add a payment card to both iPhone and Apple Watch. To determine the compatibility of the card with a particular device, use the
canAddCardDetails()
function.
When the cardholder selects Add card to Apple Wallet, use the Adyen SDK to initiate provisioning by calling the start()
function with two parameters: delegate
and presentingViewController
.
try provisioningService.start(
delegate: self,
presentingViewController: viewController
)
Implement ProvisioningServiceDelegate
to receive the provision(sdkOutput)
callback from the Adyen SDK. In the callback:
paymentInstruments/{id}/networkTokenActivationData
request and pass sdkOutput
to provision the payment instrument. The response contains the sdkInput
object.Return sdkInput
from the provision
method.
func provision(sdkOutput: Data) async -> Data? {
let sdkInput = // POST `sdkOutput` to server
return sdkInput
}
After the provisioning is complete, the Adyen SDK calls the delegate method didFinishProvisioning
. This callback can be used to update your UI and show Added to Apple Wallet or Available in Apple Wallet.
func didFinishProvisioning(with pass: PKPaymentPass?, error: Error?) {
// Update your UI
}
You can also start card provisioning directly from Apple Wallet. For this to work, you must implement the Apple Wallet extension. After this, your app name and icon will appear in the From Apps on Your iPhone list of cards available to be added to the Wallet.
The following diagram walks you through the wallet extension provisioning flow. Green labels correspond to the steps described further on the page:
Before you start implementing the Wallet provisioning flow:
To add the Apple Wallet extension:
Add a new target to your Xcode project with any Extension template. Then, change the values under the NSExtension
dictionary in the Info.plist
as follows:
Key | Type | Value |
---|---|---|
NSExtension |
String | com.apple.PassKit.issuer-provisioning |
NSExtensionPrincipalClass |
String | $(PRODUCT_MODULE_NAME).ExtensionHandler |
Create an ExtensionHandler
class that implements the PKIssuerProvisioningExtensionHandler
protocol. This class manages the issuer-provided extension that is used during the card provisioning process.
import PassKit
class ExtensionHandler: PKIssuerProvisioningExtensionHandler {
}
The Apple Wallet authenticates cardholders before allowing them to provision cards. The user interface for this authentication can be implemented by your app, by adding an additional extension. In this extension, you can implement UI to authenticate your user.
Add another target to your Xcode project with any Extension template. Change the values under the NSExtension
dictionary in the Info.plist
as follows:
Key | Type | Value |
---|---|---|
NSExtension |
String | com.apple.PassKit.issuer-provisioning.authorization |
NSExtensionPrincipalClass |
String | $(PRODUCT_MODULE_NAME).AuthorizationProvider |
Create an AuthorizationProvider
class that is a subclass of UIViewController
and implements the PKIssuerProvisioningExtensionAuthorizationProviding
protocol. Use the completionHandler
to communicate if the authentication was successful.
The Apple Wallet will call the extension while the user is interacting with the Wallet app. Because of this, the extension has strict limits on how much time certain steps can take to execute. The first method that is called on your extension, status()
, needs to complete within 100 ms. Therefore, it is not possible to reliably to a network call to initialize the Adyen SDK.
In your main app, save the sdkInput
after doing the network call. Retrieve this stored value in your extension.
Initialize the ExtensionProvisioningService
class. This class manages the custom extension used during the provisioning process for adding new payment cards to the Apple Wallet.
import AdyenApplePayExtensionProvisioning
// For one payment instrument:
let provisioningService = ExtensionProvisioningService(sdkInput: sdkInput)
// For multiple payment instruments:
let provisioningService = ExtensionProvisioningService(sdkInputs: [sdkInput1, sdkInput2, ..])
To implement the status()
method of the PKIssuerProvisioningExtensionHandler
protocol, call extensionStatus()
on the Adyen SDK. Use the requiresAuthentication
parameter to indicate that you will provide authentication through the UI extension. If the ExtensionProvisioningService
could not be initialized, use entriesUnavailableExtensionStatus
to indicate that no pass entries are available.
func status() async -> PKIssuerProvisioningExtensionStatus {
// Initialize the service
return provisioningService.extensionStatus(requiresAuthentication: true)
// If service could not be initialized
return ExtensionProvisioningService.entriesUnavailableExtensionStatus
}
To return available passes, implement two methods: passEntries()
and remotePassEntries()
. The first method will be called from the Wallet app, the second from the Watch app on iPhone or the Wallet app on Apple Watch. For this step, the time limit is 20 seconds. Because of this, a network call can be performed to refresh the sdkInput
.
From your server, make a GET /paymentInstruments/{id}/networkTokenActivationData
request and specify the id
of the payment instrument in the path.
curl https://balanceplatform-api-test.adyen.com/bcl/v2/paymentInstruments/{id}/networkTokenActivationData \
-H 'x-api-key: YOUR_BALANCE_PLATFORM_API_KEY' \
-H 'content-type: application/json' \
The response contains the sdkInput
that you need to initialize the provisioning service in the next step. If the extension needs to show multiple payment instruments, request sdkInput
for each one.
Initialize the ExtensionProvisioningService
class.
import AdyenApplePayExtensionProvisioning
// For one payment instrument:
let provisioningService = ExtensionProvisioningService(sdkInput: sdkInput)
// For multiple payment instruments:
let provisioningService = ExtensionProvisioningService(sdkInputs: [sdkInput1, sdkInput2, ..])
The Wallet app will show a preview of the card(s) to the user. To supply the SDK with the correct image of the card, implement ExtensionProvisioningServiceDelegate
to receive the cardArt(forBrand: String)
callback from the Adyen SDK. In this method, return the card art for the specified brand. Make sure the card art accurately represents the card that will be added to Apple Wallet.
func cardArt(forBrand brand: String) -> CGImage {
// Return card art
}
Implement the methods passEntries()
and remotePassEntries()
by calling the corresponding methods of the SDK. Supply the delegate created in the previous step.
func passEntries() async -> [PKIssuerProvisioningExtensionPassEntry] {
return provisioningService.passEntries(withDelegate: delegate)
}
func remotePassEntries() async -> [PKIssuerProvisioningExtensionPassEntry] {
return provisioningService.remotePassEntries(withDelegate: delegate)
}
To provision the card when the extension requests it:
ExtensionProvisioningServiceDelegate
to receive the provision(paymentInstrumentId, sdkOutput)
callback from the Adyen SDK.paymentInstruments/{id}/networkTokenActivationData
request and pass sdkOutput
to provision the payment instrument.Return the sdkInput
from the response back to Adyen SDK.
func provision(paymentInstrumentId: String, sdkOutput: Data) async -> Data? {
let sdkInput = // POST `sdkOutput` to server
return sdkInput
}
To generate a request for adding a card:
The ExtensionHandler
class will be re-instantiated for each PKIssuerProvisioningExtensionHandler
method call. This means that you will need to re-initialize the Adyen SDK. Follow steps 1 and 2 from [the previous chapter]{#return-pass-entries}.
To implement the generateAddPaymentPassRequestForPassEntryWithIdentifier()
method of the PKIssuerProvisioningExtensionHandler
class, call the method with the same name from the Adyen SDK to obtain a pass request. This call has an additional parameter delegate
that will be called to handle card provisioning. The method returns the pass request, that can then be passed to the extension.
func generateAddPaymentPassRequestForPassEntryWithIdentifier(
_ identifier: String,
configuration: PKAddPaymentPassRequestConfiguration,
certificateChain certificates: [Data],
nonce: Data,
nonceSignature: Data
) async -> PKAddPaymentPassRequest? {
try? await provisioningService.generateAddPaymentPassRequestForPassEntryWithIdentifier(
identifier,
configuration: configuration,
certificateChain: certificates,
nonce: nonce,
nonceSignature: nonceSignature,
delegate: self
)
}