Manual de Integração 3DS

Um guia completo para integrar o protocolo de segurança que protege suas vendas online, aumenta a aprovação e elimina chargebacks por fraude.

Manual de Integração 3DS

Índice

  1. Visão Geral
  2. O que é 3DS?
  3. Como Funciona o 3DS
  4. Começando: Configuração Inicial
  5. Guia de Integração Passo a Passo
  6. Objeto Order
  7. Ambientes de Teste
  8. Tratamento de Erros
  9. Solução de Problemas

Visão Geral

Para que serve esta documentação?

Esta documentação é seu guia completo para integrar a autenticação 3DS da Stone usando nossa biblioteca 3ds-nx-js. Se você é um desenvolvedor que precisa implementar 3DS em sua aplicação, você está no lugar certo.

O que você vai aprender?

  • Conceitos fundamentais do 3DS de forma simples e clara
  • Implementação prática com exemplos de código prontos para usar
  • Como testar sua integração com cartões de teste específicos
  • Tratamento de cenários tanto de fluxo simples quanto de desafio
  • Boas práticas para uma implementação segura

O que é 3DS?

O 3D Secure (3DS) é um protocolo de segurança que adiciona uma camada extra de autenticação para transações com cartão de crédito e débito online. Pense nele como uma "segunda verificação" que confirma se é realmente o portador do cartão quem está fazendo a compra.

Por que usar 3DS?

Proteção contra fraudes: Reduz significativamente transações fraudulentas

Liability Shift: A responsabilidade por chargebacks passa para o emissor do cartão

Aprovação aumentada: Emissores tendem a aprovar mais transações autenticadas


Como Funciona o 3DS

O processo de autenticação 3DS pode seguir dois caminhos principais:

🚀 Fluxo Frictionless (Sem Atrito)

O que acontece: O emissor aprova a transação automaticamente baseado no perfil de risco, sem interação do usuário.

🔐 Fluxo Challenge (Com Desafio)

O que acontece: O emissor solicita que o cliente confirme sua identidade através de SMS, app do banco, ou outro método.


Começando: Configuração Inicial

Pré-requisitos

  • Secret Key da Stone (todos os clientes Stone possuem)
  • Conhecimento básico em JavaScript
  • Ambiente backend capaz de fazer requisições HTTPS

1. Incluir a biblioteca no HTML

URLs das bibliotecas por ambiente

AmbienteURL da Biblioteca
Sandboxhttps://3ds-nx-js.stone.com.br/test/v2/3ds2.min.js
Produçãohttps://3ds-nx-js.stone.com.br/live/v2/3ds2.min.js

Exemplo de como incluir a biblioteca no seu HTML:

<!-- Para o ambiente de sandbox: -->
<script src="https://3ds-nx-js.stone.com.br/test/v2/3ds2.min.js"></script>

<!-- Para o ambiente de produção: -->
<script src="https://3ds-nx-js.stone.com.br/live/v2/3ds2.min.js"></script>

2. Estrutura básica do HTML

A biblioteca requer alguns containers no HTML da sua aplicação para funcionar corretamente. Dentro deles ocorrerá o processo de 3DS Method e Challenge. Recomendamos que sejam elementos do tipo <div> para facilitar a manipulação. Dentro desses elementos a biblioteca irá inserir os iframes e forms necessários para o processo de autenticação. Portanto, sua aplicação não deve interferir na estrutura interna desses containers.

Um exemplo de como preparar os containers onde o 3DS vai operar:

<div id="myApplication">
  <!-- Container invisível para o 3DS Method -->
  <div id="tdsMethodContainer" style="display: none;"></div>
  
  <!-- Container para o Challenge -->
  <div id="challengeContainer"></div>
  
  <!-- Botão para iniciar o processo de autenticação 3DS que será utilizado nos exemplos a seguir -->
  <button id="submitButton">Finalizar Compra</button>
</div>

3. Geração do token 3DS

⚠️ IMPORTANTE: O token 3DS deve sempre ser gerado no seu backend por questões de segurança, dado que para gerá-lo é necessário utilizar a secret key da Stone, que deverá ser mantida em segredo e nunca exposta no frontend.

URLs base dos ambientes

AmbienteURL Base
Sandboxhttps://3ds-sdx.stone.com.br/v2
Produçãohttps://3ds.stone.com.br/v2

Como gerar o token 3DS no backend

Recomendamos criar um endpoint seguro em seu backend que seja responsável pela geração do token. Dessa forma sua aplicação pode obter o token de forma segura e evitar expor a secret key no frontend.

O token 3DS é obtido fazendo uma requisição para nosso endpoint GET /tds-token utilizando Basic Access Authentication com a secret key encodada em Base64 da seguinte forma:

Authorization: Basic <base64(secret_key:)>

Veja o exemplo abaixo da implementação de uma função em Node.js responsável por gerar o token 3DS:

// Exemplo em Node.js com a biblioteca axios
const axios = require('axios');

async function generateTDSToken(api, secretKey) {
  try {
    const response = await axios.get(`${api}/tds-token`, {
      headers: {
        'Authorization': 'Basic ' + Buffer.from(`${secretKey}:`).toString('base64'),
      },
    });
    
    return response.data.tds_token;
  } catch (error) {
    throw new Error(`Failed to generate token: ${error.response?.data?.message || error.message}`);
  }
}

O código acima faz uma requisição GET para o endpoint /tds-token, passando a secret key codificada em Base64 no cabeçalho de autorização. A resposta conterá o token 3DS necessário para iniciar o processo de autenticação.

E para que seu frontend possa obter esse token, você pode criar um endpoint em seu backend que retorne o token gerado:

// Exemplo em Node.js com a biblioteca Express.js

const TDS_API_URLS = {
  sandbox: 'https://3ds-sdx.stone.com.br/v2',
  production: 'https://3ds.stone.com.br/v2'
};

app.get('/tds-token', async (req, res) => {
  try {
    const environment = process.env.NODE_ENV === 'production' ? 'production' : 'sandbox';
    const api = TDS_API_URLS[environment];
    
    if (!api) {
      throw new Error(`Invalid environment: ${environment}`);
    }
    
    const token = await generateTDSToken(api, process.env.STONE_SECRET_KEY);
    res.json({ token });
  } catch (error) {
    console.error('Error generating 3DS token:', error.message);
    res.status(500).json({ error: 'Failed to generate token' });
  }
});

O código acima cria um endpoint /generate-3ds-token que, ao ser chamado, gera e retorna o token 3DS. O ambiente (sandbox ou produção) é determinado pela variável de ambiente NODE_ENV. O token é gerado utilizando a função generateTDSToken do exemplo anterior e a secret key da Stone que está sendo obtida da variável de ambiente STONE_SECRET_KEY.


Guia de Integração Passo a Passo

Passo 1: Obter o token 3DS no frontend

O seu frontend deve solicitar ao seu backend o token 3DS antes de iniciar o processo de autenticação 3DS. Recomendamos que o token seja obtido sempre antes de cada transação, para garantir que você esteja usando o token mais recente e válido, evitando problemas de expiração.

Abaixo está um exemplo de uma função em JavaScript que faz uma requisição para o endpoint POST /generate-3ds-token definido nos exemplos anteriores:

async function getToken() {
  const response = await fetch('/generate-3ds-token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    }
  });
  
  const data = await response.json();
  return data.token;
}

Passo 2: Preparar os dados do pedido

A biblioteca espera receber um objeto com os dados do pedido (order) que será processado. Esses dados são similares aos dados que você enviaria para criar um pedido na Stone, mas com algumas diferenças, consulte a seção Objeto Order para mais detalhes.

Para o ambiente de teste, é possível utilizar uma série de cartões de teste que simulam diferentes cenários de autenticação 3DS. Basta informar o número do cartão no campo number do objeto card dentro do pedido. Para uma lista completa de cartões de teste, consulte a seção Cartões de Teste Específicos para 3DS.

Exemplo da estrutura de dados do pedido:

const orderData = {
  "payments": [
    {
      "payment_method": "credit_card",
      "credit_card": {
        "card": {
          "number": "3000100811112072", // Cartão de teste que gera challenge manual
          "holder_name": "JOAO SILVA",
          "exp_month": 12,
          "exp_year": 2028,
          "billing_address": {
            "country": "BR",
            "state": "SP",
            "city": "São Paulo",
            "zip_code": "01234567",
            "line_1": "123, Rua das Flores, Centro",
            "line_2": "Apartamento 45"
          }
        },
      },
      "amount": 20000
    }
  ],
  "customer": {
    "name": "João Silva",
    "email": "[email protected]",
    "code": "CUST-001",
    "document": "12345678901",
    "phones": {
      "home_phone": {
        "country_code": "55",
        "area_code": "11",
        "number": "987654321"
      },
      "mobile_phone": {
        "country_code": "55",
        "area_code": "11",
        "number": "123456789"
      }
    }
  },
  "items": [
    {
      "amount": 15000,
      "description": "Smartphone Samsung Galaxy",
      "quantity": 1,
      "code": "ITEM-001"
    },
    {
      "amount": 5000,
      "description": "Capa Protetora",
      "quantity": 2,
      "code": "ITEM-002"
    }
  ],
  "shipping": {
    "recipient_name": "João Silva",
    "address": {
      "country": "BR",
      "state": "SP",
      "city": "São Paulo",
      "zip_code": "01234567",
      "line_1": "123, Rua das Flores, Centro",
      "line_2": "Apartamento 45"
    }
  }
};

Passo 3: Iniciando o processo de autenticação 3DS

Assim que o cliente finalizar o preenchimento dos dados necessários para o pedido, você pode iniciar o processo de autenticação 3DS. A biblioteca 3ds-nx-js fornece uma função chamada TDS.init que gerencia todo o fluxo de autenticação com apenas uma chamada. Para isso você deve passar o token 3DS, os containers onde o 3DS Method e o Challenge serão exibidos, e os dados do pedido.

Abaixo está um exemplo de como implementar essa chamada:

async function process3DS() {
  // Desabilita o botão para evitar duplo clique
  document.getElementById('submitButton').disabled = true;
  
  try {
    // 1. Obter o token do backend
    const token = await getToken();
    
    // 2. Referências dos containers
    const tdsMethodContainer = document.getElementById('tdsMethodContainer');
    const challengeContainer = document.getElementById('challengeContainer');
    
    // 3. Executar o fluxo 3DS
    const response = await window.TDS.init({
      token: token,
      tds_method_container_element: tdsMethodContainer,
      challenge_container_element: challengeContainer,
      use_default_challenge_iframe_style: true, // Usar estilo padrão
      challenge_window_size: '03' // Tamanho médio da janela
    }, orderData);
    
    // 4. Processar resposta
    console.log('3DS Response:', response);
    alert('Transação processada com sucesso!');
    
  } catch (error) {
    console.error('Erro no 3DS:', error);
    alert('Erro ao processar pagamento: ' + error.message);
  } finally {
    // Reabilitar o botão
    document.getElementById('submitButton').disabled = false;
  }
}

// Vincular ao botão
document.getElementById('submitButton').addEventListener('click', process3DS);

Passo 4: CSS para o challenge

O challenge será renderizado em um iframe dentro do container informado em challenge_container_element. Se você definir use_default_challenge_iframe_style: true na chamada da função TDS.init, a biblioteca aplicará um estilo padrão ao iframe do challenge, que pode funcionar bem para alguns casos, mas pode não ser o ideal para todos os layouts. Por esse motivo, recomendamos que você personalize o estilo do iframe para melhor se adequar ao design da sua aplicação.

Para fazer isso, você deve definir use_default_challenge_iframe_style: false e aplicar seu próprio CSS ao iframe do challenge. Sempre que houver uma challenge para ser exibida, a biblioteca irá inserir um iframe dentro do container challenge_container_element. Você pode personalizar o estilo desse iframe para que ele se encaixe melhor no design da sua aplicação.

Veja um exemplo de como fazer isso:

/* Estilo personalizado para o challenge iframe */
#challengeContainer iframe {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 60%;
  max-width: 500px;
  height: 600px;
  border: 2px solid #ddd;
  border-radius: 8px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  background: white;
  z-index: 9999;
}

Passo 5: Definindo o challenge_window_size

O parâmetro challenge_window_size define o tamanho da janela do challenge e impacta diretamente na experiência do usuário. Por esse motivo é importante escolher um tamanho adequado para o seu público-alvo.

Recomendamos que você escolha o tamanho de janela baseado no dispositivo que o seu cliente estiver utilizando no momento da autenticação. Abaixo estão os tamanhos recomendados:

CódigoDimensõesRecomendado para
'01'250x400pxMobile portrait
'02'390x400pxMobile landscape
'03'500x600pxDesktop (padrão)
'04'600x400pxDesktop wide
'05'FullscreenMáxima compatibilidade

Passo 6: Finalizando o pedido

Após o processo de autenticação 3DS, a função TDS.init retornará um array de objetos, um para cada cartão autenticado com o seguinte formato:

[
  {
    tds_server_trans_id: "<UUID>",
    trans_status: "<Y|A|N|C|U|R|I>",
    authenticated_card: "<card number>",
    challenge_canceled: <true|false>,
  }
]
Nome do campoDescriçãoTipo
tds_server_trans_idID da transação autenticadastring
trans_statusStatus da transação 3DSstring
authenticated_cardNúmero do cartão autenticado (mascarado)string
challenge_canceledIndica se o challenge foi cancelado pelo usuárioboolean
Status (trans_status)DescriçãoResponsabilidade em caso de fraude
YTransação autenticadaEmissor
ATentativa de autenticaçãoLojista ou Emissor (depende da resposta do emissor)
NTransação não autenticadaLojista
CSolicitação de desafioLojista ou Emissor (depende da resposta do emissor)
UAutenticação indisponívelLojista
RAutenticação negada pelo emissorLojista
IApenas informaçãoLojista

Vale destacar que as transações que possuem Liability Shift são as transações com retorno "Y", e eventualmente os status "A" e "C", a depender da resposta do emissor.
Essas são as transações que quando autenticadas pelo emissor do cartão de crédito, ele se responsabiliza por eventuais fraudes.

Após receber a resposta, se desejar criar o pedido (order), utilize o tdsServerTransID retornado. No contrato de criação de pedidos, o campo correspondente ao tdsServerTransID é o ds_transaction_id, mais detalhes na seção Transações Autenticadas dessa documentação.


Objeto Order

O objeto Order representa os dados do pedido que será processado no fluxo de autenticação 3DS. Abaixo estão as especificações detalhadas de cada campo e tipo necessário.

Order

Nome do campoTipoObrigatório?
customerCustomerSim
itemsItem[]Não
shippingShippingNão
paymentsPayment[]Sim

Customer

Nome do campoTipoObrigatório?
namestringNão
emailstringSim
codestringNão
documentstringNão
phonesPhonesNão

Phones

Nome do campoTipoObrigatório?
home_phonePhoneNão
mobile_phonePhoneNão

Phone

Nome do campoTipoObrigatório?
country_codestringSim
area_codestringSim
numberstringSim

Item

Nome do campoTipoObrigatório?
amountnumberSim
descriptionstringSim
quantitynumberSim
codestringSim

Shipping

Nome do campoTipoObrigatório?
recipient_namestringSim
electronic_deliverybooleanNão
addressAddressÉ obrigatório apenas se electronic_delivery for false

Payment

Nome do campoTipoObrigatório?
payment_methodstringNão
credit_cardCreditCardSim
amountnumberSim
purposestringNão
recurrenceRecurrenceNão

CreditCard

Nome do campoTipoObrigatório?
cardCardSim

Recurrence

Nome do campoTipoObrigatório?
deadlinestringSim
frequency_daysnumberSim

Card

Nome do campoTipoObrigatório?
numberstringSim
holder_namestringSim
exp_monthnumberSim
exp_yearnumberSim
billing_addressAddressSim

Address

Nome do campoTipoObrigatório?Formato
countrystringSimCódigo do país no formato ISO 3166-1 alpha-2 (ex.: BR, US, FR)
statestringSim-
citystringSim-
zip_codestringSim-
line_1stringSimnumber, street, district
line_2stringSim-

Ambientes de Teste

Cartões de Teste Específicos para 3DS

Use estes cartões em ambiente sandbox para testar diferentes cenários:

Cartões 3DS 2.2

Número do CartãoVersão 3DSComportamentoDescrição
40001005111120032.2Frictionless SuccessAprovado automaticamente sem challenge
50001004111122032.23DS Method TimeoutTesta timeout do 3DS Method
60001006111121032.2Frictionless sem 3DS MethodAprovado sem executar 3DS Method
30001008111120722.2Manual ChallengeExibe challenge manual para o usuário
70001009111120702.2Auto Challenge SuccessChallenge automático - aprovado
30001010111120712.2Auto Challenge FailChallenge automático - negado

Exemplo de Teste Completo

// Função para testar diferentes cenários
async function testScenario(cardNumber, description) {
  console.log(`🧪 Testando: ${description}`);
  
  const testOrder = {
    ...orderData,
    payments: [{
      ...orderData.payments[0],
      credit_card: {
        ...orderData.payments[0].credit_card,
        card: {
          ...orderData.payments[0].credit_card.card,
          number: cardNumber // Sobrescrever com cartão de teste
        }
      }
    }]
  };
  
  try {
    const token = await getToken();
    const response = await window.TDS.init({
      token,
      tds_method_container_element: document.getElementById('tdsMethodContainer'),
      challenge_container_element: document.getElementById('challengeContainer')
    }, testOrder);
    
    console.log(`✅ ${description} - Resultado:`, response);
  } catch (error) {
    console.log(`❌ ${description} - Erro:`, error.message);
  }
}
// Executar testes
async function runAllTests3DS22() {
  // Testes 3DS 2.2
  await testScenario('4000100511112003', 'Frictionless Success (3DS 2.2)');
  await testScenario('3000100811112072', 'Manual Challenge (3DS 2.2)');
  await testScenario('7000100911112070', 'Auto Challenge Success (3DS 2.2)');
}

Tratamento de Erros

Erros Comuns e Soluções

1. "Invalid JWT for 3DS"

// ❌ Erro comum
catch (error) {
  if (error.message.includes('Invalid JWT')) {
    console.error('Token inválido ou expirado');
    // Gerar um novo token
    const newToken = await getToken();
    // Tentar novamente
  }
}

Solução: Verifique se o token está sendo gerado corretamente e se não expirou (20 segundos de validade).

2. "config.token is required"

// ✅ Validação correta
if (!token || typeof token !== 'string') {
  throw new Error('Token é obrigatório e deve ser uma string');
}

3. "HTMLElement not found"

// ✅ Verificação dos containers
const tdsMethodContainer = document.getElementById('tdsMethodContainer');
const challengeContainer = document.getElementById('challengeContainer');

if (!tdsMethodContainer || !challengeContainer) {
  throw new Error('Containers do 3DS não encontrados no DOM');
}

Exemplo de Tratamento Robusto

async function robustProcess3DS() {
  const maxRetries = 3;
  let attempt = 0;
  
  while (attempt < maxRetries) {
    try {
      const token = await getToken();
      
      const response = await window.TDS.init({
        token,
        tds_method_container_element: document.getElementById('tdsMethodContainer'),
        challenge_container_element: document.getElementById('challengeContainer'),
        use_default_challenge_iframe_style: true,
        challenge_window_size: '03'
      }, orderData);
      
      return response; // Sucesso
      
    } catch (error) {
      attempt++;
      console.warn(`Tentativa ${attempt} falhou:`, error.message);
      
      if (attempt >= maxRetries) {
        throw new Error(`Falha após ${maxRetries} tentativas: ${error.message}`);
      }
      
      // Aguardar antes de tentar novamente
      await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
    }
  }
}

Solução de Problemas

Cenário 1: Challenge não aparece

Sintomas: O processo parece executar mas nenhum challenge é exibido.

Possíveis causas:

  • Container challengeContainer não existe ou está oculto
  • CSS está impedindo a exibição
  • Cartão de teste não requer challenge

Solução:

// Debug do container
const container = document.getElementById('challengeContainer');
console.log('Container exists:', !!container);
console.log('Container visible:', container && container.offsetHeight > 0);

// Testar com cartão que força challenge
const testCard = '3000100811111072'; // Manual challenge

Cenário 2: Timeout frequente

Sintomas: Erro "Token expirado" ou timeout.

Possíveis causas:

  • Operações lentas no backend
  • Problemas de conectividade
  • Token gerado muito cedo

Solução:

// Gerar token just-in-time
async function justInTimeProcess() {
  // Preparar tudo primeiro
  const orderReady = validateOrder(orderData);
  const containersReady = validateContainers();
  
  if (orderReady && containersReady) {
    // Só então gerar o token
    const token = await getToken();
    return window.TDS.init(/* ... */);
  }
}

Cenário 3: Erro de CORS

Sintomas: Erro de Cross-Origin Request.

Solução:

// Backend deve incluir headers CORS
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://seudominio.com');
  res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next();
});