3DS
Pay2Win SDK v3.1.0 - Documentação
Sistema completo de pagamentos com 3D Secure
Instalação
1. Incluir o SDK
html
<script src="https://api.pay2w.com/static/js/pay2win-threeds-sdk.js"></script>
2. Inicializar o SDK
// Configurar chave pública
await Pay2Win.setPublicKey('pk_live_sua_chave_aqui');
// Definir ambiente (false para sandbox, true para produção)
Pay2Win.setProduction(false);
console.log('Pay2Win SDK v3.1.0 inicializado');
Fluxo de Pagamento Simplificado
A versão simplifica drasticamente o processo de integração. Agora você precisa apenas:
-
Chamar
prepareThreeDS()- que faz tudo automaticamente -
Enviar os tokens para sua API
/transactions
Diagrama do Fluxo
┌─────────────────────────────────────────────────────────┐
│ 1. Frontend: prepareThreeDS() │
│ ├─ Coleta dados do dispositivo (DDC) │
│ ├─ Tokeniza o cartão │
│ ├─ Chama backend Pay2Win (/threeds/prepare) │
│ ├─ Abre modal 3DS se necessário (automático) │
│ └─ Retorna: { card_token, threeds_token } │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 2. Backend: POST /transactions │
│ Envia: card_token + threeds_token + dados transação │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 3. Backend: Processa e retorna resultado │
│ Status: approved | declined | refused │
└─────────────────────────────────────────────────────────┘
Implementação Completa
Passo 1: Preparar dados do cartão
const cardData = {
number: "4000000000002701",
holderName: "JOAO DA SILVA",
expirationMonth: "12",
expirationYear: "2027",
cvv: "123"
};
const transactionData = {
amount: 150.00, // Valor em reais
installments: 1,
currency: 'BRL'
};
Passo 2: Chamar prepareThreeDS()
javascript
try {
// prepareThreeDS FAZ TUDO:
// - DDC (Device Data Collection)
// - Tokenização do cartão
// - Chamada ao backend Pay2Win
// - Abertura do modal 3DS se necessário
// - Retorna tokens prontos para uso
const result = await Pay2Win.prepareThreeDS(transactionData, cardData);
console.log('Tokens recebidos:', result);
// {
// card_token: "card_abc123...",
// threeds_token: "threeds_xyz789..."
// }
} catch (error) {
console.error('Erro no prepareThreeDS:', error.message);
// Tratar erro: dados inválidos, timeout, etc.
}
Passo 3: Criar transação no seu backend
javascript
const response = await fetch('https://api.pay2w.com/transactions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic {credentials}'
},
body: JSON.stringify({
// Valor em centavos
value: Math.round(transactionData.amount * 100),
installments: transactionData.installments,
// Dados do cliente
customer: {
id: "customer_123",
externalRef: "USER_456",
name: cardData.holderName,
email: "[email protected]",
phone: "+5547988123456",
birthdate: "1990-01-15",
document: {
number: "12345678901",
type: "CPF"
}
},
// Referências
externalReference: "ORDER_" + Date.now(),
postbackUrl: "https://seu-site.com/webhook",
// Items
items: [{
title: "Produto Exemplo",
externalRef: "PROD_789",
unitPrice: Math.round(transactionData.amount * 100),
quantity: 1,
tangible: true
}],
// Endereço de entrega
shipping: {
fee: 1500,
address: {
street: "Rua Exemplo",
streetNumber: "123",
complement: "Apto 45",
zipCode: "89010100",
neighborhood: "Centro",
city: "Blumenau",
state: "SC",
country: "Brasil"
}
},
// Método de pagamento
paymentMethod: "credit_card",
// TOKENS DO prepareThreeDS
card: {
token: result.card_token
},
threeds: {
token: result.threeds_token
}
})
});
const transaction = await response.json();
console.log('Resposta:', transaction);
Passo 4: Processar resposta e decide ação a ser tomada
Métodos da API
Pay2Win.setPublicKey(publicKey)
Define a chave pública do Pay2Win.
Parâmetros:
-
publicKey(string): Sua chave pública Pay2Win
Exemplo:
await Pay2Win.setPublicKey('pk_live_xXXO18VoShBFToB00NkGgnfLBvRoeFi5');
Pay2Win.setProduction(isProduction)
Define o ambiente de execução.
Parâmetros:
-
isProduction(boolean):falsepara sandbox,truepara produção
Exemplo:
Pay2Win.setProduction(false); // Usar sandbox
Pay2Win.prepareThreeDS(transactionData, cardData)
Método principal - Prepara e processa toda a autenticação 3D Secure.
Este método faz tudo automaticamente:
-
Coleta dados do dispositivo (DDC)
-
Tokeniza o cartão de crédito
-
Chama o backend Pay2Win (
/threeds/prepare) -
Abre modal 3DS se necessário
-
Retorna tokens prontos para uso
Parâmetros:
transactionData (object):
javascript
{
amount: 150.00, // Valor em reais
installments: 1, // Número de parcelas
currency: 'BRL' // Moeda
}
cardData (object):
{
number: "4000000000002701",
holderName: "JOAO DA SILVA",
expirationMonth: "12",
expirationYear: "2027",
cvv: "123"
}
Retorna:
{
card_token: string, // Token do cartão
threeds_token: string // Token 3DS
}
Exemplo completo:
const tokens = await Pay2Win.prepareThreeDS(
{
amount: 150.00,
installments: 3,
currency: 'BRL'
},
{
number: "4000000000002701",
holderName: "JOAO DA SILVA",
expirationMonth: "12",
expirationYear: "2027",
cvv: "123"
}
);
console.log(tokens.card_token); // "card_abc123..."
console.log(tokens.threeds_token); // "threeds_xyz789..."
Cartões de Teste - Sandbox
Frictionless - Autenticação sem Challenge
|
Cartão |
Bandeira |
Resultado |
Descrição |
|---|---|---|---|
|
|
Visa |
✅ Aprovado |
Autenticação frictionless bem-sucedida |
|
|
Mastercard |
✅ Aprovado |
Autenticação frictionless bem-sucedida |
|
|
Visa |
❌ Falha |
Falha na autenticação frictionless |
|
|
Visa |
⚠️ Tentativa |
Autenticação stand-in |
|
|
Visa |
⚠️ Indisponível |
Autenticação indisponível do emissor |
|
|
Visa |
❌ Rejeitado |
Rejeitado pelo emissor |
Step Up - Autenticação com Challenge (Modal)
|
Cartão |
Bandeira |
Resultado |
Descrição |
|---|---|---|---|
|
|
Visa |
✅ Aprovado |
Challenge + aprovação |
|
|
Mastercard |
✅ Aprovado |
Challenge + aprovação |
|
|
Visa |
❌ Falha |
Challenge + falha na autenticação |
|
|
Visa |
⚠️ Indisponível |
Challenge indisponível |
Cenários Especiais - Erros e Situações Técnicas
|
Cartão |
Resultado |
Descrição |
|---|---|---|
|
|
Não disponível |
Autenticação não disponível no lookup |
|
|
Erro |
Erro durante o lookup |
|
|
Timeout |
Timeout na transação cmpi_lookup |
|
|
Erro |
Erro durante a autenticação |
|
|
Bypass |
Autenticação ignorada (bypassed) |
Dados padrão para todos os testes:
-
Nome: JOAO DA SILVA
-
Validade: 12/27
-
CVV: 123
Status da Transação
const transaction = await response.json();
switch (transaction.status) {
case 'paid':
console.log('Pagamento aprovado!');
redirectToSuccess(transaction.id);
break;
case 'refused':
console.log('Pagamento recusado:', transaction.reason);
showError(transaction.reason);
break;
default:
console.warn('Status inesperado:', transaction.status);
}
Checar Disponibilidade Do 3DS
https://api.pay2w.com/threeds/check-availability → REQUEST GET
3DS ATIVADO { "success": true, "status": true }
3DS DESATIVADO { "success": true, "status": false }
Pagina SAMPLE para teste
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pay2Win SDK - Demo</title>
<!-- IMPORTAR O SDK -->
<script src="https://api.pay2w.com/static/js/pay2win-threeds-sdk.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
:root {
--primary: #0070F3;
--primary-dark: #0051CC;
--accent: #3291FF;
--success: #0070F3;
--error: #EE0000;
--gray-50: #FAFAFA;
--gray-100: #F5F5F5;
--gray-200: #E5E5E5;
--gray-300: #D4D4D4;
--gray-400: #A3A3A3;
--gray-600: #525252;
--gray-900: #171717;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: white;
min-height: 100vh;
color: var(--gray-900);
line-height: 1.6;
}
.page-header {
background: linear-gradient(135deg, #0070F3 0%, #0051CC 100%);
padding: 24px 20px;
box-shadow: 0 1px 0 rgba(0,0,0,0.05);
}
.header-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 16px;
}
.logo {
height: 32px;
width: auto;
}
.header-divider {
height: 24px;
width: 1px;
background: rgba(255,255,255,0.3);
}
.header-title {
color: white;
font-size: 16px;
font-weight: 600;
letter-spacing: -0.02em;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 48px 20px;
}
.hero {
text-align: center;
margin-bottom: 64px;
}
.hero-title {
font-size: 56px;
font-weight: 700;
letter-spacing: -0.04em;
line-height: 1.1;
margin-bottom: 16px;
background: linear-gradient(135deg, var(--gray-900) 0%, var(--gray-600) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-subtitle {
font-size: 20px;
color: var(--gray-600);
max-width: 600px;
margin: 0 auto 24px;
}
.version-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--primary);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.content-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 32px;
margin-bottom: 48px;
}
.card {
background: white;
border: 1px solid var(--gray-200);
border-radius: 12px;
padding: 24px;
transition: all 0.2s;
}
.card:hover {
border-color: var(--primary);
box-shadow: 0 8px 30px rgba(0,0,0,0.08);
}
.card-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
color: var(--gray-900);
}
.card-icon {
width: 20px;
height: 20px;
color: var(--primary);
}
.card-description {
color: var(--gray-600);
font-size: 14px;
line-height: 1.6;
margin-bottom: 16px;
}
.flow-steps {
display: flex;
flex-direction: column;
gap: 12px;
}
.flow-step {
display: flex;
align-items: start;
gap: 12px;
padding: 12px;
background: var(--gray-50);
border-radius: 8px;
border: 1px solid var(--gray-200);
}
.step-number {
min-width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
background: var(--primary);
color: white;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
}
.step-text {
font-size: 14px;
line-height: 1.5;
}
.step-text code {
background: white;
border: 1px solid var(--gray-200);
color: var(--primary);
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
font-family: 'SF Mono', Monaco, monospace;
}
.test-cards-grid {
display: grid;
gap: 8px;
margin-top: 16px;
}
.test-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: white;
border: 1px solid var(--gray-200);
border-radius: 8px;
cursor: pointer;
transition: all 0.15s;
}
.test-card:hover {
border-color: var(--primary);
background: var(--gray-50);
transform: translateX(2px);
}
.test-card code {
font-family: 'SF Mono', Monaco, monospace;
color: var(--gray-900);
font-weight: 600;
font-size: 13px;
}
.test-card .badge {
color: var(--gray-600);
font-size: 12px;
font-weight: 500;
padding: 4px 10px;
background: var(--gray-100);
border-radius: 100px;
}
.test-cards-section {
margin-top: 16px;
}
.toggle-cards {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 10px 16px;
background: var(--gray-50);
border: 1px solid var(--gray-200);
border-radius: 8px;
font-size: 14px;
font-weight: 500;
color: var(--gray-900);
cursor: pointer;
transition: all 0.15s;
margin-bottom: 16px;
}
.toggle-cards:hover {
background: var(--gray-100);
border-color: var(--gray-300);
}
.toggle-cards svg {
transition: transform 0.2s;
}
.toggle-cards.active svg {
transform: rotate(180deg);
}
.cards-container {
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.cards-category-title {
font-size: 13px;
color: var(--gray-900);
display: block;
margin-bottom: 8px;
font-weight: 600;
}
.card-subtitle {
font-size: 11px;
color: var(--gray-500);
margin-top: 2px;
font-weight: 400;
}
.payment-section {
background: white;
border: 1px solid var(--gray-200);
border-radius: 12px;
padding: 32px;
max-width: 540px;
margin: 0 auto;
}
.section-title {
font-size: 24px;
font-weight: 600;
margin-bottom: 24px;
color: var(--gray-900);
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: var(--gray-900);
}
input, select {
width: 100%;
padding: 12px 16px;
border: 1px solid var(--gray-300);
border-radius: 8px;
font-size: 14px;
color: var(--gray-900);
transition: all 0.15s;
font-family: inherit;
background: white;
}
input::placeholder {
color: var(--gray-400);
}
input:hover, select:hover {
border-color: var(--gray-400);
}
input:focus, select:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(0, 112, 243, 0.1);
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.amount-input {
position: relative;
}
.amount-input::before {
content: 'R$';
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
color: var(--gray-600);
font-weight: 500;
font-size: 14px;
}
.amount-input input {
padding-left: 44px;
}
.pay-button {
width: 100%;
padding: 14px 24px;
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
font-size: 15px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s;
margin-top: 8px;
}
.pay-button:hover:not(:disabled) {
background: var(--primary-dark);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 112, 243, 0.2);
}
.pay-button:active:not(:disabled) {
transform: translateY(0);
}
.pay-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.status {
padding: 14px 16px;
border-radius: 8px;
margin-top: 16px;
font-size: 14px;
font-weight: 500;
display: none;
border: 1px solid;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.status.success {
background: #F0FDF4;
border-color: #86EFAC;
color: #166534;
}
.status.error {
background: #FEF2F2;
border-color: #FCA5A5;
color: #991B1B;
}
.status.info {
background: #EFF6FF;
border-color: #93C5FD;
color: #1E40AF;
}
@media (max-width: 768px) {
.hero-title {
font-size: 40px;
}
.hero-subtitle {
font-size: 18px;
}
.content-grid {
grid-template-columns: 1fr;
gap: 24px;
}
.form-row {
gap: 12px;
}
.container {
padding: 32px 20px;
}
.payment-section {
padding: 24px;
}
}
</style>
</head>
<body>
<div class="page-header">
<div class="header-content">
<img src="https://pay2w.com/images/assets/logo-white.svg" alt="Pay2Win" class="logo">
<div class="header-divider"></div>
<span class="header-title">SDK Demo</span>
</div>
</div>
<div class="container">
<div class="hero">
<h1 class="hero-title">Pay2Win SDK</h1>
<p class="hero-subtitle">Integração completa de pagamentos com 3D Secure. Resposta simplificada do backend com card_token e threeds_token.</p>
</div>
<div class="content-grid">
<div class="card">
<h2 class="card-title">
<svg class="card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
Fluxo de Integração
</h2>
<p class="card-description">Processo simplificado em 3 etapas para processar pagamentos com 3D Secure</p>
<div class="flow-steps">
<div class="flow-step">
<div class="step-number">1</div>
<div class="step-text">Frontend chama <code>prepareThreeDS()</code> que executa token 3DS, DDC, backend e modal automaticamente</div>
</div>
<div class="flow-step">
<div class="step-number">2</div>
<div class="step-text">SDK retorna <code>card_token</code> e <code>threeds_token</code> prontos para uso</div>
</div>
<div class="flow-step">
<div class="step-number">3</div>
<div class="step-text">Envia tokens para <code>POST /transactions</code> e finaliza pagamento</div>
</div>
</div>
</div>
<div class="card">
<h2 class="card-title">
<svg class="card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"></path>
</svg>
Cartões de Teste
</h2>
<p class="card-description">Clique em qualquer cartão para preencher automaticamente o formulário</p>
<div class="test-cards-section">
<button class="toggle-cards" onclick="toggleTestCards()">
<span id="toggle-text">Ver todos os cartões</span>
<svg id="toggle-icon" width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</button>
<div id="basic-cards" class="cards-container">
<div style="margin-bottom: 16px;">
<strong class="cards-category-title">✅ Frictionless - Sucesso</strong>
<div class="test-cards-grid">
<div class="test-card" onclick="fillCard('4000000000002701')">
<code>4000 0000 0000 2701</code>
<span class="badge">Visa</span>
</div>
<div class="test-card" onclick="fillCard('5200000000002235')">
<code>5200 0000 0000 2235</code>
<span class="badge">Mastercard</span>
</div>
</div>
</div>
<div>
<strong class="cards-category-title">🔐 Step Up - Com Challenge</strong>
<div class="test-cards-grid">
<div class="test-card" onclick="fillCard('4000000000002503')">
<code>4000 0000 0000 2503</code>
<span class="badge">Visa</span>
</div>
<div class="test-card" onclick="fillCard('5200000000001096')">
<code>5200 0000 0000 1096</code>
<span class="badge">Mastercard</span>
</div>
</div>
</div>
</div>
<div id="all-cards" class="cards-container" style="display: none;">
<div style="margin-bottom: 16px;">
<strong class="cards-category-title">✅ Frictionless - Sucesso</strong>
<div class="test-cards-grid">
<div class="test-card" onclick="fillCard('4000000000002701')">
<div>
<code>4000 0000 0000 2701</code>
<div class="card-subtitle">Successful Frictionless Authentication</div>
</div>
<span class="badge">Visa</span>
</div>
<div class="test-card" onclick="fillCard('5200000000002235')">
<div>
<code>5200 0000 0000 2235</code>
<div class="card-subtitle">Successful Frictionless Authentication</div>
</div>
<span class="badge">Mastercard</span>
</div>
</div>
</div>
<div style="margin-bottom: 16px;">
<strong class="cards-category-title">⚠️ Frictionless - Falhas</strong>
<div class="test-cards-grid">
<div class="test-card" onclick="fillCard('4000000000002925')">
<div>
<code>4000 0000 0000 2925</code>
<div class="card-subtitle">Failed Frictionless Authentication</div>
</div>
<span class="badge">Visa</span>
</div>
<div class="test-card" onclick="fillCard('4000000000002719')">
<div>
<code>4000 0000 0000 2719</code>
<div class="card-subtitle">Attempts Stand-In Frictionless</div>
</div>
<span class="badge">Visa</span>
</div>
<div class="test-card" onclick="fillCard('4000000000002313')">
<div>
<code>4000 0000 0000 2313</code>
<div class="card-subtitle">Unavailable Frictionless from Issuer</div>
</div>
<span class="badge">Visa</span>
</div>
<div class="test-card" onclick="fillCard('4000000000002537')">
<div>
<code>4000 0000 0000 2537</code>
<div class="card-subtitle">Rejected Frictionless by Issuer</div>
</div>
<span class="badge">Visa</span>
</div>
</div>
</div>
<div style="margin-bottom: 16px;">
<strong class="cards-category-title">🔐 Step Up - Com Challenge</strong>
<div class="test-cards-grid">
<div class="test-card" onclick="fillCard('4000000000002503')">
<div>
<code>4000 0000 0000 2503</code>
<div class="card-subtitle">Successful Step Up Authentication</div>
</div>
<span class="badge">Visa</span>
</div>
<div class="test-card" onclick="fillCard('5200000000001096')">
<div>
<code>5200 0000 0000 1096</code>
<div class="card-subtitle">Successful Step Up Authentication</div>
</div>
<span class="badge">Mastercard</span>
</div>
<div class="test-card" onclick="fillCard('4000000000002370')">
<div>
<code>4000 0000 0000 2370</code>
<div class="card-subtitle">Failed Step Up Authentication</div>
</div>
<span class="badge">Visa</span>
</div>
<div class="test-card" onclick="fillCard('4000000000002420')">
<div>
<code>4000 0000 0000 2420</code>
<div class="card-subtitle">Step Up Authentication Unavailable</div>
</div>
<span class="badge">Visa</span>
</div>
</div>
</div>
<div>
<strong class="cards-category-title">🔧 Erros e Situações Especiais</strong>
<div class="test-cards-grid">
<div class="test-card" onclick="fillCard('4000000000002990')">
<div>
<code>4000 0000 0000 2990</code>
<div class="card-subtitle">Authentication Not Available on Lookup</div>
</div>
<span class="badge">Visa</span>
</div>
<div class="test-card" onclick="fillCard('4000000000002446')">
<div>
<code>4000 0000 0000 2446</code>
<div class="card-subtitle">Error on Lookup</div>
</div>
<span class="badge">Visa</span>
</div>
<div class="test-card" onclick="fillCard('4000000000002354')">
<div>
<code>4000 0000 0000 2354</code>
<div class="card-subtitle">Timeout on cmpi_lookup Transaction</div>
</div>
<span class="badge">Visa</span>
</div>
<div class="test-card" onclick="fillCard('4000000000002644')">
<div>
<code>4000 0000 0000 2644</code>
<div class="card-subtitle">Error on Authentication</div>
</div>
<span class="badge">Visa</span>
</div>
<div class="test-card" onclick="fillCard('4000000000002560')">
<div>
<code>4000 0000 0000 2560</code>
<div class="card-subtitle">Bypassed Authentication</div>
</div>
<span class="badge">Visa</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="payment-section">
<h2 class="section-title">Processar Pagamento</h2>
<form id="payment-form">
<div class="form-group">
<label for="cardNumber">Número do Cartão</label>
<input
type="text"
id="cardNumber"
maxlength="19"
placeholder="0000 0000 0000 0000"
required
>
</div>
<div class="form-group">
<label for="cardHolder">Nome no Cartão</label>
<input
type="text"
id="cardHolder"
placeholder="NOME COMPLETO"
required
>
</div>
<div class="form-row">
<div class="form-group">
<label for="expiry">Validade</label>
<input
type="text"
id="expiry"
placeholder="MM/AA"
maxlength="5"
required
>
</div>
<div class="form-group">
<label for="cvv">CVV</label>
<input
type="text"
id="cvv"
placeholder="123"
maxlength="4"
required
>
</div>
</div>
<div class="form-row">
<div class="form-group">
<div class="amount-input">
<label for="amount">Valor</label>
<input
type="text"
id="amount"
value="150,00"
required
>
</div>
</div>
<div class="form-group">
<label for="installments">Parcelas</label>
<select id="installments">
<option value="1">1x sem juros</option>
<option value="2">2x sem juros</option>
<option value="3" selected>3x sem juros</option>
<option value="6">6x sem juros</option>
<option value="12">12x sem juros</option>
</select>
</div>
</div>
<button type="submit" id="pay-button" class="pay-button">
Processar Pagamento
</button>
</form>
<div id="status" class="status"></div>
</div>
</div>
<script>
const cardData = {
number: '',
holderName: '',
expirationMonth: '',
expirationYear: '',
cvv: ''
};
const form = document.getElementById('payment-form');
const payButton = document.getElementById('pay-button');
const status = document.getElementById('status');
document.addEventListener('DOMContentLoaded', async () => {
try {
await Pay2Win.setPublicKey('pk_live_xXXO18VoShBFToB00NkGgnfLBvRoeFi5');
Pay2Win.setProduction(false);
console.log('Pay2Win SDK inicializado');
showStatus('SDK inicializado com sucesso', 'success');
} catch (error) {
console.error('Erro ao inicializar:', error);
showStatus('Erro ao inicializar: ' + error.message, 'error');
}
});
form.addEventListener('submit', async (e) => {
e.preventDefault();
payButton.disabled = true;
payButton.textContent = 'Processando...';
showStatus('Iniciando prepareThreeDS...', 'info');
try {
const [expMonth, expYear] = document.getElementById('expiry').value.split('/');
cardData.number = document.getElementById('cardNumber').value.replace(/\s/g, '');
cardData.holderName = document.getElementById('cardHolder').value;
cardData.expirationMonth = expMonth.padStart(2, '0');
cardData.expirationYear = expYear.length === 2 ? '20' + expYear : expYear;
cardData.cvv = document.getElementById('cvv').value;
const amount = parseFloat(document.getElementById('amount').value.replace(/\./g, '').replace(',', '.'));
const installments = parseInt(document.getElementById('installments').value);
const transactionData = {
amount: amount,
installments: installments,
currency: 'BRL'
};
console.log('Dados preparados:', {
cardBrand: Pay2Win.Utils.detectCardBrand(cardData.number),
lastDigits: cardData.number.slice(-4),
amount: amount
});
console.log('1. Chamando prepareThreeDS...');
showStatus('Preparando 3DS (DDC + Backend + Modal se necessário)...', 'info');
const prepareData = await Pay2Win.prepareThreeDS(transactionData, cardData);
console.log('prepareThreeDS concluído:', prepareData);
console.log('2. Criando transação com card_token e threeds_token...');
showStatus('Criando transação no backend...', 'info');
const response = await fetch('https://api.pay2win.test/transactions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'pk_live_xXXO18VoShBFToB00NkGgnfLBvRoeFi5'
},
body: JSON.stringify({
value: Math.round(amount * 100),
installments: installments,
customer: {
id: "customer_" + Date.now(),
externalRef: "USER_" + Math.floor(Math.random() * 10000),
name: cardData.holderName,
email: "[email protected]",
phone: "+5547988123456",
birthdate: "1990-01-15",
document: {
number: "05863178132",
type: "CPF"
}
},
externalReference: "ORDER_" + Date.now(),
postbackUrl: "https://webhook.site/demo",
traceable: true,
items: [{
title: "Produto Demo Pay2Win",
externalRef: "PROD_" + Date.now(),
unitPrice: Math.round(amount * 100),
quantity: 1,
tangible: true
}],
shipping: {
fee: 1500,
address: {
street: "Rua Exemplo",
streetNumber: "123",
complement: "Apto 45",
zipCode: "89010100",
neighborhood: "Centro",
city: "Blumenau",
state: "SC",
country: "Brasil"
}
},
paymentMethod: "credit_card",
card: {
token: prepareData.card_token
},
threeds: {
token: prepareData.threeds_token
}
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || errorData.error || 'Erro na transação');
}
const result = await response.json();
console.log('3. Resposta do backend:', result);
if (result.status === 'approved' || result.status === 'paid' || result.status === 'authorized') {
showStatus('✅ Pagamento aprovado! ID: ' + result.id, 'success');
resetForm();
} else if (result.status === 'declined' || result.status === 'refused') {
showStatus('❌ Pagamento recusado: ' + (result.reason || 'Verifique os dados'), 'error');
} else {
showStatus('Status: ' + result.status, 'info');
}
} catch (error) {
console.error('Erro:', error);
showStatus('Erro: ' + error.message, 'error');
} finally {
payButton.disabled = false;
payButton.textContent = 'Processar Pagamento';
}
});
function showStatus(message, type) {
status.textContent = message;
status.className = `status ${type}`;
status.style.display = 'block';
if (type === 'success') {
setTimeout(() => {
status.style.display = 'none';
}, 8000);
}
}
function resetForm() {
form.reset();
document.getElementById('amount').value = '150,00';
document.getElementById('installments').value = '3';
cardData.number = '';
cardData.holderName = '';
cardData.expirationMonth = '';
cardData.expirationYear = '';
cardData.cvv = '';
}
function fillCard(cardNumber) {
document.getElementById('cardNumber').value = cardNumber;
document.getElementById('cardHolder').value = 'JOAO DA SILVA';
document.getElementById('expiry').value = '12/27';
document.getElementById('cvv').value = '123';
showStatus('Cartão preenchido - clique em Processar', 'info');
}
function toggleTestCards() {
const basicCards = document.getElementById('basic-cards');
const allCards = document.getElementById('all-cards');
const toggleText = document.getElementById('toggle-text');
const toggleBtn = document.querySelector('.toggle-cards');
if (allCards.style.display === 'none') {
basicCards.style.display = 'none';
allCards.style.display = 'block';
toggleText.textContent = 'Ver menos cartões';
toggleBtn.classList.add('active');
} else {
basicCards.style.display = 'block';
allCards.style.display = 'none';
toggleText.textContent = 'Ver todos os cartões';
toggleBtn.classList.remove('active');
}
}
document.getElementById('cardNumber').addEventListener('input', (e) => {
let value = e.target.value.replace(/\s/g, '');
let formatted = value.match(/.{1,4}/g)?.join(' ') || value;
e.target.value = formatted;
});
document.getElementById('expiry').addEventListener('input', (e) => {
let value = e.target.value.replace(/\D/g, '');
if (value.length >= 2) {
value = value.slice(0, 2) + '/' + value.slice(2, 4);
}
e.target.value = value;
});
document.getElementById('cvv').addEventListener('input', (e) => {
e.target.value = e.target.value.replace(/\D/g, '');
});
document.getElementById('amount').addEventListener('input', (e) => {
let value = e.target.value.replace(/\D/g, '');
if (value.length === 0) {
value = '0';
} else if (value.length === 1) {
value = '0' + value;
}
let formattedValue = '';
if (value.length <= 2) {
formattedValue = '0,' + value.padStart(2, '0');
} else {
const integerPart = value.slice(0, -2);
const decimalPart = value.slice(-2);
const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
formattedValue = formattedInteger + ',' + decimalPart;
}
e.target.value = formattedValue;
});
</script>
</body>
</html>