Tokenizando Cartões
Visão Geral
Os requisitos do PCI DSS proíbem armazenar ou transmitir dados brutos de cartão a menos que você possua a certificação apropriada. A solução de tokenização da Inyo cuida disso para você: a biblioteca inyo.js criptografa os dados do cartão diretamente no navegador e retorna um token que só as suas chaves de API podem usar.
Como funciona:
- Carregue o
inyo.jsna sua página de pagamento - Adicione atributos
data-fieldaos seus campos de entrada de cartão - Inicialize o
InyoTokenizercom a sua chave pública e callbacks - Chame
tokenizeCard()quando o usuário enviar o formulário — a biblioteca lê o formulário, criptografa os dados e retorna um token - Envie o token para o seu backend para criar um pagamento via
POST /v2/payment
Whitelist de Domínios (Obrigatório)
Por razões de CORS e segurança, a URL da página que carrega o tokenizador deve ser registrada na Inyo antes de poder fazer requisições de tokenização. Isso se aplica a todos os ambientes (sandbox e produção).
- Antes de entrar em produção, forneça à Inyo a URL de origem completa (ex.:
https://checkout.yoursite.com) de cada página que chamará o tokenizador. - Qualquer mudança na URL (novo domínio, subdomínio ou porta) deve ser comunicada à Inyo para que a whitelist seja atualizada.
- Requisições de tokenização de origens fora da whitelist serão bloqueadas pelo CORS.
Se você estiver usando a integração PostMessage para o 3DS, a URL da página que recebe os eventos postMessage também deve ser registrada na Inyo. Isso garante que apenas a página pretendida possa escutar os resultados da autenticação 3DS. Veja Tratando o 3D Secure para detalhes.
Entre em contato com o seu gerente de integração Inyo ou com [email protected] para registrar as suas URLs.
Carregando a Biblioteca
Inclua o script do tokenizador com um hash de integridade para prevenir adulterações:
<script src="https://cdn.simpleps.com/sandbox/inyo.js" integrity="sha384-..." crossorigin="anonymous"> </script>
| Ambiente | URL |
|---|---|
| Sandbox | https://cdn.simpleps.com/sandbox/inyo.js |
| Produção | https://cdn.simpleps.com/production/inyo.js |
Configuração do Formulário HTML
Adicione atributos data-field aos elementos de entrada do cartão. O tokenizador se vincula a eles automaticamente — não é necessário ler os valores dos inputs você mesmo.
<div id="payment-form">
<!-- Nome do portador do cartão -->
<input type="text" data-field="cardholder" id="cc-name" required>
<!-- Número do cartão (PAN) -->
<input type="text" data-field="pan" id="cc-number" maxlength="19" required>
<!-- Data de validade (MM/YY) -->
<input type="text" data-field="expirationDate" id="cc-expiration"
maxlength="5" placeholder="MM/YY" required>
<!-- CVV / Código de segurança -->
<input type="text" data-field="securitycode" id="cc-cvv"
maxlength="4" required>
<button type="button" id="pay-btn">Pay Now</button>
</div>
Valores obrigatórios de data-field:
data-field | Campo | Observações |
|---|---|---|
cardholder | Nome completo no cartão | Como impresso no cartão |
pan | Número do cartão | 13–19 dígitos |
expirationDate | Validade | Formato: MM/YY |
securitycode | CVV/CVC | 3 dígitos (Visa/MC/Discover) ou 4 dígitos (Amex) |
Inicializando o Tokenizador
Crie uma instância do InyoTokenizer quando o DOM estiver pronto. A configuração do 3DS depende do método de integração que você escolher — URL Redirect ou PostMessage:
Exemplo — URL Redirect (tradicional)
O provedor de 3DS redireciona o navegador para o seu successUrl ou failUrl após a autenticação. Ideal para aplicações renderizadas no servidor e fluxos de checkout de página inteira.
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();
});
});
Exemplo — PostMessage (SPA / embutido)
O resultado do 3DS é entregue via API postMessage do navegador para a janela pai. Ideal para single-page apps e experiências de checkout em iframe/modal — sem necessidade de navegação 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();
});
});
Ao usar
enablePostMessage: true, você não fornecesuccessUrlnemfailUrl. Em vez disso, você escuta eventosmessagena janela pai após abrir oredirectAcsUrlem um iframe. Veja Tratando o 3D Secure para o guia de implementação completo.
Parâmetros de Configuração
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
targetId | string | Sim | Seletor CSS do contêiner que contém os inputs data-field (ex.: '#payment-form') |
publicKey | string | Sim | Sua chave pública de estabelecimento, fornecida pela Inyo |
successCallback | function | Sim | Chamado quando a tokenização é bem-sucedida |
errorCallback | function | Sim | Chamado quando a tokenização falha |
storeLaterUse | boolean | Não | false (padrão) = token de uso único; true = token recorrente/armazenado |
threeDSData | object | Não | Configuração do 3D Secure (veja abaixo) |
Configuração do 3DS (threeDSData)
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
enable | boolean | Sim | Se deve solicitar autenticação 3DS |
successUrl | string | Apenas modo Redirect. URL para a qual o provedor de 3DS redireciona em caso de sucesso | |
failUrl | string | Apenas modo Redirect. URL para a qual o provedor de 3DS redireciona em caso de falha | |
enablePostMessage | boolean | Apenas modo PostMessage. Defina true para receber os resultados do 3DS via window.postMessage em vez de redirecionamento de URL |
Escolha um modo:
- Redirect: Defina
successUrl+failUrl. Não definaenablePostMessage.- PostMessage: Defina
enablePostMessage: true. Não definasuccessUrl/failUrl.Habilitar o 3DS não garante que um challenge ocorrerá — o banco emissor decide com base na sua avaliação de risco. Veja Tratando o 3D Secure para o fluxo completo e exemplos de código para ambos os modos.
Tratando os Callbacks
Callback de Sucesso
function handleSuccess(response) {
console.log('Tokenization response:', response);
if (response.reasonCode === 'WAITING_TRANSACTION') {
// Token criado — extraia-o
const token = response.additionalData.token;
const lastFour = response.additionalData.lastFour;
console.log(`Card ending in ${lastFour}, token: ${token}`);
// Envie o token para o seu servidor backend
// Seu backend chamará POST /v2/payment com este token
submitPaymentToBackend(token);
} else {
console.warn('Unexpected response step:', response.step);
}
}
Campos da resposta de sucesso:
| Campo | Descrição |
|---|---|
reasonCode | "WAITING_TRANSACTION" quando o token está pronto |
additionalData.token | O UUID do token do cartão — use como cardTokenId nas requisições de pagamento |
additionalData.lastFour | Últimos 4 dígitos do número do cartão (para exibição) |
Callback de Erro
function handleError(response) {
console.error('Tokenization error:', response);
// Limpa estados de erro anteriores
document.querySelectorAll('#cc-number, #cc-expiration, #cc-cvv')
.forEach(el => el.classList.remove('is-invalid'));
// Destaca o campo específico que falhou
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 erro:
| Código | Descrição |
|---|---|
INVALID_PAN | O número do cartão é inválido (falhou na verificação de Luhn ou bandeira não suportada) |
INVALID_EXPIRY_DATE | A data de validade é inválida ou o cartão está expirado |
INVALID_CVV | O código de segurança é inválido |
Tokens de Uso Único vs. Recorrentes
| Tipo de Token | storeLaterUse | Uso |
|---|---|---|
| Uso único | false | Apenas uma transação — não pode ser reutilizado |
| Recorrente | true | Pode ser armazenado e reutilizado para cobranças futuras |
Regras:
- Você não deve armazenar tokens de uso único após o uso
- Você pode armazenar tokens recorrentes no lado do servidor para transações futuras
- Você nunca deve armazenar dados brutos do cartão (PAN, CVV, validade), independentemente do tipo de token
- Ao usar um token recorrente armazenado para um pagamento subsequente, passe o
previousPaymentId(da autorização original) junto com ocardTokenId
Exemplos Completos
Exemplo A — URL Redirect (checkout renderizado no 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;
// Envie o token para o seu backend → POST /v2/payment
// Se a API retornar status: "CHALLENGE", seu backend
// redireciona o navegador para redirectAcsUrl.
// Após a autenticação, o provedor de 3DS redireciona para
// o seu successUrl ou 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>
Exemplo B — PostMessage (SPA / checkout embutido)
<!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 do 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 // ← Sem necessidade de 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');
}
});
// Escuta os resultados do 3DS via postMessage
window.addEventListener('message', handle3DSResult);
});
async function handleTokenSuccess(response) {
if (response.reasonCode === 'WAITING_TRANSACTION') {
const token = response.additionalData.token;
// Chame seu backend para criar o pagamento
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) {
// Abre o challenge 3DS no overlay com 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) {
// Segurança: valide a origem da mensagem
if (!event.origin.includes('simpleps.com')) return;
const data = event.data;
// Fecha o overlay do 3DS
document.getElementById('threeds-overlay').style.display = 'none';
document.getElementById('threeds-iframe').src = '';
if (data.approved === true || data.approved === 'true') {
// Sempre verifique no seu backend antes de concluir o 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 Passos
Com um token em mãos, prossiga para Autorizando um Pagamento com Cartão para criar a transação via POST /v2/payment.
