Skip to main content

O que é o Keranos?

O Keranos é a biblioteca oficial do Autorizou para criptografia de dados de cartão de crédito no frontend. Ele garante que informações sensíveis nunca trafeguem em texto puro pela rede, atendendo aos requisitos de segurança PCI DSS.

Principais Funcionalidades

Criptografia de Cartões

  • Criptografia AES-256-CBC no browser
  • Suporte a todas as principais bandeiras
  • Detecção automática de bandeira do cartão
  • Validação de dados antes da criptografia

3D Secure 2.0

  • Suporte completo ao protocolo 3DS2
  • Interface de challenge integrada
  • Callbacks configuráveis para sucesso/falha

Google Pay

  • Integração nativa com Google Pay
  • Criação de botões personalizados
  • Suporte a transações tokenizadas

Instalação

Via CDN

<!-- Incluir na tag <head> ou antes do </body> -->
<script src="https://js.autorizou.cloud/keranos/latest/embed.js"></script>

Via NPM (para projetos React/Next.js)

npm install @autorizou/keranos
# ou
yarn add @autorizou/keranos
Importante: A versão NPM ainda está em desenvolvimento. Atualmente, recomendamos usar a versão CDN para garantir compatibilidade.

Configuração

Variáveis de Ambiente

Para usar o Keranos, você precisa configurar a chave de criptografia:
# .env ou .env.local
NEXT_PUBLIC_AES_KEY=your_aes_key_here
Chave AES: Entre em contato com o suporte do Autorizou para obter sua chave de criptografia específica para cada ambiente (sandbox/production).

Uso Básico

1. Incluir o Script

<!DOCTYPE html>
<html>
<head>
  <title>Meu E-commerce</title>
  <!-- Incluir Keranos -->
  <script src="https://js.autorizou.cloud/keranos/latest/embed.js"></script>
</head>
<body>
  <!-- Seu conteúdo -->
</body>
</html>

2. Criptografar Dados do Cartão

// Preparar dados do cartão
const cardData = {
  number: '4111111111111111',
  holder: 'JOAO SILVA',
  expMonth: '12',
  expYear: '2030',
  securityCode: '123'
};

// Preparar dados do portador
const cardHolder = {
  name: 'João Silva',
  identification: {
    type: 'CPF',
    number: '12345678900'
  }
};

// Criptografar usando callbacks
await window.Autorizou.encryptCard(
  { cardData, cardHolder },
  {
    onSuccess: (response) => {
      console.log('Token criptografado:', response.token);
      // Enviar response.token para sua API
    },
    onError: (error) => {
      console.error('Erro na criptografia:', error.message);
    }
  }
);

3. Enviar para a API

const response = await fetch('https://zeus-sandbox.autorizou.dev/api/v1/cards', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ...',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    customer_id: 'cus_abc123',
    encrypted: result.token
  })
});

const card = await response.json();
console.log('Cartão salvo:', card);

Integração React/Next.js

Carregar Keranos no Layout

// app/layout.js ou pages/_app.js
import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html lang="pt-BR">
      <head>
        {/* Carregar Keranos antes de qualquer script */}
        <Script
          src="https://js.autorizou.cloud/keranos/latest/embed.js"
          strategy="beforeInteractive"
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

Componente de Formulário

'use client';

import { useState } from 'react';

export default function CardForm({ customerId, onSuccess, onError }) {
  const [formData, setFormData] = useState({
    number: '',
    holder: '',
    expMonth: '',
    expYear: '',
    securityCode: ''
  });
  const [loading, setLoading] = useState(false);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);

    try {
      // 1. Verificar se Keranos está carregado
      if (!window.Autorizou?.encryptCard) {
        throw new Error('Keranos não está carregado');
      }

      // 2. Criptografar dados usando callbacks
      await window.Autorizou.encryptCard(
        { cardData: formData },
        {
          onSuccess: async (encryptResult) => {
            try {
              // 3. Enviar para a API
              const response = await fetch('/api/cards', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                  customer_id: customerId,
                  encrypted: encryptResult.token
                })
              });

              if (!response.ok) {
                const error = await response.json();
                throw new Error(error.message || 'Erro ao salvar cartão');
              }

              const card = await response.json();
              onSuccess?.(card);

              // Limpar formulário
              setFormData({
                number: '',
                holder: '',
                expMonth: '',
                expYear: '',
                securityCode: ''
              });
            } catch (err) {
              console.error('Erro ao salvar:', err);
              onError?.(err);
            } finally {
              setLoading(false);
            }
          },
          onError: (error) => {
            console.error('Erro na criptografia:', error);
            onError?.(error);
            setLoading(false);
          }
        }
      );

    } catch (err) {
      console.error('Erro:', err);
      onError?.(err);
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div>
        <label htmlFor="number">Número do Cartão</label>
        <input
          id="number"
          name="number"
          type="text"
          value={formData.number}
          onChange={handleChange}
          placeholder="0000 0000 0000 0000"
          maxLength={19}
          required
        />
      </div>

      <div>
        <label htmlFor="holder">Nome no Cartão</label>
        <input
          id="holder"
          name="holder"
          type="text"
          value={formData.holder}
          onChange={handleChange}
          placeholder="NOME COMPLETO"
          style={{ textTransform: 'uppercase' }}
          required
        />
      </div>

      <div className="grid grid-cols-2 gap-4">
        <div>
          <label htmlFor="expMonth">Mês</label>
          <input
            id="expMonth"
            name="expMonth"
            type="text"
            value={formData.expMonth}
            onChange={handleChange}
            placeholder="MM"
            maxLength={2}
            required
          />
        </div>

        <div>
          <label htmlFor="expYear">Ano</label>
          <input
            id="expYear"
            name="expYear"
            type="text"
            value={formData.expYear}
            onChange={handleChange}
            placeholder="AAAA"
            maxLength={4}
            required
          />
        </div>
      </div>

      <div>
        <label htmlFor="securityCode">CVV</label>
        <input
          id="securityCode"
          name="securityCode"
          type="text"
          value={formData.securityCode}
          onChange={handleChange}
          placeholder="123"
          maxLength={4}
          required
        />
      </div>

      <button
        type="submit"
        disabled={loading}
        className="w-full bg-primary text-white py-2 rounded"
      >
        {loading ? 'Processando...' : 'Salvar Cartão'}
      </button>
    </form>
  );
}

API Route (Next.js)

// app/api/cards/route.js
import { NextResponse } from 'next/server';

export async function POST(request) {
  try {
    const body = await request.json();
    const { customer_id, encrypted } = body;

    // Validar dados
    if (!customer_id || !encrypted) {
      return NextResponse.json(
        { message: 'customer_id e encrypted são obrigatórios' },
        { status: 400 }
      );
    }

    // Enviar para Zeus API
    const response = await fetch('https://zeus-sandbox.autorizou.dev/api/v1/cards', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.AUTORIZOU_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ customer_id, encrypted })
    });

    const data = await response.json();

    if (!response.ok) {
      return NextResponse.json(data, { status: response.status });
    }

    return NextResponse.json(data);

  } catch (error) {
    console.error('Erro na API de cartões:', error);
    return NextResponse.json(
      { message: 'Erro interno do servidor' },
      { status: 500 }
    );
  }
}

Validações e Boas Práticas

Validação de Número de Cartão

function validateCardNumber(number) {
  // Remover espaços e caracteres não numéricos
  const cleaned = number.replace(/\D/g, '');

  // Verificar tamanho (13-19 dígitos)
  if (cleaned.length < 13 || cleaned.length > 19) {
    return false;
  }

  // Algoritmo de Luhn
  let sum = 0;
  let isEven = false;

  for (let i = cleaned.length - 1; i >= 0; i--) {
    let digit = parseInt(cleaned.charAt(i), 10);

    if (isEven) {
      digit *= 2;
      if (digit > 9) {
        digit -= 9;
      }
    }

    sum += digit;
    isEven = !isEven;
  }

  return sum % 10 === 0;
}

Validação de Data de Expiração

function validateExpiration(month, year) {
  const now = new Date();
  const currentYear = now.getFullYear();
  const currentMonth = now.getMonth() + 1;

  const expMonth = parseInt(month, 10);
  const expYear = parseInt(year, 10);

  // Validar mês
  if (expMonth < 1 || expMonth > 12) {
    return false;
  }

  // Validar ano (aceitar 2 ou 4 dígitos)
  const fullYear = expYear < 100 ? 2000 + expYear : expYear;

  // Verificar se não está expirado
  if (fullYear < currentYear) {
    return false;
  }

  if (fullYear === currentYear && expMonth < currentMonth) {
    return false;
  }

  return true;
}

Detecção de Bandeira

function detectCardBrand(number) {
  const cleaned = number.replace(/\D/g, '');

  const patterns = {
    visa: /^4/,
    mastercard: /^(5[1-5]|2[2-7])/,
    amex: /^3[47]/,
    elo: /^(4011|4312|4389|4514|4576|5041|5066|5067|6277|6362|6363|6504|6505|6516)/,
    diners: /^(36|38|30[0-5])/,
    discover: /^6(?:011|5)/,
    jcb: /^35/,
    hipercard: /^606282/
  };

  for (const [brand, pattern] of Object.entries(patterns)) {
    if (pattern.test(cleaned)) {
      return brand;
    }
  }

  return 'unknown';
}

Tratamento de Erros

Erros Comuns

try {
  const result = await window.Autorizou.encryptCard({ cardData });

  if (result.errors) {
    // Erros de validação do Keranos
    switch (result.errors.message[0]) {
      case 'Número de cartão inválido':
        alert('Por favor, verifique o número do cartão');
        break;
      case 'Data de expiração inválida':
        alert('Cartão expirado ou data inválida');
        break;
      case 'CVV inválido':
        alert('CVV deve ter 3 ou 4 dígitos');
        break;
      default:
        alert('Erro na validação do cartão');
    }
    return;
  }

  // Continuar com o envio...

} catch (error) {
  // Erro de rede ou exceção
  console.error('Erro:', error);
  alert('Erro ao processar cartão. Tente novamente.');
}

Segurança

Boas Práticas

  1. Nunca armazene dados de cartão no localStorage ou sessionStorage
  2. Sempre use HTTPS em produção
  3. Valide dados no frontend antes de criptografar
  4. Implemente rate limiting no backend
  5. Use CSP (Content Security Policy) adequado

Content Security Policy

<meta http-equiv="Content-Security-Policy" content="
  default-src 'self';
  script-src 'self' https://js.autorizou.cloud;
  connect-src 'self' https://zeus.autorizou.cloud;
  style-src 'self' 'unsafe-inline';
">

Fluxo Completo de Integração

Passo a Passo

  1. Incluir o Keranos no projeto (CDN ou NPM)
  2. Coletar dados do cartão no formulário
  3. Validar dados localmente antes de enviar
  4. Criptografar usando window.Autorizou.encryptCard()
  5. Enviar token criptografado para sua API backend
  6. Backend encaminha para Zeus API
  7. Processar resposta e atualizar interface

Diagrama de Fluxo

[Browser] → Keranos → [Token Criptografado] → [Seu Backend] → Zeus API

                                              [Cartão Tokenizado]

Troubleshooting

Keranos não está carregado

Problema: window.Autorizou is undefined Solução:
// Verificar se Keranos está disponível
if (typeof window.Autorizou === 'undefined') {
  console.error('Keranos não foi carregado. Verifique o script.');
  // Tentar recarregar ou mostrar mensagem ao usuário
}

Erro de CORS

Problema: Erro de Cross-Origin ao chamar a API Solução:
  • Sempre envie o token criptografado do seu backend para o Zeus
  • Nunca faça chamadas diretas do browser para zeus.autorizou.cloud

Token inválido

Problema: Backend retorna “invalid_encrypted_card” Possíveis causas:
  • Chave AES incorreta ou não configurada
  • Dados do cartão em formato incorreto
  • Token corrompido durante transmissão
Solução:
// Verificar se a criptografia foi bem-sucedida
await window.Autorizou.encryptCard(
  { cardData },
  {
    onSuccess: (response) => {
      console.log('Token válido:', response.token);
      // Verificar se token não está vazio
      if (!response.token) {
        console.error('Token vazio!');
      }
    },
    onError: (error) => {
      console.error('Erro na criptografia:', error);
    }
  }
);

Perguntas Frequentes

Sim, a chave AES deve estar disponível no frontend para que o Keranos possa criptografar os dados. Use variáveis de ambiente (NEXT_PUBLIC_AES_KEY) e nunca commite a chave no código.A chave é usada apenas para criptografar no browser. A descriptografia acontece apenas no backend seguro do Zeus.
Sim, o Keranos é compatível com:
  • React / Next.js
  • Vue / Nuxt.js
  • Angular
  • Svelte
  • Vanilla JavaScript
Basta incluir o script via CDN e usar window.Autorizou.
Sim, você deve usar chaves AES diferentes para cada ambiente:
  • NEXT_PUBLIC_AES_KEY_DEV para desenvolvimento
  • NEXT_PUBLIC_AES_KEY_STAGING para staging
  • NEXT_PUBLIC_AES_KEY_PROD para produção
Recomendado mas não obrigatório. Validar localmente melhora a UX:
  • Algoritmo de Luhn para número do cartão
  • Validação de data de expiração
  • Formato do CVV (3-4 dígitos)
O Keranos e o Zeus também fazem validações, mas feedback imediato é melhor.
Sem JavaScript, o Keranos não funcionará. Considere:
  • Mostrar mensagem informativa
  • Oferecer método de pagamento alternativo
  • A maioria dos usuários tem JS habilitado

Boas Práticas de Segurança

✅ Faça

  • Use HTTPS em produção sempre
  • Implemente rate limiting no backend
  • Valide dados no frontend E backend
  • Use CSP (Content Security Policy)
  • Limpe o formulário após sucesso
  • Implemente timeout nas requisições

❌ Não Faça

  • Armazenar dados de cartão em localStorage/sessionStorage
  • Enviar dados de cartão em texto puro
  • Logar dados sensíveis no console em produção
  • Reutilizar tokens criptografados
  • Fazer chamadas diretas do browser para Zeus API

Exemplos Avançados

Formulário com Máscara

// Aplicar máscara no número do cartão
function formatCardNumber(value) {
  return value
    .replace(/\s/g, '')
    .replace(/(\d{4})/g, '$1 ')
    .trim();
}

<input
  type="text"
  value={cardNumber}
  onChange={(e) => {
    const formatted = formatCardNumber(e.target.value);
    setCardNumber(formatted);
  }}
  maxLength={19}
  placeholder="0000 0000 0000 0000"
/>

Detecção Automática de Bandeira

function detectBrand(number) {
  const cleaned = number.replace(/\s/g, '');

  if (/^4/.test(cleaned)) return 'visa';
  if (/^5[1-5]/.test(cleaned)) return 'mastercard';
  if (/^3[47]/.test(cleaned)) return 'amex';
  if (/^6(?:011|5)/.test(cleaned)) return 'discover';
  if (/^3(?:0[0-5]|[68])/.test(cleaned)) return 'diners';
  if (/^35/.test(cleaned)) return 'jcb';
  if (/^(4011|4312|4389|4514|4576|5041|5066|5067|636368)/.test(cleaned)) return 'elo';
  if (/^606282/.test(cleaned)) return 'hipercard';

  return 'unknown';
}

// Uso
const [brand, setBrand] = useState('');

useEffect(() => {
  if (cardNumber.length >= 4) {
    setBrand(detectBrand(cardNumber));
  }
}, [cardNumber]);

Loading States

const [loadingStates, setLoadingStates] = useState({
  encrypting: false,
  saving: false
});

const handleSubmit = async (e) => {
  e.preventDefault();

  setLoadingStates({ encrypting: true, saving: false });

  await window.Autorizou.encryptCard(
    { cardData },
    {
      onSuccess: async (encryptResult) => {
        setLoadingStates({ encrypting: false, saving: true });

        try {
          const response = await fetch('/api/cards', {
            method: 'POST',
            body: JSON.stringify({
              customer_id: customerId,
              encrypted: encryptResult.token
            })
          });

          if (!response.ok) throw new Error('Erro ao salvar');

          // Sucesso!
          setLoadingStates({ encrypting: false, saving: false });
        } catch (err) {
          setLoadingStates({ encrypting: false, saving: false });
          console.error(err);
        }
      },
      onError: () => {
        setLoadingStates({ encrypting: false, saving: false });
      }
    }
  );
};

Suporte e Recursos

Dúvidas? Entre em contato com nosso time de suporte técnico. Estamos aqui para ajudar!