> 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 completo de implementación

Use esta muestra de código para entender cómo encajan los pasos anteriores.

Inicia el pago sin contacto desde:

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

{% hint style="info" %}
Este ejemplo asume que ya revisaste 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 podría necesitar iniciar la emulación en la detección de campo
    var isPaymentOngoing = false
    
    // para admitir el inicio de pagos sin contacto mediante doble clic o acción de detección de campo cuando la aplicación no está en ejecución:
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Usa este método para configurar opcionalmente y adjuntar la UIWindow `window` a la UIWindowScene `scene` proporcionada.
        // Si usas 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 que se conectan sean nuevas (consulta en su lugar `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, ya que solo se activa cuando es la aplicación de pago predeterminada
                    isDefaultPaymentApp = true
                    startPayment()
                case .readerDetected:
                    // establecer en true, ya que solo se activa cuando es la 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 podrías establecer otra aplicación como predeterminada
        isDefaultPaymentApp = false
        
        // Se llama cuando la escena pasa del primer plano al fondo.
        // Usa este método para guardar datos, liberar recursos compartidos y almacenar suficiente 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 iniciar la sesión, aceptará APDU y completará el pago en segundo plano
                // esto es para cancelarlo mientras la app va al fondo.
                // Esta acción también se recomienda cuando la app pasa a una pantalla diferente de la pantalla de pago, etc.
                await session.cancel()
            }
        }
    }
    

}

extension SceneDelegate: NFCWindowSceneDelegate {
    @available(iOS 17.4, *)
    // para admitir el inicio de pagos sin contacto mediante doble clic o acción de detección de campo cuando la aplicación está en segundo plano o en 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 {
    
    // mostrar en la UI que el pago se inicia con doble clic
    func displayAlertWithDoubleClickStartPayment(withCurrentVC currentVC: UIViewController?) {
        let alertController = UIAlertController(title: "Por favor, haz doble clic para iniciar el pago", message: nil, preferredStyle: .alert)
        alertController.addAction(
            UIAlertAction(title: "OK", style: .default)
        )
        currentVC?.present(alertController, animated: false)
    }
    
    // devuelve true si 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 la consideramos readerDetect para una paymentSession en curso
        // solo para iOS 18 +
        if #available(iOS 18, *), isPaymentOngoing {
            // es iOS 18 + donde el pago en curso ya recibió el evento readerDetect
            Task {
                try await self.session.startEmulation()
            }
        } else {
            // manejar la detección de campo normalmente en el siguiente escenario:
            // - 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 digital
            if biometricType == .touchID {
                startPayment()
            } else {
                displayAlertWithDoubleClickStartPayment(withCurrentVC: currentVC)
            }
        }
    }
    
    func startPayment(withDigitalCardID digitalCardID: String? = nil) {
        Task {
            
            // inicializa el SDK
            try await TSHPay.shared.configure(withVerificationMethod: .userPresence)
            
            if #available(iOS 17.4, *) {
                
                // indica que el pago está en curso, es necesario para la detección de campo y iOS 18
                isPaymentOngoing = false
                                
                // comprobar si es la aplicación de pago predeterminada y verificar si presentmentIntent es válido
                if !isDefaultPaymentApp, !PresentmentIntentWrapper.isValid() {
                    do {
                        // No detecta el comportamiento de la 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 período de enfriamiento
                        // tenemos que iniciar la emulación directamente; al menos no nos redirigirán la app
                    }
                }
                
                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 tarjeta
                        // si quieres iniciarEmulation en Event .authenticationCompleted
                        // try await session.startEmulation()
                        break
                    case .posConnected:
                        print("StartPayment:: posConnected")
                        await session.setAlertMessage("POS conectado")
                        // si la aplicación no adquiere NFCPresentmentIntentAssertion, no podrá llamar a startEmulation() en este evento.
                        try await session.startEmulation()
                        break
                    case .posDisconnected:
                        print("StartPayment:: posDisconnected")
                        await session.setAlertMessage("POS desconectado")
                    case .transactionCompleted(let context):
                        print("StartPayment:: transactionCompleted")
                        await session.setAlertMessage("Transacción completada")
                        
                        // mostrar al usuario
                        displayTransactionContext(context)
                    case .errorEncountered(let error):
                        switch (error as? ContactlessPaymentSession.Error) {
                        case .cancelled:
                            // El usuario final cancela la transacción.
                            break
                        case .maxSessionDurationReached:
                            // Muestra un mensaje al usuario final de que la transacción ha caducado.
                            break
                        case .nfcPermissionNotAccepted:
                            // Muestra un mensaje al usuario final de que el permiso no está permitido.
                            break
                        case .systemEligibilityFailed:
                            // Muestra un mensaje al usuario final de que el sistema no es elegible para el pago sin contacto.
                            // por ejemplo, Apple ID o la ubicación del dispositivo no está en el EEE.
                            break
                        case .noPaymentKeys:
                            // Se requiere reposición. Consulta la guía de reposición.
                            break
                        case .noDefaultCard:
                            // La tarjeta predeterminada debe establecerse antes de la transacción.
                            break
                        case .keychainError:
                            // Muestra un mensaje de error al usuario final
                            break
                        case .apduFailure(let reason):
                            // Intenta de nuevo
                            break
                        case .unknown(let underlying):
                            // Muestra un 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 inseguro del dispositivo
                            break
                        case .setDefaultCardFailure:
                            // Error encontrado al intentar establecer el Digital Card ID proporcionado como tarjeta predeterminada durante la llamada a la API `startPayment(withDigitalCardID:)`.
                            break
                        case .invalidDigitalCardID:
                            // El Digital Card ID proporcionado no es válido cuando se usa ``startPayment(withDigitalCardID:)``
                            break
                        case .authenticationExpired:
                            // Error encontrado cuando el pago se realiza después de que la autenticación haya 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 el pago sin contacto.
                            break
                        case .cardNotActive:
                            // La tarjeta seleccionada no está activa.
                            break
                        case .authenticationFailed:
                            // Error encontrado cuando falla la autenticación.
                            break
                        case .authenticationKeyInvalidated(let underlying):
                            // La clave de autenticación se invalida porque el código del dispositivo está desactivado o se restablece, lo que desencadena un restablecimiento de seguridad.
                            // El SDK borrará automáticamente las credenciales almacenadas cuando ocurra este error.
                            // La app debe guiar al usuario a través del proceso de nuevo registro.
                            // Publica 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 el registro biométrico.
                            showAlert(message: "\(message)")
                            break
                        @unknown default:
                            break
                        }
                    @unknown default:
                        fatalError("fixme: encountered unknown case")
                    }
                }
                
                isPaymentOngoing = false
            } else {
                // Alternativa en versiones anteriores.
                let alertController = UIAlertController(title: "Por favor, usa un 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.
