> 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/nfc-wallet-sdk-ios/es/implement-nfc-wallet/make-payments/implement-contactless-payment/full-implementation-example.md).

# Ejemplo de implementación completo

Use este ejemplo de código para entender cómo encajan los pasos anteriores.

Inicia el pago sin contacto desde:

* **Haga doble clic** (`NFCWindowSceneEvent.presentation`)
* **Detección de campo** (`NFCWindowSceneEvent.readerDetected`)

{% hint style="info" %}
Este ejemplo asume que ya revisó los pasos de implementación individuales.
{% endhint %}

### Código fuente completo

{% code title="SceneDelegate.swift" expandable="true" %}

```swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
    var window: UIWindow?
    var rootViewController: UIViewController?
    let session = ContactlessPaymentSession()
    
    // si se detecta doble clic o detección de campo, establecer en true
    var isDefaultPaymentApp = false
    
    // hecho para iOS 18 donde puede necesitar iniciar la emulación en detección de campo
    var isPaymentOngoing = false
    
    // para admitir iniciar pagos sin contacto mediante acción de doble clic o detección de campo cuando la aplicación no se está ejecutando:
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use este método para configurar opcionalmente y adjuntar la UIWindow `window` a la UIWindowScene `scene` proporcionada.
        // Si usa un storyboard, la propiedad `window` se inicializará y adjuntará automáticamente a la escena.
        // Este delegado no implica que la escena o la sesión de conexión sean nuevas (vea `application:configurationForConnectingSceneSession`).
        guard let windowScene = (scene as? UIWindowScene) else { return }
        
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let landingVC = storyboard.instantiateViewController(identifier: "LandingSB")
        
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = landingVC
        window.makeKeyAndVisible()
        self.window = window
        
        rootViewController = landingVC
        
        // establecer en false cada vez que entra
        isDefaultPaymentApp = false
        
        if #available(iOS 17.4, *) {
            if let nfcEvent = connectionOptions.nfcEvent {
                switch nfcEvent {
                case .presentation:
                    // establecer en true solo cuando se haya activado como aplicación de pago predeterminada
                    isDefaultPaymentApp = true
                    startPayment()
                case .readerDetected:
                    // establecer en true solo cuando se haya activado como aplicación de pago predeterminada
                    isDefaultPaymentApp = true
                    handleFieldDetect(withCurrentVC: window.rootViewController)
                @unknown default: break
                }
            }
        }
    }
    
    func sceneDidEnterBackground(_ scene: UIScene) {
        // establecer en false cada vez que sale, ya que puede configurar otra aplicación como predeterminada
        isDefaultPaymentApp = false
        
        // Llamado cuando la escena pasa del primer plano al fondo.
        // Use este método para guardar datos, liberar recursos compartidos y almacenar información de estado específica de la escena
        // para restaurar la escena a su estado actual.
        if #available(iOS 17.4, *) {
            Task {
                // la aplicación de pago predeterminada, después de que la sesión haya comenzado, aceptará APDU y completará el pago en segundo plano
                // esto es para cancelarlo cuando la aplicación pasa a segundo plano.
                // También se recomienda esta acción cuando la aplicación cambia a una pantalla diferente a la de pago, etc.
                await session.cancel()
            }
        }
    }
    

}

extension SceneDelegate: NFCWindowSceneDelegate {
    @available(iOS 17.4, *)
    // para admitir iniciar pagos sin contacto mediante acción de doble clic o detección de campo cuando la aplicación está en segundo plano o primer plano:
    func windowScene(_ windowScene: UIWindowScene, didReceiveNFCWindowSceneEvent event: NFCWindowSceneEvent) {
        switch event {
        case .presentation:
            isDefaultPaymentApp = true
            startPayment()
        case .readerDetected:
            isDefaultPaymentApp = true
            handleFieldDetect(withCurrentVC: rootViewController)
        @unknown default: break
        }
    }
}

extension SceneDelegate {
    
    // solicitar a la interfaz de usuario que indique iniciar el pago mediante doble clic
    func displayAlertWithDoubleClickStartPayment(withCurrentVC currentVC: UIViewController?) {
        let alertController = UIAlertController(title: "Por favor, haga doble clic para iniciar el pago", message: nil, preferredStyle: .alert)
        alertController.addAction(
            UIAlertAction(title: "OK", style: .default)
        )
        currentVC?.present(alertController, animated: false)
    }
    
    // devolver true si se requiere el aviso de doble clic
    @available(iOS 17.4, *)
    func handleFieldDetect(withCurrentVC currentVC: UIViewController?){
        // para indicar si es una detección de campo normal o lo consideramos readerDetect para una sesión de pago en curso
        // solo para iOS 18 +
        if #available(iOS 18, *), isPaymentOngoing {
            // es iOS 18 + donde el pago en curso ha recibido el evento readerDetect
            Task {
                try await self.session.startEmulation()
            }
        } else {
            // manejar la detección de campo normalmente en los siguientes escenarios:
            // - iOS 17.4+, independientemente de si el pago está en curso o no.
            // - iOS 18.x, pago no en curso.
            
            let biometricType = biometricType()
            // iniciar el pago directamente para huella dactilar
            if biometricType == .touchID {
                startPayment()
            } else {
                displayAlertWithDoubleClickStartPayment(withCurrentVC: currentVC)
            }
        }
    }
    
    func startPayment(withDigitalCardID digitalCardID: String? = nil) {
        Task {
            
            // inicializar el SDK
            try await TSHPay.shared.configure(withVerificationMethod: .userPresence)
            
            if #available(iOS 17.4, *) {
                
                // indicar que el pago está en curso, es requerido para detección de campo e iOS 18
                isPaymentOngoing = false
                                
                // comprobando si es la aplicación de pago predeterminada y verificar si presentmentIntent es válido
                if !isDefaultPaymentApp, !PresentmentIntentWrapper.isValid() {
                    do {
                        // No se detecta el comportamiento de aplicación de pago predeterminada, intentamos obtener presentmentIntentAssertion
                        try await PresentmentIntentWrapper.acquire()
                        print("presentmentIntent assertion acquired \(PresentmentIntentWrapper.isValid())")
                    } catch {
                        // falló... ya sea por error o por el tiempo de enfriamiento
                        // tenemos que iniciar la emulación directamente, al menos no nos redirigirán la aplicación
                    }
                }
                
                if let digitalCardID {
                    print("StartPayment:: with digitalCardID")
                    await session.startPayment(withDigitalCardID: digitalCardID)
                } else {
                    print("StartPayment:: without digitalCardID")
                    await session.startPayment()
                }
                
                
                for await event in await session.eventStream {
                    switch event {
                    case .authenticationRequired(let action):
                        print("StartPayment:: authenticationRequired")
                        Task {
                            action.proceed()
                        }
                    case .authenticationCompleted:
                        print("StartPayment:: authenticationCompleted")
                        isPaymentOngoing = true
                        // Escenario A: cambiar de tarjeta
                        // si desea iniciar la emulación en el evento .authenticationCompleted
                        // try await session.startEmulation()
                        break
                    case .posConnected:
                        print("StartPayment:: posConnected")
                        await session.setAlertMessage("POS Connected")
                        // si la aplicación no adquiere NFCPresentmentIntentAssertion, no puede llamar a startEmulation() en este evento.
                        try await session.startEmulation()
                        break
                    case .posDisconnected:
                        print("StartPayment:: posDisconnected")
                        await session.setAlertMessage("POS Disconnected")
                    case .transactionCompleted(let context):
                        print("StartPayment:: transactionCompleted")
                        await session.setAlertMessage("Transaction Completed")
                        
                        // mostrar al usuario
                        displayTransactionContext(context)
                    case .errorEncountered(let error):
                        switch (error as? ContactlessPaymentSession.Error) {
                        case .cancelled:
                            // El usuario final canceló la transacción.
                            break
                        case .maxSessionDurationReached:
                            // Mostrar un mensaje al usuario final de que la transacción ha expirado.
                            break
                        case .nfcPermissionNotAccepted:
                            // Mostrar un mensaje al usuario final de que el permiso no está permitido.
                            break
                        case .systemEligibilityFailed:
                            // Mostrar un mensaje al usuario final de que el sistema no es elegible para pagos sin contacto.
                            // p. ej., Apple ID o la ubicación del dispositivo no está en el EEE.
                            break
                        case .noPaymentKeys:
                            // Se requiere reabastecimiento. Consulte la guía de reabastecimiento.
                            break
                        case .noDefaultCard:
                            // La tarjeta predeterminada debe establecerse antes de la transacción.
                            break
                        case .keychainError:
                            // Mostrar mensaje de error al usuario final
                            break
                        case .apduFailure(let reason):
                            // Reintentar
                            break
                        case .unknown(let underlying):
                            // Mostrar mensaje de error al usuario final
                            break
                        case .transmissionError:
                            // Error encontrado en el intercambio APDU entre el dispositivo y el lector POS
                            break
                        case .sessionInvalidated:
                            // La sesión de la tarjeta ha sido invalidada por el sistema
                            break
                        case .deviceEnvironmentUnsafe(_):
                            // Error de entorno de dispositivo inseguro
                            break
                        case .setDefaultCardFailure:
                            // Error encontrado al intentar establecer la ID de Tarjeta Digital proporcionada como tarjeta predeterminada durante la llamada a la API `startPayment(withDigitalCardID:)`.
                            break
                        case .invalidDigitalCardID:
                            // La ID de Tarjeta Digital proporcionada no es válida en ``startPayment(withDigitalCardID:)``
                            break
                        case .authenticationExpired:
                            // Error encontrado cuando el pago se realiza después de que la autenticación ha sido validada.
                            break
                        case .posNotSupported:
                            // Error encontrado cuando el POS no tiene el AID seleccionado en la lista.
                            break
                        case .cardNotSupported:
                            // La tarjeta no es compatible con pagos sin contacto.
                            break
                        case .cardNotActive:
                            // La tarjeta seleccionada no está activa.
                            break
                        case .authenticationFailed:
                            // Error encontrado cuando la autenticación falla.
                            break
                        case .authenticationKeyInvalidated(let underlying):
                            // La clave de autenticación se invalida porque el código de acceso del dispositivo se apagó o se restableció, lo que desencadena un restablecimiento de seguridad.
                            // El SDK borrará automáticamente las credenciales almacenadas cuando ocurra este error.
                            // La aplicación debe guiar al usuario a través del proceso de reinscripción.
                            // Publique una notificación para recargar la tarjeta. 
                            showAlert(message: underlying.localizedDescription) {
                              NotificationCenter.default.post(name: .didReceiveRequestForReloadCard, object: nil)
                            }
                            break
                        case .biometricNotEnrolled(let message):
                            // Muestra un mensaje al usuario final para realizar la inscripción biométrica.
                            showAlert(message: "\(message)")
                            break
                        @unknown default:
                            break
                        }
                    @unknown default:
                        fatalError("fixme: encountered unknown case")
                    }
                }
                
                isPaymentOngoing = false
            } else {
                // Compatibilidad con versiones anteriores.
                let alertController = UIAlertController(title: "Por favor use el dispositivo con iOS 17.4 o superior", message: nil, preferredStyle: .alert)
                alertController.addAction(
                    UIAlertAction(title: "OK", style: .default)
                )
                rootViewController?.present(alertController, animated: false)
            }
        }
    }
    
    func biometricType() -> LABiometryType {
        let authContext = LAContext()
        if #available(iOS 11, *) {
            authContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
            return authContext.biometryType
        }
        return LABiometryType.none
    }
}
```

{% endcode %}


---

# 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/nfc-wallet-sdk-ios/es/implement-nfc-wallet/make-payments/implement-contactless-payment/full-implementation-example.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.
