Tokenización de Tarjetas
Descripción General
Los requisitos de PCI DSS prohíben almacenar o transmitir datos de tarjeta sin procesar a menos que usted posea la certificación apropiada. La solución de tokenización de Inyo se encarga de esto por usted: la biblioteca inyo.js cifra los datos de la tarjeta directamente en el navegador y devuelve un token que solo sus claves de API pueden usar.
Cómo funciona:
- Cargue
inyo.jsen su página de pago - Agregue atributos
data-fielda sus campos de entrada de tarjeta - Inicialice
InyoTokenizercon su clave pública y sus callbacks - Llame a
tokenizeCard()cuando el usuario envíe el formulario — la biblioteca lee el formulario, cifra los datos y devuelve un token - Envíe el token a su backend para crear un pago mediante
POST /v2/payment
Lista Blanca de Dominios (Requerido)
Por razones de CORS y de seguridad, la URL de la página que carga el tokenizador debe registrarse con Inyo antes de que pueda realizar solicitudes de tokenización. Esto aplica a todos los entornos (sandbox y producción).
- Antes de salir a producción, proporcione a Inyo la URL de origen completa (p. ej.,
https://checkout.yoursite.com) de cada página que llamará al tokenizador. - Cualquier cambio en la URL (nuevo dominio, subdominio o puerto) debe comunicarse a Inyo para que la lista blanca pueda actualizarse.
- Las solicitudes de tokenización desde orígenes no incluidos en la lista blanca serán bloqueadas por CORS.
Si está usando la integración PostMessage para 3DS, la URL de la página que recibe los eventos postMessage también debe registrarse con Inyo. Esto garantiza que solo su página prevista pueda escuchar los resultados de autenticación 3DS. Vea Manejo de 3D Secure para más detalles.
Contacte a su gerente de integración de Inyo o a [email protected] para registrar sus URLs.
Carga de la Biblioteca
Incluya el script del tokenizador con un hash de integridad para prevenir alteraciones:
<script src="https://cdn.simpleps.com/sandbox/inyo.js" integrity="sha384-..." crossorigin="anonymous"> </script>
| Entorno | URL |
|---|---|
| Sandbox | https://cdn.simpleps.com/sandbox/inyo.js |
| Producción | https://cdn.simpleps.com/production/inyo.js |
Configuración del Formulario HTML
Agregue atributos data-field a sus elementos de entrada de tarjeta. El tokenizador se vincula a ellos automáticamente — no necesita leer los valores de los inputs usted mismo.
<div id="payment-form">
<!-- Nombre del tarjetahabiente -->
<input type="text" data-field="cardholder" id="cc-name" required>
<!-- Número de tarjeta (PAN) -->
<input type="text" data-field="pan" id="cc-number" maxlength="19" required>
<!-- Fecha de vencimiento (MM/YY) -->
<input type="text" data-field="expirationDate" id="cc-expiration"
maxlength="5" placeholder="MM/YY" required>
<!-- CVV / Código de seguridad -->
<input type="text" data-field="securitycode" id="cc-cvv"
maxlength="4" required>
<button type="button" id="pay-btn">Pay Now</button>
</div>
Valores de data-field requeridos:
data-field | Entrada | Notas |
|---|---|---|
cardholder | Nombre completo en la tarjeta | Tal como está impreso en la tarjeta |
pan | Número de tarjeta | 13–19 dígitos |
expirationDate | Vencimiento | Formato: MM/YY |
securitycode | CVV/CVC | 3 dígitos (Visa/MC/Discover) o 4 dígitos (Amex) |
Inicialización del Tokenizador
Cree una instancia de InyoTokenizer cuando el DOM esté listo. La configuración de 3DS depende del método de integración que elija — Redirección por URL o PostMessage:
Ejemplo — Redirección por URL (tradicional)
El proveedor de 3DS redirige el navegador a su successUrl o failUrl después de la autenticación. Ideal para aplicaciones renderizadas en el servidor y flujos de checkout de página completa.
document.addEventListener('DOMContentLoaded', () => {
const tokenizer = new InyoTokenizer({
targetId: '#payment-form',
publicKey: 'YOUR_PUBLIC_KEY',
storeLaterUse: false,
threeDSData: {
enable: true,
successUrl: 'https://yoursite.com/3ds/success',
failUrl: 'https://yoursite.com/3ds/fail'
},
successCallback: handleSuccess,
errorCallback: handleError
});
document.getElementById('pay-btn').addEventListener('click', () => {
tokenizer.tokenizeCard();
});
});
Ejemplo — PostMessage (SPA / embebido)
El resultado de 3DS se entrega mediante la API postMessage del navegador a la ventana padre. Ideal para aplicaciones de una sola página y experiencias de checkout en iframe/modal — no requiere navegación de página.
document.addEventListener('DOMContentLoaded', () => {
const tokenizer = new InyoTokenizer({
targetId: '#payment-form',
publicKey: 'YOUR_PUBLIC_KEY',
storeLaterUse: false,
threeDSData: {
enable: true,
enablePostMessage: true
},
successCallback: handleSuccess,
errorCallback: handleError
});
document.getElementById('pay-btn').addEventListener('click', () => {
tokenizer.tokenizeCard();
});
});
Cuando use
enablePostMessage: true, usted no proporcionasuccessUrlnifailUrl. En su lugar, escucha eventosmessageen la ventana padre después de abrir elredirectAcsUrlen un iframe. Vea Manejo de 3D Secure para la guía de implementación completa.
Parámetros de Configuración
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
targetId | string | Sí | Selector CSS del contenedor que contiene los inputs con data-field (p. ej., '#payment-form') |
publicKey | string | Sí | Su clave pública de comerciante, proporcionada por Inyo |
successCallback | function | Sí | Se llama cuando la tokenización tiene éxito |
errorCallback | function | Sí | Se llama cuando la tokenización falla |
storeLaterUse | boolean | No | false (predeterminado) = token de un solo uso; true = token recurrente/almacenado |
threeDSData | object | No | Configuración de 3D Secure (vea a continuación) |
Configuración de 3DS (threeDSData)
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
enable | boolean | Sí | Si se debe solicitar la autenticación 3DS |
successUrl | string | Solo modo redirección. URL a la que el proveedor de 3DS redirige en caso de éxito | |
failUrl | string | Solo modo redirección. URL a la que el proveedor de 3DS redirige en caso de falla | |
enablePostMessage | boolean | Solo modo PostMessage. Establezca true para recibir los resultados de 3DS mediante window.postMessage en lugar de redirección por URL |
Elija un modo:
- Redirección: Establezca
successUrl+failUrl. No establezcaenablePostMessage.- PostMessage: Establezca
enablePostMessage: true. No establezcasuccessUrl/failUrl.Habilitar 3DS no garantiza que ocurra un challenge — el banco emisor decide según su evaluación de riesgo. Vea Manejo de 3D Secure para el flujo completo y ejemplos de código para ambos modos.
Manejo de Callbacks
Callback de Éxito
function handleSuccess(response) {
console.log('Tokenization response:', response);
if (response.reasonCode === 'WAITING_TRANSACTION') {
// Token creado — extráigalo
const token = response.additionalData.token;
const lastFour = response.additionalData.lastFour;
console.log(`Card ending in ${lastFour}, token: ${token}`);
// Envíe el token a su servidor backend
// Su backend llamará a POST /v2/payment con este token
submitPaymentToBackend(token);
} else {
console.warn('Unexpected response step:', response.step);
}
}
Campos de la respuesta de éxito:
| Campo | Descripción |
|---|---|
reasonCode | "WAITING_TRANSACTION" cuando el token está listo |
additionalData.token | El UUID del token de la tarjeta — úselo como cardTokenId en las solicitudes de pago |
additionalData.lastFour | Últimos 4 dígitos del número de tarjeta (para mostrar) |
Callback de Error
function handleError(response) {
console.error('Tokenization error:', response);
// Limpie los estados de error anteriores
document.querySelectorAll('#cc-number, #cc-expiration, #cc-cvv')
.forEach(el => el.classList.remove('is-invalid'));
// Resalte el campo específico que falló
switch (response.code) {
case 'INVALID_PAN':
document.querySelector('#cc-number').classList.add('is-invalid');
break;
case 'INVALID_EXPIRY_DATE':
document.querySelector('#cc-expiration').classList.add('is-invalid');
break;
case 'INVALID_CVV':
document.querySelector('#cc-cvv').classList.add('is-invalid');
break;
default:
console.error('Unknown error code:', response.code);
}
}
Códigos de error:
| Código | Descripción |
|---|---|
INVALID_PAN | El número de tarjeta es inválido (falló la verificación de Luhn o esquema no soportado) |
INVALID_EXPIRY_DATE | La fecha de vencimiento es inválida o la tarjeta está vencida |
INVALID_CVV | El código de seguridad es inválido |
Tokens de Un Solo Uso vs. Recurrentes
| Tipo de Token | storeLaterUse | Uso |
|---|---|---|
| Un solo uso | false | Solo una transacción — no puede reutilizarse |
| Recurrente | true | Puede almacenarse y reutilizarse para cargos futuros |
Reglas:
- No debe almacenar tokens de un solo uso después de usarlos
- Puede almacenar tokens recurrentes en el servidor para transacciones futuras
- Nunca debe almacenar datos de tarjeta sin procesar (PAN, CVV, vencimiento), sin importar el tipo de token
- Al usar un token recurrente almacenado para un pago posterior, pase el
previousPaymentId(de la autorización original) junto con elcardTokenId
Ejemplos Completos
Ejemplo A — Redirección por URL (checkout renderizado en el servidor)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Inyo Payment — Redirect</title>
</head>
<body>
<form id="checkout-form" novalidate>
<div id="payment-form">
<div>
<label for="cc-name">Name on Card</label>
<input type="text" data-field="cardholder" id="cc-name" required>
</div>
<div>
<label for="cc-number">Card Number</label>
<input type="text" data-field="pan" id="cc-number" maxlength="19" required>
</div>
<div>
<label for="cc-expiration">Expiration</label>
<input type="text" data-field="expirationDate" id="cc-expiration"
placeholder="MM/YY" maxlength="5" required>
</div>
<div>
<label for="cc-cvv">CVV</label>
<input type="text" data-field="securitycode" id="cc-cvv"
maxlength="4" required>
</div>
<button type="button" id="pay-btn">Pay $99.99</button>
</div>
</form>
<script src="https://cdn.simpleps.com/sandbox/inyo.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const tokenizer = new InyoTokenizer({
targetId: '#payment-form',
publicKey: 'YOUR_PUBLIC_KEY',
storeLaterUse: false,
threeDSData: {
enable: true,
successUrl: 'https://yoursite.com/3ds/success',
failUrl: 'https://yoursite.com/3ds/fail'
},
successCallback: handleSuccess,
errorCallback: handleError
});
document.getElementById('pay-btn').addEventListener('click', () => {
const form = document.getElementById('checkout-form');
if (form.checkValidity()) {
tokenizer.tokenizeCard();
} else {
form.classList.add('was-validated');
}
});
});
function handleSuccess(response) {
if (response.reasonCode === 'WAITING_TRANSACTION') {
const token = response.additionalData.token;
// Envíe el token a su backend → POST /v2/payment
// Si la API devuelve status: "CHALLENGE", su backend
// redirige el navegador a redirectAcsUrl.
// Después de la autenticación, el proveedor de 3DS redirige a
// su successUrl o failUrl.
fetch('/api/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cardToken: token, amount: 99.99 })
});
}
}
function handleError(response) {
document.querySelectorAll('#cc-number, #cc-expiration, #cc-cvv')
.forEach(el => el.classList.remove('is-invalid'));
if (response.code === 'INVALID_PAN')
document.querySelector('#cc-number').classList.add('is-invalid');
else if (response.code === 'INVALID_EXPIRY_DATE')
document.querySelector('#cc-expiration').classList.add('is-invalid');
else if (response.code === 'INVALID_CVV')
document.querySelector('#cc-cvv').classList.add('is-invalid');
}
</script>
</body>
</html>
Ejemplo B — PostMessage (checkout SPA / embebido)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Inyo Payment — PostMessage</title>
</head>
<body>
<form id="checkout-form" novalidate>
<div id="payment-form">
<div>
<label for="cc-name">Name on Card</label>
<input type="text" data-field="cardholder" id="cc-name" required>
</div>
<div>
<label for="cc-number">Card Number</label>
<input type="text" data-field="pan" id="cc-number" maxlength="19" required>
</div>
<div>
<label for="cc-expiration">Expiration</label>
<input type="text" data-field="expirationDate" id="cc-expiration"
placeholder="MM/YY" maxlength="5" required>
</div>
<div>
<label for="cc-cvv">CVV</label>
<input type="text" data-field="securitycode" id="cc-cvv"
maxlength="4" required>
</div>
<button type="button" id="pay-btn">Pay $99.99</button>
</div>
</form>
<!-- Iframe oculto para el challenge 3DS -->
<div id="threeds-overlay" style="display:none; position:fixed; inset:0;
background:rgba(0,0,0,0.5); z-index:1000; align-items:center;
justify-content:center;">
<iframe id="threeds-iframe" style="width:500px; height:600px;
border:none; border-radius:8px; background:white;"
sandbox="allow-forms allow-scripts allow-same-origin allow-popups">
</iframe>
</div>
<script src="https://cdn.simpleps.com/sandbox/inyo.js"></script>
<script>
let tokenizer;
document.addEventListener('DOMContentLoaded', () => {
tokenizer = new InyoTokenizer({
targetId: '#payment-form',
publicKey: 'YOUR_PUBLIC_KEY',
storeLaterUse: false,
threeDSData: {
enable: true,
enablePostMessage: true // ← No se necesitan successUrl/failUrl
},
successCallback: handleTokenSuccess,
errorCallback: handleTokenError
});
document.getElementById('pay-btn').addEventListener('click', () => {
const form = document.getElementById('checkout-form');
if (form.checkValidity()) {
tokenizer.tokenizeCard();
} else {
form.classList.add('was-validated');
}
});
// Escuche los resultados 3DS por postMessage
window.addEventListener('message', handle3DSResult);
});
async function handleTokenSuccess(response) {
if (response.reasonCode === 'WAITING_TRANSACTION') {
const token = response.additionalData.token;
// Llame a su backend para crear el pago
const res = await fetch('/api/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cardToken: token, amount: 99.99 })
});
const payment = await res.json();
if (payment.status === 'CHALLENGE' && payment.redirectAcsUrl) {
// Abra el challenge 3DS en el overlay del iframe
document.getElementById('threeds-overlay').style.display = 'flex';
document.getElementById('threeds-iframe').src = payment.redirectAcsUrl;
} else if (payment.status === 'AUTHORIZED' || payment.status === 'CAPTURED') {
showSuccess(payment);
} else {
showError(payment.message || 'Payment declined.');
}
}
}
function handle3DSResult(event) {
// Seguridad: valide el origen del mensaje
if (!event.origin.includes('simpleps.com')) return;
const data = event.data;
// Cierre el overlay de 3DS
document.getElementById('threeds-overlay').style.display = 'none';
document.getElementById('threeds-iframe').src = '';
if (data.approved === true || data.approved === 'true') {
// Verifique siempre en su backend antes de completar el pedido
verifyAndComplete(data.externalPaymentId);
} else {
showError('Card verification failed. Please try again.');
}
}
async function verifyAndComplete(externalPaymentId) {
const res = await fetch(`/api/payment/${externalPaymentId}/verify`);
const payment = await res.json();
if (payment.status === 'AUTHORIZED' || payment.status === 'CAPTURED') {
showSuccess(payment);
} else {
showError('Payment could not be confirmed.');
}
}
function handleTokenError(response) {
document.querySelectorAll('#cc-number, #cc-expiration, #cc-cvv')
.forEach(el => el.classList.remove('is-invalid'));
if (response.code === 'INVALID_PAN')
document.querySelector('#cc-number').classList.add('is-invalid');
else if (response.code === 'INVALID_EXPIRY_DATE')
document.querySelector('#cc-expiration').classList.add('is-invalid');
else if (response.code === 'INVALID_CVV')
document.querySelector('#cc-cvv').classList.add('is-invalid');
}
function showSuccess(payment) {
alert(`Payment confirmed! ID: ${payment.paymentId || payment.externalPaymentId}`);
}
function showError(message) {
alert(message);
}
</script>
</body>
</html>
Próximos Pasos
Con un token en mano, continúe con Autorización de un Pago con Tarjeta para crear la transacción mediante POST /v2/payment.
