> For the complete documentation index, see [llms.txt](https://docs.payments.thalescloud.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.payments.thalescloud.io/classic-push-provisioning/use-cases/push-provisioning-to-xpay-wallets/ios/push-provisioning-extensions.md).

# Push Provisioning Extensions

With application extensions, you can expose custom functionality and content beyond the issuer application. This lets the end user access it while interacting with other apps or iOS. For more information, see the [Apple extension documentation](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionOverview.html).

With an Apple Wallet extension, tokens can be provisioned directly from the Apple Wallet app on supported iPhone and Apple Watch devices.

Provisioning cards in the Apple Wallet app improves the in-app experience. It prevents the end user from manually entering payment card details.

> <i class="fa-info-circle">:info-circle:</i>
>
> #### Note <a href="#note" id="note"></a>
>
> The Apple Wallet extension installs when the end user installs the issuer application. For Apple Wallet to detect available `passes` and show the extension, the end user must launch the issuer application at least once.

## Overview <a href="#overview" id="overview"></a>

<figure><img src="/files/gwT6SEOTp8PQIbrRp33G" alt=""><figcaption></figcaption></figure>

When the end user launches the Apple Wallet app:

1. Apple checks with the issuer application for the availability of the cards by calling the [status](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3571367-status) API.
2. When the end user selects the issuer application, the issuer requests user authentication using the [UI extension](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionauthorizationproviding).
3. Apple checks with the issuer application for the cards available and displays the cards to the end user. Apple calls the [passEntries](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3571366-passentries) API for iPhone or [remotePassEntries](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3606586-remotepassentries) for Watch.
4. The end user selects one or more cards. For each selected card, Apple requests the provisioning payload from the issuer application. Apple then proceeds with the provisioning flow by calling the [generateAddPaymentPassRequest](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3571365-generateaddpaymentpassrequestfor) API.

> <i class="fa-info-circle">:info-circle:</i>
>
> #### Note <a href="#note-1" id="note-1"></a>
>
> * The Push Provisioning SDK uses the `generateAddPaymentPassRequestForInput` API to get the provisioning payload for step 4.
> * If the authentication data from step 2 is used to obtain card details in steps 3 and 4, set the expiration time to a sufficient value. For example, 5 minutes.

The Apple Wallet extension requires two extensions in the issuer application:

* A non-UI extension to report extension status and card discoverability in the issuer application. This covers steps 1, 3, and 4. Implement it by extending the [PKIssuerProvisioningExtensionHandler](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler) superclass.
* A UI extension to authenticate the end user in step 2. This is a separate screen that uses the same issuer application login credentials. Implement it by conforming to the [PKIssuerProvisioningExtensionAuthorizationProviding](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionauthorizationproviding) protocol.

## Integrating Apple Wallet via Push Provisioning SDK <a href="#integrating-apple-wallet-via-push-provisioning-sdk" id="integrating-apple-wallet-via-push-provisioning-sdk"></a>

### App Configuration <a href="#app-configuration" id="app-configuration"></a>

No additional configuration is required for the Push Provisioning SDK. However, you must set up an App Group ID in the issuer application. Use it to configure the shared container between the issuer application and its extensions.

Then the issuer application can use this container to store card details (such as the last 4 digits of the PAN and card art) and authentication data. The extension can retrieve the same data.

By default, the issuer application does not share storage with its extensions. Both the issuer application and its extensions must use the App Group to share storage. For more information, see the [Apple extension documentation](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionOverview.html).

In Xcode, add the App Group ID in the Capabilities tab, as shown below. For more information, see the [Apple App Groups documentation](https://developer.apple.com/documentation/xcode/configuring-app-groups).

<figure><img src="/files/osvG1qdI44PpNscUn47E" alt=""><figcaption></figcaption></figure>

After adding the App Group ID in Xcode, the issuer application can store data in the keychain access group.

```swift
// save user authentication data and card last4 (and other static
// info, e.g. card holder name, etc..) to keychain
// so that it could be accessed in Wallet extensions
// e.g. save card last4
saveToKeychain(key: "cardID1_Last4", value: "1234")
saveToKeychain(key: "cardID2_Last4", value: "5678")

func saveToKeychain(key: String, value: String) {
    guard let valueData = value.data(using: .utf8) else {
        return
    }
    let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: "BankServiceKechain",
        kSecAttrAccessGroup as String: "group.com.thalesgroup.gemalto.tpcsdk.app",
        kSecAttrAccount as String: key]
    var addQuery = query
    addQuery[kSecValueData as String] = valueData
    let status = SecItemAdd(addQuery as CFDictionary, nil)

    if status == errSecDuplicateItem  {
        let updatedData: [String: Any] = [kSecValueData as String: valueData]
        let status = SecItemUpdate(query as CFDictionary, updatedData as CFDictionary)
    }
}
```

### Apple Pay Entitlement <a href="#apple-pay-entitlement" id="apple-pay-entitlement"></a>

Similar to the Apple Pay provisioning flow, the application developer must add the `com.apple.developer.payment-pass-provisioning` entitlement to both the UI and non-UI extension targets. For more information, see [SDK configuration](/classic-push-provisioning/developer-guide/sdk-configuration/ios.md).

### UI Extension <a href="#ui-extension" id="ui-extension"></a>

The UI extension is used to perform user authentication. As the SDK does not offer such functionality, the issuer application must implement its own authentication UI in this extension.

#### **Create a UI extension**

1. In Xcode, add a new target with type **Intents UI Extension**.
2. In the newly created target, add the entitlement and, optionally, the App Group ID. These values are the same as those defined in the issuer application.

<figure><img src="/files/muSXjIFF85gosIaLjntU" alt=""><figcaption></figcaption></figure>

Example of WalletExtensionsUI.entitlements:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.developer.payment-pass-provisioning</key>
	<true/>
	<key>com.apple.security.application-groups</key>
	<array>
		<string>group.com.example.IssuerApp</string>
	</array>
</dict>
</plist>
```

3. Replace the values in `Info.plist` with the following values:

| KEY                        | VALUE                                                                         |
| -------------------------- | ----------------------------------------------------------------------------- |
| NSExtensionPointIdentifier | com.apple.PassKit.issuer-provisioning.authorization                           |
| NSExtensionPrincipalClass  | This class conforms to `PKIssuerProvisioningExtensionAuthorizationProviding`. |

Example of Wallet UI Extension Info.plist:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>NSExtension</key>
	<dict>
		<key>NSExtensionPointIdentifier</key>
		<string>com.apple.PassKit.issuer-provisioning.authorization</string>
		<key>NSExtensionPrincipalClass</key>
		<string>$(PRODUCT_MODULE_NAME).IntentViewController</string>
	</dict>
</dict>
</plist>
```

**Implement the UI extension `PKIssuerProvisioningExtensionAuthorizationProviding` protocol**

The end user authentication in Apple Wallet extension is implemented by conforming to the protocol [PKIssuerProvisioningExtensionAuthorizationProviding](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionauthorizationproviding).

Although Apple marks this as optional, we recommend authenticating the end user before provisioning.

Example of Implementing Wallet UI Extension:

```swift
import UIKit
import PassKit

// reference: https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionauthorizationproviding
class IntentViewController: UIViewController, PKIssuerProvisioningExtensionAuthorizationProviding {
    var completionHandler: ((PKIssuerProvisioningExtensionAuthorizationResult) -> Void)?
    private let authButton = UIButton(type: .system)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        /* configure UI layout */
        authButton.addTarget(self, action: #selector(authButtonTouched), for: .touchUpInside)
    }

    @objc func authButtonTouched() {
        // Provide both manual and biometric login methods based on Apple functional requirements
        // ...

        // based on authentication
        // if success, send .authorized result
        self.completionHandler?(.authorized)
        // otherwise, send .canceled result
        //self.completionHandler?(.canceled)
    }
}
```

### Non-UI Extension <a href="#non-ui-extension" id="non-ui-extension"></a>

The non-UI extension reports card availability and card details. It also obtains the provisioning payload by calling `generateAddPaymentPassRequestForInput`.

**Create a non-UI extension**

1. In Xcode, add a new target with type **Intents Extension**. Clear the **Include UI Extension** check box as the UI extension has been created in the previous section.
2. In the newly created target, add the entitlement and, optionally, the App Group ID. The values are the same as those defined in the issuer application.

<figure><img src="/files/Fhl3TgldqtPA6CcV6aNC" alt=""><figcaption></figcaption></figure>

Example of WalletExtensions.entitlements:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.developer.payment-pass-provisioning</key>
	<true/>
	<key>com.apple.security.application-groups</key>
	<array>
		<string>group.com.example.IssuerApp</string>
	</array>
</dict>
</plist>
```

3. Replace the values in `Info.plist` with the following values:

| KEY                        | VALUE                                                               |
| -------------------------- | ------------------------------------------------------------------- |
| NSExtensionPointIdentifier | com.apple.PassKit.issuer-provisioning                               |
| NSExtensionPrincipalClass  | This class that conforms to `PKIssuerProvisioningExtensionHandler`. |

Example of Wallet Non-UI Extension Info.plist:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>NSExtension</key>
	<dict>
		<key>NSExtensionPointIdentifier</key>
		<string>com.apple.PassKit.issuer-provisioning</string>
		<key>NSExtensionPrincipalClass</key>
		<string>$(PRODUCT_MODULE_NAME).IntentHandler</string>
	</dict>
</dict>
</plist>
```

**Implement a non-UI extension `PKIssuerProvisioningExtensionHandler` superclass**

Apple requires the issuer application to extend the [PKIssuerProvisioningExtensionHandler](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler) superclass to implement Apple Wallet extension. The following sections describe each of the APIs used:

* [status](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3571367-status)
* [passEntries](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3571366-passentries)
* [remotePassEntries](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3606586-remotepassentries)
* [generateAddPaymentPassRequest](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3571365-generateaddpaymentpassrequestfor)

Example of Implementing Wallet Non-UI Extension:

```swift
// reference: https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler
class IntentHandler: PKIssuerProvisioningExtensionHandler {

    override func status(completion: @escaping (PKIssuerProvisioningExtensionStatus) -> Void) {
        // Refer to section below: 3. Implement Non-UI Extension API `status`.
    }

    override func passEntries(completion: @escaping ([PKIssuerProvisioningExtensionPassEntry]) -> Void) {
        // Refer to section below: 4. Implement Non-UI Extension API `passEntries`.
    }

    // reference:
    // https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3606586-remotepassentries
    // Time constraint: 20s
    override func remotePassEntries(completion: @escaping ([PKIssuerProvisioningExtensionPassEntry]) -> Void) {
        // Refer to section below: 5. Implement Non-UI Extension API `remotePassEntries`.
    }

    // reference:
    // https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3571365-generateaddpaymentpassrequestfor
    // Time constraint: 20s
    override open func generateAddPaymentPassRequestForPassEntryWithIdentifier(
            _ identifier: String,
            configuration: PKAddPaymentPassRequestConfiguration,
            certificateChain certificates: [Data],
            nonce: Data,
            nonceSignature: Data,
            completionHandler completion: @escaping (PKAddPaymentPassRequest?) -> Void
    ) {
        // Refer to section below: 6. Implement Non-UI Extension API `generateAddPaymentPassRequest`.
    }

    func loadFromKeychain(key: String) -> String? {
        let getQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: "BankServiceKechain",
            kSecAttrAccessGroup as String: "group.com.thalesgroup.gemalto.tpcsdk.app",
            kSecAttrAccount as String: key,
            kSecReturnData as String: true]
        var item: AnyObject?
        let status = SecItemCopyMatching(getQuery as CFDictionary, &item)
        if status == errSecSuccess,
            let data = item as? Data {
            return String(data: data, encoding: .utf8)
        }
        return nil
    }
}
```

**Implement a non-UI extension `status` API**

Apple calls the [status](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3571367-status) API to check with the issuer application if there are any available cards.

Apple enforces a 100 ms time limit to return from the handler. If you exceed it, the Apple Wallet app might not display the issuer application. We recommend retrieving card information from the local keychain group. Then use the Push Provisioning SDK `getToken` API to check whether the card is digitized.

Before calling `getToken`, configure the Push Provisioning SDK by calling `configure`.

Example of Implementing Wallet Non-UI Extension API `status:`

```swift
class IntentHandler: PKIssuerProvisioningExtensionHandler {
    // reference:
    // https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3571367-status
    // Time constraint: 100ms
    override func status(completion: @escaping (PKIssuerProvisioningExtensionStatus) -> Void) {
        let status = PKIssuerProvisioningExtensionStatus()
        status.requiresAuthentication = true
        status.passEntriesAvailable = false
        status.remotePassEntriesAvailable = false

        // configure TPCSDK, e.g. for PPROD environment
        do {
            try TPCSDK.configure(url: "https://hapi.dbp-stg.thalescloud.io/mg/tpc", issuerId: "<issuerId>")
        } catch {
            // handle error
            completion(status)
        }

        // load the card info from shared keychain
        // Refer to previous step, for sample code of `loadFromKeychain`
        let last4Array = [
            loadFromKeychain(key: "cardID1_Last4"),
            loadFromKeychain(key: "cardID2_Last4")
        ].compactMap { $0 }

        let semaphore = DispatchSemaphore(value: 0)
        // for each card, check whether it is available (not digitized)
        for last4 in last4Array {
            // call TPC SDK getToken to check the digitization state
            TPCSDK.getToken(input: GetTokenInput(last4: last4)) { local, remote, error in
                if let error = error {
                    // handle error
                } else {
                    if local == nil {
                        // if at least one is not digitized in iPhone,
                        // there is card available for digitization
                        status.passEntriesAvailable = true
                    }

                    if remote == nil {
                        // if at least one is not digitized in Watch,
                        // there is card available for digitization
                        status.remotePassEntriesAvailable = true
                    }
                }

                semaphore.signal()
            }
            semaphore.wait()

            // if both pass and remotePass is available, no need to iterate further.
            if status.passEntriesAvailable && status.remotePassEntriesAvailable {
                break
            }
        }

        completion(status)
    }
}
```

**Implement a non-UI extension `passEntries` API**

Apple calls the [passEntries](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3571366-passentries) API to request the card details available to add to an iPhone and display it to the end user.

You must provide the following information so the card displays correctly:

* card art
* cardholder name
* last 4 digits of the PAN
* card scheme
* card product title
* card identifier, used later by the `generateAddPaymentPassRequest` API

This API has a 20 s time limit to retrieve card details. If you exceed it, the Apple Wallet app treats it as no cards available.

Example of Implementing Wallet Non-UI Extension API `passEntries:`

```swift
class IntentHandler: PKIssuerProvisioningExtensionHandler {
    var urlSession: URLSession?
    // reference:
    // https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3571366-passentries
    // Time constraint: 20s
    override func passEntries(completion: @escaping ([PKIssuerProvisioningExtensionPassEntry]) -> Void) {
        var entries: [PKIssuerProvisioningExtensionPassEntry] = []
        let semaphore = DispatchSemaphore(value: 0)

        // e.g. load the cardID from keychain
        let cards = [
            "cardID1",
            "cardID2",
        ]

        // If you perform network calls, you MUST re-initialize urlSession
        // urlSession = ...

        // Each card need to have the following info, otherwise it might not be displayed:
        // - card art
        // - card holder name
        // - card last4
        // - card scheme
        // - card product title
        // - card identifier, identifier for the card, to identify the card on the callback generateAddPaymentPassRequestForPassEntryWithIdentifier(...)
        for cardID in cards {
            guard let last4 = loadFromKeychain(key: "\(cardID)_Last4") else {
                // if keychain failure, continue to next ones
                continue
            }
            // check whether the card is available (not digitized).
            var isAvailable = false
            TPCSDK.getToken(input: GetTokenInput(last4: last4)) { local, _, error in
                if let error = error {
                    // handle error
                } else {
                    if local == nil {
                        // not digitized yet so available
                        isAvailable = true
                    }
                }
                semaphore.signal()
            }
            semaphore.wait()
            guard isAvailable else {
                continue
            }

            // obtain card holder name
            let cardHolderName = "\(cardID)_cardHolderName"
            // obtain card scheme
            let scheme = PKPaymentNetwork.masterCard
            // obtain card product title
            let productTitle = "\(cardID)_productTitle"
            // obtain card art
            let art = UIImage(systemName: "creditcard")!.cgImage!

            // create the config
            guard let config = PKAddPaymentPassRequestConfiguration(encryptionScheme: .ECC_V2) else {
                // if it fails, continue to next ones
                continue
            }

            config.cardholderName = cardHolderName
            config.primaryAccountSuffix = last4
            config.paymentNetwork = scheme

            // create the actual entry
            guard let entry = PKIssuerProvisioningExtensionPaymentPassEntry(identifier: cardID,
                    title: productTitle,
                    art: art,
                    addRequestConfiguration: config) else {
                // if it fails, continue to next ones
                continue
            }

            entries.append(entry)
        }

        completion(entries)
    }
}
```

**Implement the non-UI extension `remotePassEntries` API**

Similar to the `passEntries` API, the [remotePassEntries](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3606586-remotepassentries) API is used to get the card details. The difference is that this API checks for cards available to be provisioned on an Apple Watch. Normally, the card is provisioned to the Apple Watch through the Apple Watch app (bridge app).

This API also has a 20 s time limit.

Example of Implementing Wallet Non-UI Extension API `remotePassEntries:`

```swift
class IntentHandler: PKIssuerProvisioningExtensionHandler {
    var urlSession: URLSession?
    // reference:
    // https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3606586-remotepassentries
    // Time constraint: 20s
    override func remotePassEntries(completion: @escaping ([PKIssuerProvisioningExtensionPassEntry]) -> Void) {
        var remoteEntries: [PKIssuerProvisioningExtensionPassEntry] = []
        let semaphore = DispatchSemaphore(value: 0)

        // e.g. load the cardID from keychain
        let cards = [
            "cardID1",
            "cardID2",
        ]

        // If you perform network calls, you MUST re-initialize urlSession
        // urlSession = ...

        // Each card need to have the following info, otherwise it might not be displayed:
        // - card art
        // - card holder name
        // - card last4
        // - card scheme
        // - card product title
        // - card identifier, identifier for the card, to identify the card on the callback generateAddPaymentPassRequestForPassEntryWithIdentifier(...)
        for cardID in cards {
            guard let last4 = loadFromKeychain(key: "\(cardID)_Last4") else {
                // if keychain failure, continue to next ones
                continue
            }
            // check whether the card is available (not digitized).
            var isAvailable = false
            TPCSDK.getToken(input: GetTokenInput(last4: last4)) { _, remote, error in
                if let error = error {
                    // handle error
                } else {
                    if remote == nil {
                        // not digitized yet so available
                        isAvailable = true
                    }
                }
                semaphore.signal()
            }
            semaphore.wait()
            guard isAvailable else {
                continue
            }

            // obtain card holder name
            let cardHolderName = "\(cardID)_cardHolderName"
            // obtain card scheme
            let scheme = PKPaymentNetwork.masterCard
            // obtain card product title
            let productTitle = "\(cardID)_productTitle"
            // obtain card art
            let art = UIImage(systemName: "creditcard")!.cgImage!

            // create the config
            guard let config = PKAddPaymentPassRequestConfiguration(encryptionScheme: .ECC_V2) else {
                // if it fails, continue to next ones
                continue
            }

            config.cardholderName = cardHolderName
            config.primaryAccountSuffix = last4
            config.paymentNetwork = scheme

            // create the actual entry
            guard let entry = PKIssuerProvisioningExtensionPaymentPassEntry(identifier: cardID,
                    title: productTitle,
                    art: art,
                    addRequestConfiguration: config) else {
                // if it fails, continue to next ones
                continue
            }

            remoteEntries.append(entry)
        }

        completion(remoteEntries)
    }
}
```

**Implement the non-UI extension `generateAddPaymentPassRequest` API**

After the end user has selected the card to be provisioned, Apple calls the [generateAddPaymentPassRequest](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3571365-generateaddpaymentpassrequestfor) API to obtain the provisioning payload.

TPC SDK provides the `generateAddPaymentPassRequestForInput` API to get the provisioning payload.

This API also has a 20 s time limit.

Example of Implementing Wallet Non-UI Extension API `generateAddPaymentPassRequest:`

```swift
class IntentHandler: PKIssuerProvisioningExtensionHandler {
    var urlSession: URLSession?
    // reference:
    // https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler/3571365-generateaddpaymentpassrequestfor
    // Time constraint: 20s
    override open func generateAddPaymentPassRequestForPassEntryWithIdentifier(
            _ identifier: String,
            configuration: PKAddPaymentPassRequestConfiguration,
            certificateChain certificates: [Data],
            nonce: Data,
            nonceSignature: Data,
            completionHandler completion: @escaping (PKAddPaymentPassRequest?) -> Void
    ) {
        let cardID = identifier
        // based on cardID, get encryptedPayload and authorizationCode, similar in TPCSDK.provision(...)
        // - encryptedPayload, card information encrypted in PKCS7.
        // - authorizationCode, authorization code provided by issuer.
        let encryptedPayload = "<PKCS7 encrypted>"
        let authorizationCode = "<authorization code>"
        // scheme for the card
        let scheme = CardScheme.Mastercard

        // If you perform network calls, you MUST re-initialize urlSession
        // urlSession = ...

        // call TPCSDK to generate the request
        let sdkInput = GenerateAddPaymentPassRequestInput(scheme: scheme,
                encryptedPayload: encryptedPayload,
                authorizationCode: authorizationCode,
                certificateChain: certificates,
                nonce: nonce,
                nonceSignature: nonceSignature)
        // Only applicable for domestic scheme. This can be retrieved from Bank's Card Management System.
        // otherwise, it is nil
        sdkInput.productId = "<productId>"
        TPCSDK.generateAddPaymentPassRequestForInput(sdkInput) { request, error in
            if let error = error {
                // handle error
                completion(nil)
            } else {
                completion(request)
            }
        }
    }
}
```

### Apple Functional Requirements <a href="#apple-functional-requirements" id="apple-functional-requirements"></a>

To align with Apple functional requirements, the issuer application must adopt the following practices:

* Display card art and cardholder name.
* Use square edges for card art and the app icon.
* Support both manual and biometric login methods for authentication in the UI extension.
* Check card eligibility within 100 ms after Apple invokes the API.

## FAQ <a href="#faq" id="faq"></a>

**The issuer application needs to provide information to Apple Wallet if the cards are available for provisioning before the end user logs in. How do the issuers do this?**

The end user must log in to the issuer application at least once. This updates the extension with card status before the end user uses the Apple Wallet extension.

**If a login fails, where does the app display the error?**

It is recommended to show the error on the login screen. The UI extension follows the same policy as a typical app login.

**What are the size and resolution requirements for the card images?**

The digital card image should follow Apple functional requirements:

* Use a vector PDF (recommended) or a raster PNG.
* Use 1536 x 969 resolution.
* Keep the image size under 4 MB.
* Use square (not rounded) corners.
* Exclude physical-only elements, such as the card number, embossed characters, hologram, and chip contacts.
* Use landscape orientation. If the physical card uses portrait orientation, reorient it to landscape.
* Contactless indicator may be added where NFC payments are accepted.

**Do the Apple Wallet extensions require a new bundle identifier?**

Yes. Add the new bundle IDs to `associatedApplicationIdentifier` in the PNO. iOS supports wildcard bundle identifiers. Issuers must also update existing `passes` in Apple Wallet by using the PNO APIs.

If the bundle ID is not included in `associatedApplicationIdentifier`, the issuer application might not be able to access the card that was added through the Apple Wallet extension (for example, via `getToken`).

**What could be wrong if the issuer application icon doesn’t show?**

Ensure the new issuer application is installed and opened at least once to update the Apple Wallet extensions.

**Do issuers need to implement In-App Provisioning for Apple Wallet extensions to work?**

Yes. The issuer application must implement [Push Provisioning](/classic-push-provisioning/use-cases/push-provisioning-to-xpay-wallets/ios/push-provisioning.md) before implementing the Apple Wallet extension.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.payments.thalescloud.io/classic-push-provisioning/use-cases/push-provisioning-to-xpay-wallets/ios/push-provisioning-extensions.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
