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 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. Check if the cardholder can add a payment card to their Apple Wallet (phone or watch). If the card cannot be added it means it is already in the wallet. You can use WatchAvailability
class to determine if the watch is paired.
import AdyenApplePayProvisioning
// Create only one instance of WatchAvailability
let watchAvailability = WatchAvailability()
let provisioningService = ProvisioningService(sdkInput: sdkInput)
let isWatchActivated = await watchAvailability.activate()
let state = provisioningService.canAddCardDetails(isWatchActivated: isWatchActivated)
if state.canAddCard {
// show "Add to Apple Wallet" button
}
Use the value returned by the canAddCard
method to show or hide the Add card to Apple Wallet button.
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, paymentInstrumentId)
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.sdkInput
from the provision
method.func provision(sdkOutput: Data, paymentInstrumentId: String) async -> Data? {
struct ProvisioningBody: Encodable {
let sdkOutput: Data
}
let encoder = JSONEncoder()
encoder.dataEncodingStrategy = .base64
do {
let body = try encoder.encode(ProvisioningBody(sdkOutput: sdkOutput))
let sdkInput = // POST the body to the server and receive `sdkInput` back
return sdkInput
} catch {
return nil
}
}
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 |
---|---|---|
NSExtensionPointIdentifier |
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 |
---|---|---|
NSExtensionPointIdentifier |
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 make a network call to initialize the Adyen SDK, instead it’s advisable to use cached activation data (e.g. stored in the keychain).
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, ..])
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, ..])
ExtensionProvisioningServiceDelegate
to receive the cardArt(paymentInstrumentId: String)
callback from the Adyen SDK. In this method, return the card art for the specified paymentInstrumentId. Make sure the card art accurately represents the card that will be added to Apple Wallet.func cardArt(paymentInstrumentId: String) -> CGImage {
// Return card art
}
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.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
)
}