Payer Auth API v1.0.0

Microservicio independiente que encapsula el flujo completo de CyberSource 3D Secure 2.x (EMV 3DS) Payer Authentication. Soporta múltiples comercios, cada uno con sus propias credenciales CyberSource.

Node.js Sails.js MongoDB Atlas

Base URL

https://auth.etomin.com/api/v1

Quick Start en 5 Pasos

  1. Crear un comercio
    Llamar a POST /api/v1/merchant con tu x-admin-key y las credenciales de CyberSource.
  2. Guardar tu API Key
    La apiKey solo se muestra al crear el comercio. Guárdala en un lugar seguro.
  3. Llamar a /setup
    Enviar datos de tarjeta y facturación. Recibirás el accessToken y deviceDataCollectionUrl.
  4. Completar Device Data Collection
    Montar el iframe oculto de Cardinal Commerce para recopilar información del dispositivo.
  5. Llamar a /enrollment
    Enviar deviceInfo del navegador. Manejar el flujo frictionless o lanzar el iframe de challenge.

Arquitectura Multi-Tenant

Comercio A
Comercio B
Comercio C
Payer Auth API
CyberSource 3DS

Cada comercio usa sus propias credenciales CyberSource identificadas por su API Key.

Autenticación

La API soporta dos mecanismos de autenticación por header HTTP.

🔑 API Key

Para comercios. Se aplica a todos los endpoints excepto los públicos.

Header: x-api-key

Valor: UUID generado al crear el comercio.

JSON — 401 Unauthorized
{ "error": 1, "message": "Unauthorized: Missing or invalid API key." }

🔐 Admin Key

Para administración. Solo aplica a endpoints /api/v1/merchant.

Header: x-admin-key

Valor: variable de entorno ADMIN_API_KEY.

JSON — 403 Forbidden
{ "error": 1, "message": "Forbidden: Invalid admin key." }

Endpoints Públicos (sin autenticación)

MétodoEndpointDescripción
GET/healthHealth check del servicio
POST/api/v1/3ds/challenge/responseCallback automático de CyberSource

Flujo 3DS Completo

Diagrama de secuencia del flujo completo de autenticación 3D Secure. Haz clic en cada paso para ver detalles.

Frontend Valerian API CyberSource Banco Emisor POST /setup decisionManager + payerAuthSetup riskScore + accessToken { accessToken, deviceDataCollectionUrl } iframe DDC (Cardinal) DDC complete ✓ POST /enrollment + deviceInfo checkEnrollment enrollment result FRICTIONLESS authResult directo CHALLENGE { stepUpUrl, enrollmentToken } iframe stepUp challenge al banco emisor POST /challenge/response validateAuth postMessage + redirect POST /validation (opcional) resultado cacheado/fresco

Endpoints — Flujo 3DS

POST /api/v1/3ds/setup Paso 1 — Decision Manager + Payer Auth Setup
Auth: x-api-key

Ejecuta Decision Manager (evaluación de riesgo) y Payer Auth Setup. Retorna el accessToken y deviceDataCollectionUrl necesarios para el iframe de Device Data Collection.

Request Body

CampoTipoRequeridoDescripción
referencestringID de correlación del cliente
cardNumberstringNúmero de tarjeta completo
expirationMonthstringMes de expiración (MM)
expirationYearstringAño de expiración (YYYY)
amountstring/numberMonto de la transacción
currencystringopcionalCódigo ISO. Default: MXN
callbackUrlstringopcionalURL de redirección post-challenge
billingInfoobjectDatos de facturación (ver abajo)
deviceInfoobjectopcionalInfo del dispositivo (si disponible en este paso)

billingInfo (requerido)

CampoDescripción
firstName, lastNameNombre del titular
emailEmail del titular
countryCódigo ISO 2 letras (ej. MX)
postalCode, state, address1Dirección de facturación
JSON
{
  "reference": "order-12345",
  "cardNumber": "4111111111111111",
  "expirationMonth": "12",
  "expirationYear": "2025",
  "amount": "150.00",
  "currency": "MXN",
  "callbackUrl": "https://mi-sitio.com/3ds-result",
  "billingInfo": {
    "firstName": "Juan",
    "lastName": "Pérez",
    "email": "juan@example.com",
    "country": "MX",
    "postalCode": "01000",
    "state": "CDMX",
    "address1": "Av. Reforma 123"
  }
}
cURL
curl -X POST https://auth.etomin.com/api/v1/3ds/setup \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "reference": "order-12345",
    "cardNumber": "4111111111111111",
    "expirationMonth": "12",
    "expirationYear": "2025",
    "amount": "150.00",
    "currency": "MXN",
    "callbackUrl": "https://mi-sitio.com/3ds-result",
    "billingInfo": {
      "firstName": "Juan",
      "lastName": "Pérez",
      "email": "juan@example.com",
      "country": "MX",
      "postalCode": "01000",
      "state": "CDMX",
      "address1": "Av. Reforma 123"
    }
  }'

Response 200

JSON
{
  "error": 0,
  "transactionId": "60f1b2c3d4e5f6a7b8c9d0e1",
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "deviceDataCollectionUrl": "https://centinelapi.cardinalcommerce.com/V1/Cruise/Collect",
  "referenceId": "abc123def456",
  "orgUnitId": "5a4b3c2d1e0f"
}
POST /api/v1/3ds/enrollment Paso 3 — Check Enrollment
Auth: x-api-key

Envía la información del dispositivo del navegador recopilada por el frontend. Retorna resultado frictionless o URL de challenge.

deviceInfo — 10 campos requeridos

CampoTipoEjemplo
httpAcceptstring"text/html,application/xhtml+xml"
httpUserAgentstring"Mozilla/5.0 (Macintosh...)"
httpBrowserColorDepthstring"24"
httpBrowserJavaEnabledbooleanfalse
httpBrowserJavaScriptEnabledbooleantrue
httpBrowserLanguagestring"es-MX"
httpBrowserScreenHeightstring"900"
httpBrowserScreenWidthstring"1440"
httpBrowserTimeDifferencestring"360"
ipAddressstring"192.168.1.100"
JSON — FRICTIONLESS
{
  "error": 0,
  "challengeRequired": false,
  "authFlow": "FRICTIONLESS",
  "transactionId": "60f1b2c3d4e5f6a7b8c9d0e1",
  "reasonCode": "100",
  "consumerAuthenticationInformation": {
    "cavv": "AAABAWFlmQAAAABjRWWZEEFgFz8=",
    "cavvAlgorithm": "2",
    "eciRaw": "05",
    "eci": "05",
    "xid": "...",
    "directoryServerTransactionId": "...",
    "specificationVersion": "2.2.0",
    "paresStatus": "Y",
    "authenticationResult": "Y"
  }
}
JSON — CHALLENGE
{
  "error": 0,
  "challengeRequired": true,
  "authFlow": "CHALLENGE",
  "transactionId": "60f1b2c3d4e5f6a7b8c9d0e1",
  "enrollmentToken": "eyJhbGciOiJIUzI1NiJ9...",
  "stepUpUrl": "https://centinelapi.cardinalcommerce.com/V2/Cruise/StepUp",
  "authenticationTransactionId": "..."
}
POST /api/v1/3ds/challenge/response Paso 4 — Callback de CyberSource (público)
Endpoint Público — No requiere autenticación. CyberSource/Cardinal hace POST automáticamente a esta URL.

Callback después de que el tarjetahabiente completa el challenge. No es llamado por el frontend directamente.

Responde con HTML que envía un postMessage al parent window y redirige al callbackUrl del comercio con el resultado en base64:

URL de redirección
https://mi-sitio.com/3ds-result?result=eyJlcnJvciI6MCwidHJhbnNhY3Rpb25JZC...

El query param result decodificado en Base64 contiene el JSON del resultado de autenticación.

POST /api/v1/3ds/validation Paso 5 (opcional) — Validar resultado
Auth: x-api-key

Consulta o re-valida el resultado de autenticación. Si ya fue validado, retorna el resultado cacheado.

JSON
{ "transactionId": "60f1b2c3d4e5f6a7b8c9d0e1" }
JSON — 200 OK
{
  "error": 0,
  "transactionId": "60f1b2c3d4e5f6a7b8c9d0e1",
  "reference": "order-12345",
  "authFlow": "FRICTIONLESS",
  "reasonCode": "100",
  "authenticationResult": "Y",
  "consumerAuthenticationInformation": {
    "cavv": "...",
    "eci": "05",
    "xid": "...",
    "directoryServerTransactionId": "...",
    "specificationVersion": "2.2.0",
    "paresStatus": "Y"
  }
}

Merchant Management (Admin)

Requiere Admin Key — Todos los endpoints de esta sección requieren el header x-admin-key.
POST /api/v1/merchant Crear nuevo comercio

Crea un nuevo comercio con credenciales CyberSource. Genera automáticamente un UUID como API Key.

⚠️ Importante: La apiKey solo se muestra en este response. Guárdala inmediatamente.
JSON
{
  "name": "Mi Comercio",
  "cybersourceMerchantId": "merchant_live_id",
  "cybersourceMerchantKeyId": "live_key_id",
  "cybersourceMerchantSecretKey": "live_secret_key",
  "cybersourceMerchantIdSandbox": "merchant_test",
  "cybersourceMerchantKeyIdSandbox": "test_key",
  "cybersourceMerchantSecretKeySandbox": "test_secret",
  "mode": "SANDBOX",
  "status": "ACTIVE",
  "orgUnitId": "5a4b3c2d1e0f",
  "defaultCurrency": "MXN"
}
JSON — 200 OK
{
  "error": 0,
  "message": "Merchant created successfully.",
  "data": {
    "id": "60f1b2c3d4e5f6a7b8c9d0e1",
    "name": "Mi Comercio",
    "apiKey": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "mode": "SANDBOX",
    "status": "ACTIVE"
  }
}
GET /api/v1/merchant Listar todos los comercios
GET /api/v1/merchant/:id Obtener comercio por ID
PUT /api/v1/merchant/:id Actualizar comercio
GET /health Health check (público)

Guía de Integración Frontend

Código JavaScript completo para integrar el flujo 3DS en tu frontend.

Paso 1 — Llamar a /setup

JavaScript
const setupResponse = await fetch('https://auth.etomin.com/api/v1/3ds/setup', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'YOUR_API_KEY'
  },
  body: JSON.stringify({
    reference: 'order-12345',
    cardNumber: '4111111111111111',
    expirationMonth: '12',
    expirationYear: '2025',
    amount: '150.00',
    currency: 'MXN',
    callbackUrl: window.location.origin + '/3ds-result',
    billingInfo: {
      firstName: 'Juan',
      lastName: 'Pérez',
      email: 'juan@example.com',
      country: 'MX',
      postalCode: '01000',
      state: 'CDMX',
      address1: 'Av. Reforma 123'
    }
  })
});
const setup = await setupResponse.json();
// setup.transactionId, setup.accessToken, setup.deviceDataCollectionUrl

Paso 2 — Device Data Collection (iframe oculto)

JavaScript
// Crear iframe oculto para DDC
const iframe = document.createElement('iframe');
iframe.name = 'collectionIframe';
iframe.style.display = 'none';
document.body.appendChild(iframe);

// Crear form y enviar al DDC URL
const form = document.createElement('form');
form.method = 'POST';
form.target = 'collectionIframe';
form.action = setup.deviceDataCollectionUrl;

const input = document.createElement('input');
input.type = 'hidden';
input.name = 'JWT';
input.value = setup.accessToken;
form.appendChild(input);
document.body.appendChild(form);
form.submit();

// Escuchar evento de DDC completado
window.addEventListener('message', function(event) {
  if (event.origin.includes('cardinalcommerce.com')) {
    console.log('DDC Complete:', event.data);
    // Proceder al enrollment
  }
});

Paso 3 — Recopilar deviceInfo y llamar a /enrollment

JavaScript
const deviceInfo = {
  httpAccept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  httpUserAgent: navigator.userAgent,
  httpBrowserColorDepth: String(screen.colorDepth),
  httpBrowserJavaEnabled: false,
  httpBrowserJavaScriptEnabled: true,
  httpBrowserLanguage: navigator.language,
  httpBrowserScreenHeight: String(screen.height),
  httpBrowserScreenWidth: String(screen.width),
  httpBrowserTimeDifference: String(new Date().getTimezoneOffset()),
  ipAddress: '0.0.0.0' // Obtener del servidor o servicio externo
};

const enrollResponse = await fetch('https://auth.etomin.com/api/v1/3ds/enrollment', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'YOUR_API_KEY'
  },
  body: JSON.stringify({
    transactionId: setup.transactionId,
    deviceInfo
  })
});
const enrollment = await enrollResponse.json();

Paso 4 — Manejar Challenge (iframe visible)

JavaScript
if (enrollment.challengeRequired) {
  // Crear iframe para step-up challenge
  const stepUpIframe = document.createElement('iframe');
  stepUpIframe.name = 'step-up-iframe';
  stepUpIframe.height = '400';
  stepUpIframe.width = '400';
  document.getElementById('challenge-container').appendChild(stepUpIframe);

  // Enviar JWT al step-up URL
  const stepUpForm = document.createElement('form');
  stepUpForm.method = 'POST';
  stepUpForm.target = 'step-up-iframe';
  stepUpForm.action = enrollment.stepUpUrl;

  const jwtInput = document.createElement('input');
  jwtInput.type = 'hidden';
  jwtInput.name = 'JWT';
  jwtInput.value = enrollment.enrollmentToken;
  stepUpForm.appendChild(jwtInput);

  const mdInput = document.createElement('input');
  mdInput.type = 'hidden';
  mdInput.name = 'MD';
  mdInput.value = enrollment.transactionId;
  stepUpForm.appendChild(mdInput);

  document.body.appendChild(stepUpForm);
  stepUpForm.submit();

  // Escuchar resultado del challenge via postMessage
  window.addEventListener('message', function(event) {
    if (event.data && event.data.type === '3ds-complete') {
      console.log('Challenge complete:', event.data.result);
      // event.data.href contiene la URL de redirección
    }
  });
}

Paso 5 — Verificar resultado

JavaScript
// Si se usa callbackUrl, decodificar el result del query param
const params = new URLSearchParams(window.location.search);
const resultBase64 = params.get('result');
if (resultBase64) {
  const result = JSON.parse(atob(resultBase64));
  console.log('3DS Result:', result);
  // result.authenticationResult: 'Y' = exitoso
  // result.consumerAuthenticationInformation.cavv
  // result.consumerAuthenticationInformation.eci
}
JavaScript
const validationResponse = await fetch('https://auth.etomin.com/api/v1/3ds/validation', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'YOUR_API_KEY'
  },
  body: JSON.stringify({
    transactionId: setup.transactionId
  })
});
const validation = await validationResponse.json();

Valores de Autenticación 3DS

CampoDescripciónValores
authenticationResult / paresStatusResultado de autenticaciónY=Exitoso · N=Fallido · U=No disponible · R=Rechazado · A=Intento
eciElectronic Commerce Indicator05=Visa auth · 06=Visa intento · 02=MC auth · 01=MC intento
cavvCardholder Auth Verification ValueBase64 string — valor criptográfico de autenticación
xidTransaction IdentifierIdentificador de la transacción 3DS
directoryServerTransactionIdDS Transaction IDUUID del Directory Server
specificationVersionVersión 3DS"2.1.0" o "2.2.0"
reasonCodeCódigo de razón CyberSource100=Exitoso · 475=Pendiente · 476=Challenge

Tipos de Tarjeta Soportados

MarcaCódigo CyberSourcePrefijos
Visa0014
Mastercard00251-55, 2221-2720
American Express00334, 37
Discover0046011, 644-649, 65
JCB0073528-3589
Maestro04250, 56-69

Tarjetas de Prueba (Sandbox)

NúmeroMarcaEscenario
4111111111111111VisaFrictionless Autenticación exitosa (Y)
4000000000002503VisaChallenge Challenge requerido
4000000000002511VisaFallida Challenge — Autenticación fallida (N)
5200000000001096MastercardFrictionless Autenticación exitosa
5200000000001005MastercardChallenge Challenge requerido
378282246310005AMEXFrictionless Autenticación exitosa

Códigos de Error

Formato estándar de respuesta de error:

JSON
{
  "error": 1,
  "message": "Descripción del error",
  "details": "Detalle técnico (solo en 500)"
}
HTTPMensajeCausa
400Missing required fields: ...Campos obligatorios no enviados
400Missing required billingInfo fields: ...Sub-campos de billingInfo faltantes
400Missing required deviceInfo fields: ...Campos del navegador faltantes
401Unauthorized: Missing or invalid API keyx-api-key ausente o inválido
403Forbidden: Invalid admin keyx-admin-key ausente o inválido
404Transaction not found...Transacción no existe o en paso incorrecto
404Merchant not foundComercio no existe
500Payer Auth Setup failedError en CyberSource Setup
500Check Enrollment failedError en CyberSource Enrollment
500Internal error during 3DS...Error interno del servidor

Estados de la Transacción

Máquina de estados del flujo de autenticación. Haz clic en un estado para ver su descripción.

SETUP
DEVICE_DATA_COLLECTED
ENROLLMENT_CHECKED
Frictionless
VALIDATED
Challenge
CHALLENGE_PENDING
CHALLENGE_COMPLETED
VALIDATED
(En cualquier paso)
FAILED

authFlow & challengeStatus

CampoValores posibles
authFlowPENDINGFRICTIONLESS | CHALLENGE
challengeStatuspendingprocessingcompleted (lock atómico anti-duplicados)