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
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
messageType | string | Sí | Siempre "CReq" |
messageVersion | string | Sí | Versión del protocolo (del Authenticate Response) |
threeDSServerTransID | string | Sí | ID de transacción (del Authenticate Response) |
acsTransID | string | Sí | ID del ACS (del Authenticate Response) |
challengeWindowSize | string | Sí | Tamaño de la ventana de challenge |
Tamaños de Ventana Disponibles
| Código | Dimensiones | Uso Recomendado |
|---|---|---|
"01" | 250px × 400px | Móvil (pantalla pequeña) |
"02" | 390px × 400px | Móvil (pantalla mediana) |
"03" | 500px × 600px | Tablet o desktop (ventana mediana) |
"04" | 600px × 400px | Desktop (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-safethreeDSSessionData: Datos de sesión que envió en elCReq, 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.
Updated about 10 hours ago