Challenge

El Challenge Request se ejecuta cuando el Authenticate Response asi lo indica. Este paso presenta al usuario un desafío de autenticación (como OTP, biometría, o preguntas de seguridad) en un iframe.


Propósito

El Challenge permite al emisor:

  • Verificar la identidad del titular de la tarjeta
  • Solicitar autenticación adicional (OTP vía SMS, app del banco, etc.)
  • Reducir fraude en transacciones de alto riesgo

Función SendChallengeRequest()

La librería JavaScript de Akua proporciona una función para enviar el Challenge Request:

function SendChallengeRequest(acsURL, creq, sessionData) {
    let frameContainer = document.getElementById('challengeFrameContainer');

    const windowSize = getWindowSize(creq.challengeWindowSize)

    const creqBase64 = encode(JSON.stringify(creq));
    const sessionDataBase64 = encode(JSON.stringify(sessionData))
    const challengeIframeName = 'challengeIframe'

    const challengeIframe = createIframe(
        frameContainer,
        challengeIframeName,
        challengeIframeName,
        windowSize[0],
        windowSize[1]
    )
    const form = createForm('threeDSCReqForm', acsURL, challengeIframe.name)
    const creqInput = createInput('creq', creqBase64)
    const sessionDataInput = createInput('threeDSSessionData', sessionDataBase64)

    form.appendChild(creqInput);
    form.appendChild(sessionDataInput);
    challengeIframe.appendChild(form);

    form.submit();
}

Estructura del CReq

El objeto CReq contiene:

{
  "messageType": "CReq",
  "messageVersion": "2.2.0",
  "threeDSServerTransID": "8a880dc0-1234-5678-9abc-def012345678",
  "acsTransID": "d7c1ee99-9478-44a6-b1f2-391e29c6b340",
  "challengeWindowSize": "05"
}

Campos del CReq

CampoTipoRequeridoDescripción
messageTypestringSiempre "CReq"
messageVersionstringVersión del protocolo (del Authenticate Response)
threeDSServerTransIDstringID de transacción (del Authenticate Response)
acsTransIDstringID del ACS (del Authenticate Response)
challengeWindowSizestringTamaño de la ventana de challenge

Tamaños de Ventana Disponibles

CódigoDimensionesUso Recomendado
"01"250px × 400pxMóvil (pantalla pequeña)
"02"390px × 400pxMóvil (pantalla mediana)
"03"500px × 600pxTablet o desktop (ventana mediana)
"04"600px × 400pxDesktop (ventana ancha)
"05"100% × 100%Pantalla completa (recomendado para responsive)

💡 Recomendación: Use "05" para una experiencia responsive que se adapte a cualquier dispositivo.

Implementación del Contenedor del Challenge

Debe crear un contenedor en su HTML donde se mostrará el iframe del challenge:

<!-- Contenedor visible para el challenge -->
<div id="challengeFrameContainer"></div>

O mejor aún, usar un modal para mejor experiencia de usuario:

<!-- Modal con overlay -->
<div id="challengeModal" class="modal">
  <div class="modal-content">
    <div id="challengeFrameContainer"></div>
  </div>
</div>

URL de Notificación del Challenge

El ACS enviará el resultado del challenge a una URL de notificación. Esta URL debe ser configurada en su servidor:

https://su-dominio.com/api/3ds/challenge-notification

Este endpoint recibirá un POST con los siguientes parámetros:

  • cres: Challenge Response en base64 URL-safe
  • threeDSSessionData: Datos de sesión que envió en el CReq, en base64 URL-safe

Formato de la Notificación

POST /api/3ds/challenge-notification
Content-Type: application/x-www-form-urlencoded

cres=eyJtZXNzYWdlVHlwZSI6IkNSZXMiLCAi...&threeDSSessionData=eyJ0cmFuc2FjdGlvbklkIjoi...

Debe decodificar estos parámetros:

function decodeBase64Url(str) {
    // Agregar padding si es necesario
    let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
    const padding = base64.length % 4;
    if (padding) {
        base64 += '='.repeat(4 - padding);
    }
    return atob(base64);
}

// Decodificar cres
const cresDecoded = JSON.parse(decodeBase64Url(cres));
// Decodificar session data
const sessionDataDecoded = JSON.parse(decodeBase64Url(threeDSSessionData));

Comunicación entre iframes usando postMessage

Después de recibir la notificación en el backend, debe notificar al frontend que el challenge se completó. Use window.postMessage() para comunicación segura entre iframes:

// En su página de challenge notification (que corre dentro del iframe)
window.parent.postMessage({
    challengeCompleted: true,
    threeDSServerTransID: sessionDataDecoded.transactionId,
    status: 'SUCCESS'
}, '*');
// En su página principal (que contiene el iframe)
window.addEventListener('message', (event) => {
    if (event.data.challengeCompleted) {
        console.log('Challenge completado:', event.data);

        // Cerrar el modal del challenge
        closeChallengeModal();

        // Obtener el resultado final
        await getResultAndAuthorize(event.data.threeDSServerTransID);
    }
});

Ejemplo Completo de Flujo Challenge

// 1. Usuario completa el formulario de pago
async function handleCheckout() {
    // ... recolectar datos ...

    // 2. Hacer Authenticate Request
    const authResponse = await fetch('/v1/3ds/authenticate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(authenticateRequest)
    });

    const authData = await authResponse.json();

    // 3. Verificar si se requiere challenge
    if (authData.data.trans_status === 'C') {
        // Challenge requerido
        const challengeRequest = {
            messageType: 'CReq',
            messageVersion: authData.data.message_version,
            threeDSServerTransID: authData.data.threeds_server_trans_id,
            acsTransID: authData.data.acs_trans_id,
            challengeWindowSize: '05'
        };

        const sessionData = {
            transactionId: authData.transaction_id
        };

        // 4. Abrir modal y enviar challenge
        openChallengeModal();
        SendChallengeRequest(
            authData.data.acs_url,
            challengeRequest,
            sessionData
        );

        // 5. Esperar mensaje de completado
        // (manejado por el event listener de 'message')
    } else if (authData.data.trans_status === 'Y') {
        // Frictionless - proceder directamente
        await authorizePayment(authData);
    }
}

// 6. Escuchar completado del challenge
window.addEventListener('message', async (event) => {
    if (event.data.challengeCompleted) {
        closeChallengeModal();

        // 7. Obtener resultado final
        const resultResponse = await fetch('/v1/3ds/result', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                merchant_id: 'merchant_12345',
                threeds_server_trans_id: event.data.threeDSServerTransID
            })
        });

        const resultData = await resultResponse.json();

        // 8. Autorizar pago con el resultado
        if (resultData.data.trans_status === 'Y') {
            await authorizePayment(resultData);
        }
    }
});

Timeout del Challenge

A diferencia del Method Request, NO debe establecer un timeout para el Challenge. El usuario puede tomar el tiempo que necesite para completar la autenticación (ingresar OTP, aprobar en app del banco, etc.).

El emisor puede tener su propio timeout, pero desde su lado debe esperar indefinidamente hasta recibir la notificación del challenge.


¿Qué sigue?
logo akua

© Akua 2025 - Todos los derechos reservados