> 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/push-provisioning/es/implement-push-provisioning/implement-push-to-digital-wallets/apple-wallet-extension.md).

# Extensión de Apple Wallet

## Extensión de Apple Wallet

Use las extensiones de Apple Wallet para permitir que los usuarios finales descubran y aprovisionen tarjetas directamente desde la aplicación Apple Wallet, además de la aplicación del emisor.

Mediante las extensiones de la aplicación, puede exponer funcionalidades y contenido personalizados fuera de la aplicación del emisor y ponerlos a disposición mientras el usuario final interactúa con Apple Wallet o con el sistema.

El aprovisionamiento de tarjetas dentro de la aplicación Apple Wallet mejora la experiencia en la app al evitar la entrada manual de los datos de la tarjeta de pago.

Consulte la [descripción general de la extensión de Apple](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionOverview.html) para obtener información general sobre las extensiones de iOS.

### Experiencia de usuario

El flujo integral de la extensión de Apple Wallet tiene dos fases principales:

1. Apple Wallet detecta que la aplicación del emisor tiene tarjetas disponibles para el aprovisionamiento.
2. El usuario final se autentica y selecciona qué tarjetas añadir; luego Apple Wallet solicita la carga útil de aprovisionamiento a la aplicación del emisor a través del SDK D1.

#### Descubrimiento y autenticación

<figure><img src="https://4070583847-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWDlYTPaq4dNHuiWtf8ux%2Fuploads%2Fk408nyZlESdWTOrsYplH%2FUX%20-%20Discover%20and%20authentication.svg?alt=media&amp;token=e3edd3ab-b5c5-44ab-9e26-f7a1cc492c42" alt="Apple Wallet showing issuer application option for adding a card"><figcaption><p>Apple Wallet muestra la aplicación del emisor como opción cuando hay tarjetas disponibles para el aprovisionamiento.</p></figcaption></figure>

{% stepper %}
{% step %}
**Descubrir la aplicación del emisor en Apple Wallet**

Apple Wallet comprueba con la aplicación del emisor si hay tarjetas disponibles. Si las hay, la aplicación del emisor aparece en la lista de opciones.
{% endstep %}

{% step %}
**El usuario final selecciona la aplicación del emisor**

Cuando el usuario final selecciona la aplicación del emisor, Apple Wallet activa la extensión de IU de la aplicación del emisor para autenticar al usuario final.
{% endstep %}

{% step %}
**Autenticar al usuario final**

La extensión de IU de la aplicación del emisor autentica al usuario final con las mismas credenciales y métodos (por ejemplo, inicio de sesión manual y biometría) que la aplicación principal del emisor.
{% endstep %}
{% endstepper %}

#### Selección y aprovisionamiento de tarjetas

<figure><img src="https://4070583847-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWDlYTPaq4dNHuiWtf8ux%2Fuploads%2F31NDGz2qKRMoHFwIMZF4%2FUX%20-%20Selecting%20and%20provisioning%20cards.svg?alt=media&amp;token=d122f374-83a4-4fce-9f36-101b75ebb228" alt="Apple Wallet showing available cards from the issuer application"><figcaption><p>Apple Wallet obtiene y muestra las tarjetas disponibles de la aplicación del emisor.</p></figcaption></figure>

{% stepper %}
{% step %}
**Apple Wallet obtiene las tarjetas disponibles**

Apple Wallet solicita la lista de tarjetas disponibles a la aplicación del emisor a través de la extensión sin interfaz de usuario. El usuario final ve una lista de tarjetas para añadir.
{% endstep %}

{% step %}
**Ver detalles de la tarjeta**

El usuario final puede revisar los detalles de la tarjeta (por ejemplo, la imagen de la tarjeta, el título del producto y los últimos cuatro dígitos) antes de añadirla.
{% endstep %}

{% step %}
**Aceptar términos y condiciones**

El usuario final revisa y acepta los términos y condiciones del emisor y de la red de pagos.
{% endstep %}

{% step %}
**Carga útil de aprovisionamiento y tokenización**

Para cada tarjeta seleccionada, Apple Wallet solicita una carga útil de aprovisionamiento a la aplicación del emisor y luego inicia el flujo de tokenización con el TSP de la red de pago.
{% endstep %}
{% endstepper %}

### Extensiones

La integración de Apple Wallet requiere dos tipos de extensiones de la aplicación del emisor:

* **Extensión sin IU**\
  Informa del estado del flujo de la extensión y de la disponibilidad de tarjetas. Esto cubre los pasos de descubrimiento de tarjetas y de carga útil de aprovisionamiento. Se implementa extendiendo la clase base del SDK D1 `D1IssuerProvisioningExtensionHandler`.
* **Extensión de IU**\
  Autentica al usuario final. Se trata de una pantalla independiente que usa las mismas credenciales de inicio de sesión que la aplicación del emisor. Implementa el `D1IssuerProvisioningExtensionAuthorizationProviding` protocolo.

{% hint style="info" %}
Las extensiones de Apple Wallet se instalan al mismo tiempo que la aplicación del emisor. Para que Apple Wallet detecte que hay pases para añadir y muestre la extensión, el usuario final debe abrir la aplicación del emisor al menos una vez.
{% endhint %}

El SDK D1 proporciona una implementación base para la extensión sin IU, de modo que no tenga que implementar toda la lógica usted mismo. La aplicación del emisor llama a `D1Task.configure()` con `WalletExtensionConfig` durante el inicio de la aplicación para compartir los detalles de las tarjetas con la extensión. Aunque esta configuración solo es necesaria una vez después de la instalación, recomendamos llamarla en cada inicio de la aplicación para mantener actualizada la información de las tarjetas.

<figure><img src="https://4070583847-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWDlYTPaq4dNHuiWtf8ux%2Fuploads%2FHtLpRmjTRrDY1JhzJxjN%2FSequence%20Diagram%20-%20%20Apple%20wallet%20extension%20-%20CONFIGURATION.svg?alt=media&amp;token=1f8f4c4d-efbc-48b6-aa19-a391543c584f" alt="High-level flow for configuring the Apple Wallet extension via D1 SDK"><figcaption><p>Flujo de alto nivel: la aplicación del emisor configura el SDK D1 para que Apple Wallet pueda descubrir tarjetas.</p></figcaption></figure>

Después de una autenticación correcta en la extensión de IU, la aplicación del emisor debe llamar a `D1IssuerProvisioningExtensionAuthorizationProviding.bindingHash()` para recuperar la carga útil codificada del hash de vinculación del cliente (`cbp`). La aplicación del emisor debe pasar este `cbp` valor al backend del emisor al solicitar el token del emisor. Luego debe llamar a `D1IssuerProvisioningExtensionAuthorizationProviding.login()` para que el SDK pueda continuar el flujo de la extensión sin IU.

{% hint style="warning" %}
El `bindingHash()` tiene un valor distinto en la aplicación del emisor y en la extensión. Genere un token de acceso del emisor independiente para la extensión.
{% endhint %}

### Diagramas de secuencia

#### 1. Apple Wallet muestra el botón de la aplicación del emisor

<figure><img src="https://4070583847-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWDlYTPaq4dNHuiWtf8ux%2Fuploads%2Fo5Mj5yYzUGnlp201RcNE%2FSequence%20Diagram%20-%20%20Apple%20Wallet%20shows%20the%20issuer%20application%20button.svg?alt=media&amp;token=4d823af2-5f1c-4c61-b870-d086af07bb93" alt="Sequence diagram showing how Apple Wallet discovers the issuer application"><figcaption><p>Apple Wallet detecta que la aplicación del emisor tiene tarjetas disponibles para el aprovisionamiento.</p></figcaption></figure>

#### 2. Autenticación del usuario final

<figure><img src="https://4070583847-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWDlYTPaq4dNHuiWtf8ux%2Fuploads%2F61Yv0rUgpTdf6kzAkK7H%2FSequence%20Diagram%20-%20End%20user%20authentication.svg?alt=media&amp;token=b3a0b382-124f-4ee3-acd7-4623e7443ebc" alt="Sequence diagram showing Apple Wallet triggering issuer UI extension for authentication"><figcaption><p>Apple Wallet inicia la extensión de IU de la aplicación del emisor para la autenticación.</p></figcaption></figure>

#### 3. Se muestran las tarjetas disponibles

<figure><img src="https://4070583847-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWDlYTPaq4dNHuiWtf8ux%2Fuploads%2FMLtj5pDy0f2Rt4fjzRNd%2FSequence%20Diagram%20-%20%20Available%20cards%20are%20displayed.svg?alt=media&amp;token=7bea5b89-19f2-442b-a4f6-82a268a35f73" alt="Sequence diagram showing Apple Wallet requesting card list from issuer"><figcaption><p>Apple Wallet solicita la lista de tarjetas disponibles y las muestra al usuario final.</p></figcaption></figure>

#### 4. Tokenización

<figure><img src="https://4070583847-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWDlYTPaq4dNHuiWtf8ux%2Fuploads%2Fh3Ti9cGW6ZDc3oaMMSQy%2FSequence%20Diagram%20-%20%20Tokenization.svg?alt=media&amp;token=134a7852-acdb-43be-9027-5aff4f25a722" alt="Sequence diagram showing Apple Wallet requesting provisioning payload and tokenizing"><figcaption><p>Apple Wallet solicita las cargas útiles de aprovisionamiento y tokeniza las tarjetas seleccionadas.</p></figcaption></figure>

### Integre Apple Wallet mediante el SDK D1

#### Configuración del SDK

Cuando realiza [la inicialización del SDK de iOS](/push-provisioning/es/integrate-the-d1-sdk/getting-started/configuration/3.-initialization/ios-initialization.md), la aplicación del emisor también debe llamar a `configure(ConfigParams.walletExtensionConfig(...))` y proporcionar:

**`cardParamsList`**

Use esta variante cuando tenga un único `consumerId` y un único `issuerId`:

* `cardParamsList` – Lista de identificadores de tarjeta, imagen de la tarjeta, títulos del producto y últimos cuatro dígitos. La imagen de la tarjeta y el título del producto se muestran en Apple Wallet.
* `appGroupID` – El ID del contenedor compartido para que el SDK D1 pueda compartir datos entre la aplicación del emisor y sus extensiones.

```swift
let coreConfig = ConfigParams.coreConfig(consumerID: "<obtenido del servidor>")
let cardConfig = ConfigParams.cardConfig()

let card = ConfigParams.CardParams(cardID: "<obtenido del servidor>",
        cardArt: UIImage(named: "<nombre_de_imagen>")!, productTitle: "<título del producto>", last4: "<últimos4>")
let walletExtensionConfig = ConfigParams.walletExtensionConfig(cardParamsList: [card],
        appGroupID: "<ID del grupo de app definido en las capacidades de la app>")

d1Task.configure([coreConfig, cardConfig, walletExtensionConfig]) { errors in
    if let errors = errors {
        for error in errors {
            // Inspeccione los errores para identificar qué configuración falló
            // Consulte la sección de gestión de errores de integración del SDK D1
        }
    }
}

// Guarde consumerId para autenticar al mismo usuario final en el flujo de la extensión de la cartera
saveToKeychain(key: "D1ConsumerID", value: "<consumer_ID>")

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: "<servicio asociado, p. ej. IssuerAppService>",
        kSecAttrAccessGroup as String: "<ID del grupo de app definido en las capacidades de la 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]
        SecItemUpdate(query as CFDictionary, updatedData as CFDictionary)
    }
}
```

**`issuerParamsList`**

Use esta variante cuando admita múltiples combinaciones de `issuerId` y `consumerId`:

* `issuerParamsList` – Lista de `cardParamsList` con el correspondiente `issuerId` y `consumerId`.
* `appGroupID` – El ID del contenedor compartido para que el SDK D1 pueda compartir datos entre la aplicación del emisor y sus extensiones.

```swift
let coreConfig = ConfigParams.coreConfig(consumerID: "<obtenido del servidor>")
let cardConfig = ConfigParams.cardConfig()

var issuerParamsList: [ConfigParams.IssuerParams] = []
let card1 = ConfigParams.CardParams(cardID: "<obtenido del servidor>",
        cardArt: UIImage(named: "<nombre_de_imagen>")!, productTitle: "<título del producto>", last4: "<últimos4>")
let card2 = ConfigParams.CardParams(cardID: "<obtenido del servidor>",
        cardArt: UIImage(named: "<nombre_de_imagen>")!, productTitle: "<título del producto>", last4: "<últimos4>")
let card3 = ConfigParams.CardParams(cardID: "<obtenido del servidor>",
        cardArt: UIImage(named: "<nombre_de_imagen>")!, productTitle: "<título del producto>", last4: "<últimos4>")
let issuerParam1 = ConfigParams.IssuerParams(cardParams: card1, issuerID: "<issuerId 1>", consumerID: "<consumerId 1>")
let issuerParam2 = ConfigParams.IssuerParams(cardParams: card2, issuerID: "<issuerId 2>", consumerID: "<consumerId 2>")
let issuerParam3 = ConfigParams.IssuerParams(cardParams: card3, issuerID: "<issuerId 2>", consumerID: "<consumerId 3>")
issuerParamsList.append(issuerParam1)
issuerParamsList.append(issuerParam2)
issuerParamsList.append(issuerParam3)

let walletExtensionConfig = ConfigParams.walletExtensionConfig(issuerParamsList: issuerParamsList,
        appGroupID: "<ID del grupo de app definido en las capacidades de la app>")

d1Task.configure([coreConfig, cardConfig, walletExtensionConfig]) { errors in
    if let errors = errors {
        for error in errors {
            // Inspeccione los errores para identificar qué configuración falló
            // Consulte la sección de gestión de errores de integración del SDK D1
        }
    }
}

// Guarde los valores de issuerId para usarlos durante la autenticación de la extensión de la cartera
saveToKeychain(key: "issuerID1", value: "<issuerId 1>")
saveToKeychain(key: "issuerID2", value: "<issuerId 2>")

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: "<servicio asociado, p. ej. IssuerAppService>",
        kSecAttrAccessGroup as String: "<ID del grupo de app definido en las capacidades de la 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]
        SecItemUpdate(query as CFDictionary, updatedData as CFDictionary)
    }
}
```

{% hint style="info" %}
Recomendamos encarecidamente usar el `last4` parámetro.

Sin `last4`, el SDK puede interpretar mal el estado de la tokenización en escenarios con dispositivo compartido. Por ejemplo, si el Usuario A tokeniza una tarjeta y luego el Usuario B inicia sesión en la misma aplicación del emisor en el mismo dispositivo, el SDK podría asumir incorrectamente que la tokenización ya se completó para el Usuario B.
{% endhint %}

#### ID del grupo de la app

De forma predeterminada, la aplicación del emisor y sus extensiones no comparten almacenamiento. Use grupos de apps para compartir almacenamiento entre ellas. Consulte el [descripción general de la extensión de Apple](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionOverview.html) para más detalles.

`appGroupID` identifica el contenedor compartido entre la aplicación del emisor y sus extensiones. Se configura en Xcode en **Capabilities**.

<figure><img src="https://4070583847-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWDlYTPaq4dNHuiWtf8ux%2Fuploads%2Fi2LKsGLuhSD1Fk2FT0Wa%2Fapple-extension-ide-config.png?alt=media&amp;token=84c4205a-2583-4327-9043-e023489fc634" alt="Xcode configuration for app groups used by issuer application and extensions"><figcaption><p>Ejemplo de configuración de un grupo de apps en Xcode para la aplicación del emisor y sus extensiones.</p></figcaption></figure>

{% hint style="info" %}
Asegúrese de que el ID del grupo de apps se añada correctamente en Xcode antes de usarlo en `configure(ConfigParams.walletExtensionConfig(...))`. De lo contrario, el SDK D1 devuelve el `invalidAppGroupID` error.
{% endhint %}

#### Entitlement de Apple Pay

De forma similar al aprovisionamiento de Apple Pay dentro de la app, debe añadir el entitlement `com.apple.developer.payment-pass-provisioning` a los targets de extensión con y sin IU. Para obtener detalles de incorporación, consulte la documentación de aprovisionamiento de Apple Wallet o **Incorporación de aprovisionamiento push para carteras**.

#### Extensión de IU

**Crear una extensión de IU**

<figure><img src="https://4070583847-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWDlYTPaq4dNHuiWtf8ux%2Fuploads%2F4fu4rpWs39nD2IhGoeg2%2Fapple-extension-ide-config-1.png?alt=media&amp;token=47e3023b-b9ce-4026-a934-f54269346f14" alt="Xcode dialog for creating an intents UI extension"><figcaption><p>Cree un target de Intents UI Extension en Xcode para la extensión de IU de Apple Wallet.</p></figcaption></figure>

{% stepper %}
{% step %}
Añada un nuevo target en Xcode de tipo **Extensión de IU de Intents**.
{% endstep %}

{% step %}
En el nuevo target, habilite los mismos valores de ID del grupo de apps y de entitlement que en la aplicación del emisor. Si el ID del grupo de apps es diferente, el `login` API devuelve el `walletExtensionAppGroupIDNotFound` error.
{% endstep %}

{% step %}
Actualice la extensión `Info.plist`:

* Establezca `NSExtensionPointIdentifier` en `com.apple.PassKit.issuer-provisioning.authorization`.
* Establezca `NSExtensionPrincipalClass` a una clase que cumpla con `D1IssuerProvisioningExtensionAuthorizationProviding`.
  {% endstep %}
  {% endstepper %}

Ejemplo de entitlements y configuración de la extensión:

```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>
```

```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>
```

**Integre el SDK para la extensión de IU**

El SDK D1 proporciona el `D1IssuerProvisioningExtensionAuthorizationProviding` protocolo, que extiende el de Apple [`PKIssuerProvisioningExtensionAuthorizationProviding`](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionauthorizationproviding) y añade `bindingHash()` y `login()` APIs.

En la extensión de IU, implemente un controlador de vista que cumpla con `D1IssuerProvisioningExtensionAuthorizationProviding`, autentica al usuario final, llama a `bindingHash()` para recuperar el `cbp`, solicita el token del emisor al backend del emisor con este `cbp`, y luego llama a `login`. Consulte la **inicio de sesión del SDK** documentación para la generación de tokens.

**Inicio de sesión con un único issuerId**

```swift
import UIKit
import PassKit
import D1

class IntentViewController: UIViewController, D1IssuerProvisioningExtensionAuthorizationProviding {
    var completionHandler: ((PKIssuerProvisioningExtensionAuthorizationResult) -> Void)?
    private let authButton = UIButton(type: .system)

    override func viewDidLoad() {
        super.viewDidLoad()
        // Configure el diseño de la interfaz de usuario
        authButton.addTarget(self, action: #selector(authButtonTouched), for: .touchUpInside)
    }

    @objc func authButtonTouched() {
        // Cargue los datos relacionados con la autenticación desde Keychain (como mínimo consumerID)
        let consumerID = loadFromKeychain(key: "D1ConsumerID")

        // Proporcione tanto métodos de autenticación manual como biométrica según lo requiera Apple

        Task {
            do {
                let cbp = try await self.bindingHash()
                var issuerToken = getAccessToken(sub: consumerID!, cbp: cbp)
                try await self.login(&issuerToken)

                await MainActor.run {
                    self.completionHandler?(.authorized)
                }
            } catch {
                await MainActor.run {
                    self.completionHandler?(.canceled)
                }
            }
        }
    }

    func loadFromKeychain(key: String) -> String? {
        let getQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: "<servicio asociado, p. ej. IssuerAppService>",
            kSecAttrAccessGroup as String: "<ID del grupo de app definido en las capacidades de la 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
    }

    func getAccessToken(sub: String, cbp: String) -> Data? {
        // Use cbp al solicitar el token del emisor al backend del emisor
        return Data()
    }
}
```

**Inicio de sesión con varios issuerIds**

```swift
import UIKit
import PassKit
import D1

class IntentViewController: UIViewController, D1IssuerProvisioningExtensionAuthorizationProviding {
    var completionHandler: ((PKIssuerProvisioningExtensionAuthorizationResult) -> Void)?
    private let authButton = UIButton(type: .system)

    override func viewDidLoad() {
        super.viewDidLoad()
        // Configure el diseño de la interfaz de usuario
        authButton.addTarget(self, action: #selector(authMultiIssuerButtonTouched), for: .touchUpInside)
    }

    @objc func authMultiIssuerButtonTouched() {
        // Cargue los IDs de emisor desde Keychain
        let issuerId1 = loadFromKeychain(key: "issuerID1")
        let issuerId2 = loadFromKeychain(key: "issuerID2")

        // Proporcione tanto métodos de autenticación manual como biométrica según lo requiera Apple

        Task {
            do {
                let cbp = try await self.bindingHash()
                let issuerToken1 = getAccessToken(iss: issuerId1!, cbp: cbp)
                let issuerToken2 = getAccessToken(iss: issuerId2!, cbp: cbp)
                var issuerTokens = [issuerToken1, issuerToken2]

                try await self.login(&issuerTokens)

                await MainActor.run {
                    self.completionHandler?(.authorized)
                }
            } catch {
                await MainActor.run {
                    self.completionHandler?(.canceled)
                }
            }
        }
    }

    func loadFromKeychain(key: String) -> String? {
        let getQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: "<servicio asociado, p. ej. IssuerAppService>",
            kSecAttrAccessGroup as String: "<ID del grupo de app definido en las capacidades de la 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
    }

    func getAccessToken(iss: String, cbp: String) -> Data {
        // Use cbp al solicitar el token del emisor al backend del emisor
        return Data()
    }
}
```

{% hint style="info" %}
Si las tarjetas comparten el mismo `issuerId` pero tienen diferentes `consumerId` valores, genere un solo `issuerToken` para ese `issuerId`.
{% endhint %}

#### Extensión sin IU

**Crear una extensión sin IU**

<figure><img src="https://4070583847-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWDlYTPaq4dNHuiWtf8ux%2Fuploads%2FkcVbmYb3VtlvavC2FHhr%2Fapple-extension-ide-config-2.png?alt=media&amp;token=622bd0c3-ca14-484a-b91d-a6e3ac502e60" alt="Xcode dialog for creating an intents extension without UI"><figcaption><p>Cree un target de Intents Extension en Xcode para la extensión sin IU de Apple Wallet.</p></figcaption></figure>

{% stepper %}
{% step %}
Añada un nuevo target en Xcode de tipo **Extensión de Intents**. Desmarque **Incluir extensión de IU** porque la extensión de IU se creó previamente.
{% endstep %}

{% step %}
En el nuevo target, habilite los mismos valores de ID del grupo de apps y de entitlement que en la aplicación del emisor. Si el ID del grupo de apps es diferente, las operaciones del SDK D1 devuelven el `walletExtensionAppGroupIDNotFound` error.
{% endstep %}

{% step %}
Actualice la extensión `Info.plist`:

* Establezca `NSExtensionPointIdentifier` en `com.apple.PassKit.issuer-provisioning`.
* Establezca `NSExtensionPrincipalClass` a una clase que extienda `D1IssuerProvisioningExtensionHandler`.
  {% endstep %}
  {% endstepper %}

Ejemplo de entitlements y configuración de la extensión:

```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>
```

```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>
```

**Integre el SDK para la extensión sin IU**

El SDK D1 proporciona la clase base `D1IssuerProvisioningExtensionHandler`, que extiende el de Apple [`PKIssuerProvisioningExtensionHandler`](https://developer.apple.com/documentation/passkit/pkissuerprovisioningextensionhandler) e implementa las API requeridas. En la mayoría de los casos, solo necesita extender esta clase.

```swift
import D1

class IntentHandler: D1IssuerProvisioningExtensionHandler {
}
```

#### Gestión de errores

Opcionalmente, sobrescriba `errorEncountered(_:)` en la extensión sin IU para registrar o hacer seguimiento de los errores recibidos durante el flujo de la extensión.

```swift
import D1

class IntentHandler: D1IssuerProvisioningExtensionHandler {

    override func errorEncountered(_ error: D1Error) {
        print("Error = \(error.localizedDescription)")
    }
}
```

Los casos de error típicos incluyen:

* El `appGroupID` es inválido o no se encuentra.
* El backend D1 devuelve un error.

```console
# Ejemplo: error en la API de estado
Error del SDK = Error en la API de estado. Causa: [2](-25300) Error de acceso al almacenamiento. 
Error: [20009]No se encuentra appGroupID en la interfaz de usuario y la extensión sin IU de Apple Pay Wallet. 
Podría deberse a varias razones: en la aplicación principal no hay una configuración de extensión de cartera 
configuración llamando a `D1Task.configure(_:completion:)` con 
`ConfigParams.walletExtensionConfig(cardParamsList:appGroupID:)` O 
el appGroupID definido en las capacidades de la aplicación principal no está definido en las capacidades de los targets de la extensión con IU 
y sin IU.

# Ejemplo: error en la API passEntries
Error del SDK = Error en la API passEntries. Causa: [3]Se recibió un error del servidor con 
código de error HTTP 400 y mensaje 'Error del servidor'. Error: [40001]Se encontraron errores 
mientras se ejecutaban operaciones de tarjeta.

# Ejemplo: error en la API remotePassEntries
Error del SDK = Error en la API remotePassEntries. Causa: [3]Se recibió un error del servidor 
con código de error HTTP 400 y mensaje 'Error del servidor'. Error: [40001]Se encontraron errores 
mientras se ejecutaban operaciones de tarjeta.

# Ejemplo: error en la API generate
Error del SDK = Error en la API generate. Causa: [3]Se recibió un error del servidor con 
código de error HTTP 400 y mensaje '911. La operación falló .1'. Error: [40001]Se encontraron errores 
mientras se ejecutaban operaciones de tarjeta.
```

#### Requisitos funcionales de Apple

Para alinearse con los requisitos funcionales de Apple, el SDK D1 `D1IssuerProvisioningExtensionHandler` y la aplicación del emisor adoptan las siguientes prácticas:

* Muestre la imagen de la tarjeta y el nombre del titular.
* Determine si hay tarjetas disponibles (elegibilidad) en un plazo de 100 ms desde que Apple invoque la API.
* Proporcione la imagen de la tarjeta y los iconos de la app con bordes cuadrados.
* Admita tanto métodos de inicio de sesión manual como biométrico para la autenticación en la extensión de IU.

### Preguntas frecuentes

<details>

<summary><strong>¿Cómo puede la aplicación del emisor proporcionar la disponibilidad de tarjetas a Apple Wallet antes de que el usuario final inicie sesión?</strong></summary>

El usuario final debe iniciar sesión en la aplicación del emisor al menos una vez para que la extensión pueda actualizarse con el estado de la tarjeta. La aplicación del emisor llama a `configure(ConfigParams.walletExtensionConfig())` para proporcionar la lista de tarjetas al SDK D1.

</details>

<details>

<summary><strong>Si falla un inicio de sesión, ¿dónde se muestra el error?</strong></summary>

Recomendamos mostrar los errores de inicio de sesión directamente en la pantalla de inicio de sesión de la extensión de IU. La extensión de IU debe seguir las mismas directrices de UX que el inicio de sesión de la aplicación del emisor.

</details>

<details>

<summary><strong>¿Cuáles son los requisitos de tamaño y resolución para las imágenes de las tarjetas?</strong></summary>

La imagen digital de la tarjeta debe seguir los requisitos funcionales de Apple:

* Proporcione las imágenes en PNG (recomendado) o PDF vectorial.
  * Las extensiones de Wallet tienen límites de memoria más estrictos que las aplicaciones principales.
  * Los archivos PDF aumentan el uso de memoria porque el sistema operativo los convierte a PNG.
* Use una resolución de 1536 × 969.
* Asegúrese de que el tamaño de la imagen sea inferior a 4 MB.
* Use esquinas cuadradas (no redondeadas).
* Excluya los elementos exclusivos de la tarjeta física (por ejemplo, PAN, caracteres en relieve, holograma, contactos del chip).
* Use orientación horizontal. Si la tarjeta física es vertical, reoriéntela a horizontal.
* Opcionalmente, agregue el indicador sin contacto cuando se admitan pagos NFC.

</details>

<details>

<summary><strong>¿Las extensiones requieren nuevos identificadores de paquete?</strong></summary>

Sí. Los nuevos identificadores de paquete deben incluirse en el `associatedApplicationIdentifier` del operador de la red de pagos (PNO). iOS admite identificadores de paquete comodín.

Los emisores también deben actualizar los pases existentes en Apple Wallet mediante las API de PNO. Si el ID del paquete no está incluido, la aplicación emisora no puede acceder a las tarjetas añadidas a través de la extensión de Apple Wallet (por ejemplo, para comprobaciones del estado de digitalización).

</details>

<details>

<summary><strong>¿Qué podría estar mal si el icono de la aplicación emisora no aparece en Apple Wallet?</strong></summary>

Compruebe lo siguiente:

* La aplicación emisora está instalada y se ha abierto al menos una vez.
* La aplicación emisora llama a `configure(ConfigParams.walletExtensionConfig())` para actualizar la lista de tarjetas.
* La aplicación emisora y ambas extensiones usan el mismo `appGroupID` en `configure(ConfigParams.walletExtensionConfig())` y en las capacidades de Xcode.

</details>

<details>

<summary><strong>¿Se requiere aprovisionamiento desde la app para que las extensiones de Apple Wallet funcionen?</strong></summary>

Sí. La aplicación emisora debe implementar primero el aprovisionamiento push en la app para las billeteras antes de agregar extensiones de Apple Wallet.

</details>


---

# 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/push-provisioning/es/implement-push-provisioning/implement-push-to-digital-wallets/apple-wallet-extension.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.
