# Add a card to a digital wallet
You can enable cardholders to add cards to their Apple or Google wallet with push provisioning in your mobile app or the Apple Wallet app, using the Card Management Android or iOS SDK.
Push provisioning means the cardholder does not have to enter the card details manually.
It involves multiple parties including you, Checkout.com, the card scheme, and the wallet provider.
> **Information**
>
> To enable and onboard for push provisioning, contact your account manager.
> Your account manager collects your configuration requirements and shares your unique issuer ID.
> **Note**
>
> You must migrate to Android SDK version 3.0.0 or iOS SDK version 4.0.0. For guidance on how to update your integration, see Support – [Issuing Card Management SDKs migration guide](https://support.checkout.com/hc/en-us/articles/32194131879698).
---
## Before you start
You must:
1. Complete your [Issuing onboarding](https://www.checkout.com/docs/card-issuing/set-up-card-program.md).
2. Integrate the Card Management [Android SDK](https://www.checkout.com/docs/developer-resources/sdks/card-issuing-sdks/card-management-android-sdk.md) or [iOS SDK](https://www.checkout.com/docs/developer-resources/sdks/card-issuing-sdks/card-management-ios-sdk.md).
- Ensure you import the [Android additional libraries](https://www.checkout.com/docs/developer-resources/sdks/card-issuing-sdks/card-management-android-sdk.md#Add_libraries) or [iOS dependencies and libraries](https://www.checkout.com/docs/developer-resources/sdks/card-issuing-sdks/card-management-ios-sdk.md#Add_dependencies).
- Ensure you're using the SDK's `CheckoutCardManagement` library.
3. Create or receive your [private/public key pair](#Private/public_key_pair).
4. Add your testing device to the [device allowlist](#Device_allowlists).
4. Implement the [push-provisioning prerequisites](#Push-provisioning_prerequisites).
> **Note**
>
> If you attempt push provisioning without completing your Issuing or push-provisioning onboarding, your app intentionally crashes.
### Private/public key pair
Push provisioning requires a private/public key pair, which can be generated by either Checkout.com or you.
- Private key – You use this to sign the [JSON Web Token (JWT)](#Provide_a_JSON_Web_Token).
- Public key – The server uses this to verify the OpenID access token provided by the issuer's identity provider (IdP). This ensures that the JWT was signed with the private key that corresponds to the public key.
Example – `ABCDOEFGHIJKLMNOPqRsTu+VWx1Yzabc+DEfGhIj2kl/mN3opQr4STUV5wx6Y78z`
If Checkout.com generates the key pair, we securely share the private key with you.
If you generate the key pair, share it with Checkout.com through a safe store.
To generate the key pair, run the following command, providing your issuer ID from your push-provisioning onboarding:
```Command
$ openssl ecparam -name prime256v1 -genkey -noout -out is_cko_{Issuer_id}-jwt-priv-key.pem
$ openssl ec -in is_cko_cko-jwt-priv-key.pem -pubout > is_cko_{Issuer_id}-jwt-pub-key.pem
```
### Device allowlists
Checkout.com recommends testing push provisioning on a physical device. You must add your testing device to the allowlist for your platform:
**Android allowlist**
1. Retrieve your SHA-256 fingerprint:
- Open Android Studio and go to the _Gradle_ tab.
- Select the _Execute Gradle Task_ icon.
- In the modal that appears, enter `gradle signinReport`, and then press _Enter_.
- Copy the SHA-256 value from the report output.
2. Complete the [Push Provisioning API Access Request](https://support.google.com/faqs/contact/pp_api_allowlist) form as follows:
- Company name – Enter Checkout.com.
- Google Wallet use case – Select _Payments card_.
- Option which best describes your company – Select _Card Issuer_.
- App package name – Enter [your.app.package]. For example, `com.checkout.levant`.
- App package environment – Select _Production_.
- Fingerprints – Enter your SHA-256 fingerprint.
- Target launch date – Enter a date in the future, in `MM/DD/YYYY` format.
- Network – Select _Mastercard_ as the card scheme.
**iOS allowlist**
1. Go to _Settings_ > _General_ > _About_ > _SEID_ to retrieve the device's Secure Element ID (SEID).
2. Email the SEID to [seidreporting@apple.com](mailto:seidreporting@apple.com).
It may take 24 hours for your device to be added to the allowlist.
### Push-provisioning prerequisites
Implement the required push-provisioning prerequisites for your platform:
**Android prerequisite**
You must [request access](https://developers.google.com/pay/issuers/apis/push-provisioning/android/allowlist) to the Android Push Provisioning API.
**iOS prerequisites**
To enable push provisioning when the cardholder starts their journey in your app or in the Apple Wallet app, you must implement two Apple extensions at the same time as your app: the Intents Extension and the Intents UI Extension.
> **Information**
>
> This is a certification requirement from Apple.
> **Information**
>
> For more information on Apple app extensions, see Apple Developer – [Understand How an App Extension Works](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionOverview.html).
Follow these steps:
1. [Add an app bundle ID](#Add_an_app_bundle_ID).
2. [Create an app group ID](#Create_an_app_group_ID).
3. [Add payment-pass-provisioning entitlement](#Add_payment-pass-provisioning_entitlement).
4. [Add Intents Extension](#Add_Intents_Extension).
5. [Add Intents UI Extension](#Add_Intents_Extension).
#### Add an app bundle ID
To enable Apple Wallet to recognize the extensions and the eligible cards, you must add an app bundle ID to the Intents Extension and Intents UI Extension in the `associatedApplicationIdentifier` of your Payment Network Operator (PNO).
For example, `com.checkout.Levant`.
Alternatively, you can do this using a wildcard bundle ID.
#### Create an app group ID
To enable your app to share storage with the extensions, you must create an app group ID and add it to your app and both extensions.
For example, if your app bundle ID is `com.checkout.Levant`, use the following for your app group ID: `group.com.checkout.LevantApplication`.
1. To create the app group ID, see Apple Developer – [Configuring app groups](https://developer.apple.com/documentation/xcode/configuring-app-groups).
2. Add the ID to your app in your xCode project, under _Signing & Capabilities_ > _Capability_.
#### Add in-app provisioning capability
You must add the `payment-pass-provisioning` entitlement to your app and both extensions.
1. Request Apple's permission to add this entitlement at [apple-pay-provisioning@apple.com](mailto:apple-pay-provisioning@apple.com).
2. Add the capability to your app in your xCode project, under _Signing & Capabilities_ > _Capability_.
> **Note**
>
> If your iOS app does not have the `payment-pass-provisioning` entitlement and you trigger a tokenization flow in the SDK, you may receive a _missing entitlement: com.apple.developer.payment-pass-provisioning_ error from Apple and an SDK operation error.
> **Information**
>
> For more information, see:
> - Apple Developer – [Adding Capabilities to Your App](https://developer.apple.com/documentation/xcode/adding-capabilities-to-your-app)
> - Apple Developer – [Entitlement Key Reference](https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/EntitlementKeyReference/ApplePayandPassKitEntitlements/ApplePayandPassKitEntitlements.html#//apple_ref/doc/uid/TP40011195-CH7-SW4)
> - Thales D1 Developer Portal – [iOS Push Provisioning](https://docs.payments.thalescloud.io/classic-push-provisioning/use-cases/push-provisioning-to-xpay-wallets/ios/push-provisioning)
#### Add Intents Extension
When the cardholder starts their journey in the Apple Wallet app, the back-end Intents Extension lets Apple Wallet check with your app if the cardholder has any cards that can be digitized.
If there are eligible cards, Apple Wallet displays your app's logo to the cardholder in a list of issuer apps.
The cardholder selects your app and authenticates.
Apple Wallet retrieves and displays the list of eligible cards to the cardholder, which they can then add to their wallet.
To add the extension:
1. In your xCode project, go to _File_ > _New_ > _Target_.
2. Select _Intents Extension_.
3. Add your app group ID.
4. In the _WalletExtension.entitlements_ file, add the in-app provisioning capability as follows:
```xml
com.apple.developer.payment-pass-provisioning
com.apple.security.application-groups
group.com.yourOrgName.YourAppName
```
5. In the `Info.plist` file, add the `Extension-IntentHandler` as follows:
```xml
NSExtension
NSExtensionPointIdentifier
com.apple.PassKit.issuer-provisioning
NSExtensionPrincipalClass
$(PRODUCT_MODULE_NAME).Extension-IntentHandler
```
The `Extension-IntentHandler` is the name of the `IntentHandler` class in the Intents Extension.
> **Information**
>
> `Extension-IntentHandler` does not provide the card details. To retrieve card details, call the `cardManager.configurePushProvisioning()` method.
It must conform with the Checkout.com `IssuerProvisioningExtensionHandler` class as follows:
```swift
@available(iOS 14.0, *)
class IntentHandler: IssuerProvisioningExtensionHandler {
final public override func onError(_ error: CardManagementError.ProvisioningExtensionFailure) {
// Optional method to handle potential errors when the card is added to Apple Wallet
}
}
```
This is an optional method to handle potential errors when the card is added to Apple Wallet if:
- The app group ID in the `cardManager.configurePushProvisioning()` method is incorrect.
- There are server errors.
> **Note**
>
> Always specify a minimum deployment for the Intents Extension. If you do not, Apple defaults to the latest iOS version and push provisioning fails for users with older versions.
6. Import the `CheckoutCardManagement` dependency into the extension target under _General_ > _Frameworks and Libraries_.
#### Add Intents UI Extension
The front-end Intents UI Extension provides the screen in the Apple Wallet app to authenticate the cardholder in your app.
To add the extension:
1. In your xCode project, go to _File_ > _New_ > _Target_.
2. Select _Intents UI Extension_.
3. Add your app group ID.
4. In the _WalletExtension.entitlements_ file, add the in-app provisioning capability as follows:
```xml
com.apple.developer.payment-pass-provisioning
com.apple.security.application-groups
group.com.checkout.LevantApplication
```
5. In the `Info.plist` file, add the `Extension-IntentViewController` as follows:
```xml
NSExtension
NSExtensionPointIdentifier
com.apple.PassKit.issuer-provisioning.authorization
NSExtensionPrincipalClass
$(PRODUCT_MODULE_NAME).Extension-IntentViewController
```
The `Extension-IntentViewController` is the name of the `IntentViewController` class in the Intents UI Extension.
It must conform with the Checkout.com `IssuerProvisioningExtensionAuthorizationProviding` protocol as follows:
```swift
@available(iOS 14.0, *)
class IntentViewController: UIViewController, IssuerProvisioningExtensionAuthorizationProviding {
var completionHandler: ((PKIssuerProvisioningExtensionAuthorizationResult) -> Void)?
private let authButton = UIButton(type: .system)
override func viewDidLoad() {
super.viewDidLoad()
// UI configurations
authButton.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
@IBAction func buttonTapped(_ sender: UIButton) {
// Method to retrieve the cardholder ID from your app
let cardholderID = fetchCardHolderId()
// Fetched for the cardholder ID after authentication succeeds
let issuerToken = fetchIssuerToken()
// Method to tell the Apple Wallet app to continue the flow
login(issuerToken) { [weak self] (error: CardManagementError.ProvisioningExtensionFailure?) in
if error != nil {
self?.completionHandler?(.canceled)
} else {
self?.completionHandler?(.authorized)
}
}
}
}
```
> **Note**
>
> Always specify a minimum deployment for the Intents UI Extension. If you do not, Apple defaults to the latest iOS version and push provisioning fails for users with older versions.
6. Import the `CheckoutCardManagement` dependency into the extension target under _General_ > _Frameworks and Libraries_.
> **Information**
>
> For more information on how to share data between your app and the Intents UI Extension, see Apple Developer App Extension Programming Guide – [Handling Common Scenarios](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html).
---
## How it works
Once you have completed the setup, the flow for the cardholder to add their card to their wallet is as follows:
1. Authenticate the cardholder.
2. Retrieve the cardholder's cards.
3. Configure push provisioning.
4. Provide a JSON Web Token.
5. If you're provisioning from within your app, verify the cards' digitization status.
6. Provision the card.
---
## Authenticate the cardholder
You are responsible for performing [Strong Customer Authentication (SCA)](https://www.checkout.com/docs/business-operations/ensure-regulatory-compliance/sca-compliance.md) on the cardholder for each session where they use functionality provided by the SDK.
This applies to both the sandbox and production SDK environments.
Pass an [access token](https://api-reference.checkout.com/tag/Access/#operation/requestAnAccessToken) that your app receives from your authentication back end.
**Android SDK**
```kotlin
val token = "{Access_token}"
cardManager.logInSession(token)
```
**iOS SDK**
```swift
let token = "{Access_token}"
cardManager.logInSession(token: accessToken)
```
---
## Retrieve the cardholder's cards
Once you’ve authenticated the cardholder and your app, call the `cardManager.getCards()` method to retrieve a list of their cards:
**Android SDK**
```kotlin
cardManager.getCards { result: Result> ->
result.onSuccess {
// You receive a list of the cardholder's cards that you can display in your UI
// Returned card details include the last four digits of the PAN, expiry date, cardholder name, card status, and card ID
}.onFailure {
// If something goes wrong, you receive an error with more information
}
}
```
**iOS SDK**
```swift
cardManager.getCards { result in
switch result {
case .success(let cards):
// You receive a list of the cardholder's cards that you can display in your UI
// Returned card details include last four digits of the PAN, expiry date, cardholder name, card status, and card ID
case .failure(let error):
// If something goes wrong, you receive an error with more information
}
}
```
---
## Configure push provisioning
**Android SDK**
1. Call the `checkoutCardManager.configurePushProvisioning()` method:
```kotlin
{
val config = ProvisioningConfiguration(...)
val result = cardManager.configurePushProvisioning(
activity = "{Activity_to_handle_the_provision_outcome}",
cardholderId = "{ID_of_cardholder_performing_operation}",
configuration = config
)
result.onSuccess { /* Configuration complete */ }
.onFailure { error -> /* Handle error */ }
}
// DEPRECATED using callbacks
checkoutCardManager.configurePushProvisioning(
activity = "{Activity_to_handle_the_provision_outcome}",
cardholderId = "{ID_of_cardholder_performing_operation}",
configuration = ProvisioningConfiguration(/* */),
completionHandler = { result: Result -> /* Callback after the operation has completed*/ })
```
2. Provide a `ProvisioningConfiguration` object to the SDK as follows:
```kotlin
ProvisioningConfiguration(issuerID, // Your unique issuer identifier from Checkout.com
serviceRSAExponent, // As a bytearray object
serviceRSAModulus, // As a bytearray object
serviceURL, // The URL of the D1 Service Server
digitalCardURL) // The URL for the digital card operation
```
- `issuerID` – Provide your issuer ID from your push-provisioning onboarding.
- `serviceRSAExponent` and `serviceRSAModulus` – To retrieve these values from the Public Key Privacy Enhanced Mail (PEM) file, run the following command:
```Command
openssl rsa -pubin -inform PEM -text -noout < pubkey.pem
```
- `serviceURLString` – Provide the following URL: `https://client-api.d1.thalescloud.io/`.
- `digitalServiceURLString` – Provide the following URL: `https://hapi.dbp.thalescloud.io/mg/tpc`.
> **Information**
>
> For more information on the `ProvisioningConfiguration` object fields, see Thales D1 Developer Portal – [Initial Setup](https://docs.payments.thalescloud.io/push-provisioning/integrate-the-d1-sdk/getting-started/configuration/2.-onboarding/initial-setup).
**iOS SDK**
1. Call the `cardManager.configurePushProvisioning()` method:
```swift
do {
try await cardManager.configurePushProvisioning(cardholderID: "{ID_of_cardholder_performing_operation}",
appGroupId: "{ID_of_project_group_containing_app_and_extensions}",
configuration: configuration,
walletCards: walletCardsList)
} catch let error as CardManagementError {
// Handle the error
}
```
The card images must meet the same functional requirements as for Apple Pay and Direct Near-Field Communication Access.
For example:
- 1536 x 969 resolution
- Less than 4 MB
- Square corners
- No chip contacts
For more information, contact your Apple project contact, PNO, or service provider relationship manager.
2. Provide a `ProvisioningConfiguration` object to the SDK as follows:
```swift
ProvisioningConfiguration(issuerID, // Your unique issuer identifier from Checkout.com
serviceRSAExponent, // As a data object
serviceRSAModulus, // As a data object
serviceURLString, // The URL of the D1 Service Server
digitalServiceURLString) // The URL for the digital card operation
```
- `issuerID` – Provide your issuer ID from your push-provisioning onboarding.
- `serviceRSAExponent` and `serviceRSAModulus` – To retrieve these values from the Public Key Privacy Enhanced Mail (PEM) file, run the following command:
```Command
openssl rsa -pubin -inform PEM -text -noout < pubkey.pem
```
- `serviceURLString` – Provide the following URL: `https://client-api.d1.thalescloud.io/`.
- `digitalServiceURLString` – Provide the following URL: `https://hapi.dbp.thalescloud.io/mg/tpc`.
> **Information**
>
> For more information on the `ProvisioningConfiguration` object fields, see Thales D1 Developer Portal – [Initial Setup](https://docs.payments.thalescloud.io/push-provisioning/integrate-the-d1-sdk/getting-started/configuration/2.-onboarding/initial-setup).
---
## Provide a JSON Web Token
Provide a unique JSON Web Token (JWT) for every provisioning call for each device or app. Ensure the token is supported in your back end.
The token format must be unique and differ from all other tokens the SDK consumes.
> **Information**
>
> For more information, see Thales D1 Developer Portal – [SDK Login](https://docs.payments.thalescloud.io/click-to-pay/integrate-the-d1-sdk/getting-started/configuration/5.-authentication/sdk-login).
> For full guidance on generating JWTs, see Thales D1 Developer Portal – [Authorization Code (JWT Format)](https://docs.payments.thalescloud.io/classic-push-provisioning/developer-guide/data-encryption-and-authentication/authorization-code-jwt-format).
> Do not use any mobile end examples from this guidance in the production environment.
To generate the token, provide the following properties and sign it using your private key:
| Token property | Description | Generated token |
| --- | --- | --- |
| `scope`
`string` | The token scope:
`thales:d1` | `scope`
`string` |
| `subject`
`string` | The unique identifier for the cardholder. | `sub`
`string` |
| `issuedAt` | The date and time when you want the token issued, in UTC.
Set to the current date and time.
Format – ISO 8601 | `iat`
Android – date-time
iOS – double |
| `issuer`
`URL` | The token issuer, including your Checkout.com issuer ID from onboarding:
`https://bpsd1-demo-a2oidc.d1-dev.thalescloud.io/oidc/\{issuerId}` | `iss`
`URL` |
| `audience`
`URL` | The token audience, including your Checkout.com issuer ID:
`https://client-api.d1.thalescloud.io/oidc/\{issuerId}` | `aud`
`URL` |
| `expirationTime` | The date and time when the token expires, in UTC.
Set to a value less than 15 minutes after the current date and time.
Format – ISO 8601 | `exp`
Android – date-time
iOS – double |
| `jwtKeyID`
`string` | The token key identifier:
`d1api-{Your-Checkout.com-client-name}-01` | `kid`
`string` |
### Generated token example
`alg` is the encryption algorithm used. This can be one of: `ES256` or `RS256`.
```JSON
Header:
{
"alg" : "ES256",
"kid" : "d1api-{Your_Checkout.com_client_name}-01"
}
Payload:
{
"aud" : "https://client-api.d1.thalescloud.io/oidc/\{issuerId}",
"scope" : "thales:d1",
"iss" : "https://bpsd1-demo-a2oidc.d1-dev.thalescloud.io/oidc/\{issuerId}",
"exp" : "2025-01-31T10:20:30.456",
"sub" : "crh_d3ozhf43pcq2xbldn2g45qnb44",
"iat" : "2025-01-31T10:20:30.456"
}
Signature
{Signature generated using your private key and hash function}
```
---
## Verify digitization status
If you're provisioning from within your app, call the `card.getDigitizationState()` method to verify the digitization status of the cardholder's cards.
> **Note**
>
> If the cardholder has multiple cards, call this method for each card synchronously. If you attempt asynchronous calls, each new execution overrides the previous one.
**Android SDK**
```kotlin
{
when (val result = card.getDigitizationState(token)) {
is CardOperationResult.Success -> {
when (result.data) {
DigitizationState.DIGITIZED -> showDigitized()
DigitizationState.NOT_DIGITIZED -> showAddToWallet()
}
}
is CardOperationResult.Error -> showError(result.message)
}
}
```
A successful response returns:
| Property | Type | Description |
| --- | --- | --- |
| `state` | `DigitizationState` | The card's digitization status. |
| `localPKPass` | `PKPass`
See Apple – [PKPass](https://developer.apple.com/documentation/passkit/pkpass).
You can use these to provide extra information to the UI or better handle displaying the _Add to wallet_ button. | The card is digitized on the cardholder's iPhone. |
| `remotePKPass` | `PKPass`
See Apple – [PKPass](https://developer.apple.com/documentation/passkit/pkpass).
You can use these to provide extra information to the UI or better handle displaying the _Add to wallet_ button. | The card is digitized on the cardholder's Apple Watch. |
Cards can have the following digitization statuses:
| Digitization status | Description |
| --- | --- |
| `DIGITIZED` | The card is digitized on all associated devices. |
| `NOT_DIGITIZED` | The card is digitized on some but not all associated devices, or is not digitized on any associated devices.
**Note**: If you continue to receive this status after the card is added to the wallet, check that your app is correctly configured on the Token Service Provider (TSP) portal. |
| `PENDING_IDV` | Prompt the cardholder to [activate the card](https://www.checkout.com/docs/card-issuing/activate-a-card.md) to enable digitization on their device. |
| `DIGITIZATION_IN_PROGRESS` | The card is being digitized. |
**iOS SDK**
```swift
do {
let cardDigitizationData = try await card.getDigitizationState(provisioningToken: {JWT_generated_for_operation})
// Handle the card digitization result data
} catch let error as CardManagementError {
// Handle the error
}
```
Cards can have the following digitization statuses:
| Digitization status | Description |
| --- | --- |
| `digitized` | The card is digitized on all associated devices. |
| `notDigitized` | The card is digitized on some but not all associated devices, or is not digitized on any associated devices.
Display in your app an Add to Apple Wallet button.
For instructions, see Apple Developer – [PKAddPassButton](https://developer.apple.com/documentation/passkit/pkaddpassbutton).
**Note**: If you continue to receive this status after the card is added to the wallet, check that your app is correctly configured on the Token Service Provider (TSP) portal. |
| `pendingIDVLocal` | The cardholder must verify their identity to digitize the card on an Apple mobile device. For example, phone verification. |
| `pendingIDVRemote` | The cardholder must verify their identity to digitize the card on an Apple Watch. For example, phone verification. |
---
## Provision the card
**Android SDK**
1. Call the `card.provision()` method:
```kotlin
{
when (val result = card.provision(activity, token)) {
is CardOperationResult.Success -> {
showSuccess("Card added to wallet")
}
is CardOperationResult.Error -> showError(result.message)
}
}
```
2. Handle the push-provisioning response in the same activity you used for the `card.provision` method:
```kotlin
// Required to handle the card result
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
card.handleCardResult(requestCode, resultCode, data)
}
```
**iOS SDK**
Call the `card.provision()` method:
```swift
do {
try await card.provision(provisioningToken: {JWT_generated_for_operation})
// Handle the provisioning attempt
} catch let error as CardManagementError {
// Handle the error
}
```
> **Information**
>
> If you have integration questions or issues, contact your issuing representative or [issuing_operations@checkout.com](mailto:issuing_operations@checkout.com).
---
## Subscribe to webhooks
You can set up webhooks to be notified when cards are enrolled or their enrollment details are updated:
1. [Configure your webhook server](https://www.checkout.com/docs/developer-resources/event-notifications/receive-webhooks/configure-your-webhook-server.md).
2. Subscribe to the following webhooks:
- [Digital card pending](https://www.checkout.com/docs/developer-resources/event-notifications/event-types/digital_card_pending.md)
- [Digital card provisioned](https://www.checkout.com/docs/developer-resources/event-notifications/event-types/digital_card_provisioned.md)
- [Digital card not provisioned](https://www.checkout.com/docs/developer-resources/event-notifications/event-types/digital_card_not_provisioned.md)
- [Digital card deleted](https://www.checkout.com/docs/developer-resources/event-notifications/event-types/digital_card_deleted.md)
---
## Testing
**Android**
You can only test push provisioning in the SDK's production environment.
**iOS**
You can only test push provisioning in the SDK's production environment.
However, you can also test and mock method calls in the stub environment.
> **Note**
>
> If you use the SDK's `CheckoutCardManagementStub` library instead of `CheckoutCardManagement`, the push provisioning operation behaves as expected, but no interaction takes place between Checkout.com, Apple Wallet, or the card scheme.
>
> The result depends on which Checkout.com environment you use with the stub environment:
>
> - Sandbox environment – You receive a `pushProvisioningFailure` error, because push provisioning is only available in production.
> - Production environment – You receive an `OperationResult` success message.
### Provisioning validation
To validate push provisioning, you must test on a physical device using one of the following methods:
- Ad-hoc provisioning profiles
- TestFlight distribution – Ensure you've added the testing device to the allowlist.
Checkout.com has verified that TestFlight distribution results in correct tokenization flows, including usable push-provisioned cards in Apple Pay.
We recommend this for validating end-to-end tokenization flows.
> **Note**
>
> You can only validate using XCode if the build is distributed via TestFlight. This applies to debug builds, direct releases, and archived release builds installed directly on the physical device.
> The tokenization flow fails and you receive a _Could Not Add Card_ error from Apple.
---
## Troubleshooting
**Android**
### Authorization failure
If you receive the following `ERROR_CORE` authorization failure, ensure your testing device date is in the future:
```
ERROR_CORE [20001] (3) (401) {"error_reference":"TCsTqQepR8w4GRVnTVS7jlC1cTrk772v","securityEvent":true}
```
### Google Pay unavailable
You may receive an _API_UNAVAILABLE_ error code or message from Google Pay. For example:
```
AppsFilter: interaction: PackageSetting{ba9eff6 com.google.android.gms.tapandpay.issuer.devapp.littlebear/10337} -> PackageSetting{6e03871 com.checkout.levant/10405} BLOCKED
API: TapAndPay.TAP_AND_PAY_API is not available on this device. Connection failed with: ConnectionResult{statusCode=API_UNAVAILABLE, resolution=null, message=null}
```
This could be for the following reasons:
- During testing, you did not add your device to the Google allowlist.
- In production, your cardholder does not have Google Wallet installed. Prompt them to install the app via the Play Store.
**iOS**
### Apple Pay debugging
If tokenization flows fail outside the SDK once they reach Apple Pay, you can debug the Apple Pay app directly for better visibility of the failure reason.
Install custom logging profiles on the physical testing device, and then trigger `sysdiagnose`.
You can retrieve the logs from the iOS device filesystem.
You receive a generic Apple error, for example:
```
Response: [...]
{
[...]
}
{
statusCode = 500;
statusMessage = "Broker Service Response exception”;
}
```
> **Note**
>
> If you get this error when using a TestFlight distributed build, the SEID or Adam ID may not be correctly added to the Apple device allowlist.
> **Information**
>
> For full guidance, see Apple Developer – [Profiles and Logs](https://developer.apple.com/bug-reporting/profiles-and-logs/?name=Apple%20Pay).
### Device environment unsafe
You may receive an _ERROR_DEVICE_ENVIRONMENT_UNSAFE_ message for the following reasons:
- You've attached a debugger to the release version of the binary application.
- You're attempting push-provisioning flows on a device the SDK considers unsafe. For example, a rooted device.
> **Information**
>
> For information on countermeasures that can trigger this error, see Thales D1 Developer Portal – [Security Countermeasures](https://docs.payments.thalescloud.io/nfc-wallet-sdk-ios/security-and-privacy/security-countermeasures).
### Apple Wallet app
If the Apple Wallet App does not display your app logo in the list of issuer apps, this may be because:
- Your app was not installed and opened, or the cardholder did not go through the push-provisioning configuration.
- You provided an incorrect app group ID in the `cardManager.configurePushProvisioning()` method.
- You did not add your app group ID to the Intents Extension and/or Intents UI Extension.
- You did not add the bundle IDs for the Intents Extension and/or Intents UI Extension to the PNO's `associatedApplicationIdentifier`.
- The cardholder has already added this card to their wallet.
- The device user disabled the authentication method used by the extension.