diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 5e78ba3..c1d8467 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -430,9 +430,9 @@ Used when loading via `widget-bootstrap.v1.min.js`: ```typescript window.PaymentWidget.init(config: WidgetConfig): Promise -window.PaymentWidget.open(merchantId?: string): void -window.PaymentWidget.close(merchantId?: string): void -window.PaymentWidget.destroy(merchantId?: string): void +window.PaymentWidget.open(orderId?: string): void +window.PaymentWidget.close(orderId?: string): void +window.PaymentWidget.destroy(orderId?: string): void window.PaymentWidget.getState(): { isLoaded: boolean } ``` @@ -489,22 +489,22 @@ Always test via HTTP server (not `file://`) due to CORS restrictions. ### Issue: "ERR_NAME_NOT_RESOLVED" on widget load -**Cause**: Bootstrap has wrong CDN_BASE_URL +**Cause**: Bootstrap has wrong CDN_BASE_URL **Solution**: Update `src/bootstrap/index.ts` with correct CloudFront URL, rebuild, and redeploy ### Issue: CORS errors in browser -**Cause**: S3 bucket missing CORS configuration +**Cause**: S3 bucket missing CORS configuration **Solution**: Run `./deploy.sh` which configures CORS automatically, or manually apply CORS config ### Issue: Old files served after deploy -**Cause**: CloudFront cache not invalidated +**Cause**: CloudFront cache not invalidated **Solution**: Script invalidates automatically. If manual: `aws cloudfront create-invalidation --distribution-id EOLJNTE5PW5O9 --paths "/*"` ### Issue: Widget not found in window object -**Cause**: Using wrong API for the loaded script +**Cause**: Using wrong API for the loaded script **Solution**: - Bootstrap loader → `window.PaymentWidget` diff --git a/PUBLISHING.md b/PUBLISHING.md index 1a03ee9..5813488 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -576,7 +576,7 @@ CSS: https://d2x7cg3k3on9lk.cloudfront.net/widget.v1.min.css (4.6KB) > +``` + +### ❓ **Funciona com Next.js?** +✅ Sim! Funciona perfeitamente: + +```jsx +// pages/checkout.js ou app/checkout/page.js +import { PaymentWidget } from 'payment-widget-sdk'; + +export default function CheckoutPage() { + return ; +} +``` + +### ❓ **E com Remix?** +✅ Sim! + +```jsx +// app/routes/checkout.tsx +import { PaymentWidget } from 'payment-widget-sdk'; + +export default function CheckoutRoute() { + return ; +} +``` + +### ❓ **Posso customizar completamente o CSS?** +O widget usa Shadow DOM para isolamento. Personalize via props `theme`: + +```jsx + +``` + +### ❓ **Como debugar problemas?** +1. Abra DevTools (F12) +2. Verifique Console por erros +3. Veja Network para requests falhando +4. Use callbacks `onError` para capturar erros + +### ❓ **Suporta TypeScript?** +✅ Sim! Tipos completos incluídos: + +```typescript +import type { WidgetConfig, PaymentSuccessData } from 'payment-widget-sdk'; + +const config: WidgetConfig = { + orderId: "typed-123", + onSuccess: (data: PaymentSuccessData) => { + // Auto-complete funcionando! 🎉 + console.log(data.transactionId); + } +}; +``` + +--- + +## 🐛 Troubleshooting + +### ❌ **"Module not found: payment-widget-sdk"** + +```bash +# Verificar se foi instalado +npm list payment-widget-sdk + +# Reinstalar se necessário +npm install payment-widget-sdk +``` + +### ❌ **"React hooks error"** + +Problema: Múltiplas versões do React. + +```bash +# Verificar versões +npm ls react + +# Garantir versão única +npm dedupe +``` + +### ❌ **Widget não abre** + +1. **Verificar console** por erros JavaScript +2. **Verificar orderId** está sendo passado +3. **Testar callback onError**: + +```jsx + { + console.error('🐛 Erro capturado:', error); + alert(`Debug: ${error.message}`); + } + }} +/> +``` + +### ❌ **Estilos quebrados** + +O widget é isolado via Shadow DOM. Se estilos não aparecem: + +1. **Verificar importação** do CSS (automática no SDK) +2. **Limpar cache** do navegador +3. **Verificar tema** está sendo aplicado + +### ❌ **Callbacks não funcionam** + +```jsx +// ❌ Erro comum: função inline que recria a cada render + console.log(data) // ← Recria sempre + }} +/> + +// ✅ Correto: função estável +const handleSuccess = useCallback((data) => { + console.log(data); +}, []); + + +``` + +### 🔍 **Debug Mode** + +Para desenvolvedores: + +```jsx + console.log('🔓 Modal aberto'), + onClose: () => console.log('🔒 Modal fechado'), + onSuccess: (data) => console.log('✅ Sucesso:', data), + onError: (error) => console.error('❌ Erro:', error) + }} +/> +``` + +--- + +## 📞 Suporte + +### 🆘 Precisa de Ajuda? + +- **📖 Documentação**: Este guia +- **🐛 Issues**: https://github.com/usuario/payment-widget/issues +- **📧 Email**: suporte@cartaosimples.com +- **💬 Discord**: https://discord.gg/cartaosimples + +### 📝 Reportar Bug + +Ao reportar problemas, inclua: + +1. **Versão** do SDK (`npm list payment-widget-sdk`) +2. **Versão** do React (`npm list react`) +3. **Código** que reproduz o problema +4. **Console errors** (screenshot ou texto) +5. **Browser** e versão + +### 💡 Sugerir Feature + +Abra uma issue com: +- **Descrição** clara da funcionalidade +- **Use case** (por que é útil) +- **Mockup** ou exemplo (se visual) + +--- + +## 🚀 Próximos Passos + +1. **🧪 Teste** os exemplos acima +2. **🎨 Customize** o tema para sua marca +3. **📱 Teste** em diferentes dispositivos +4. **🚀 Deploy** em staging primeiro +5. **📊 Monitor** logs e erros +6. **🔄 Itere** baseado no feedback dos usuários + +### 🎯 Recursos Avançados + +- **Multiple Widgets**: Vários widgets na mesma página +- **State Management**: Integração com Redux/Zustand +- **Analytics**: Tracking de conversão +- **A/B Testing**: Diferentes configurações + +--- + +*💳 Payment Widget SDK - Pagamentos simples para desenvolvedores* \ No newline at end of file diff --git a/docs/GUIA-USO-WIDGET.md b/docs/GUIA-USO-WIDGET.md index a3b147e..35668bd 100644 --- a/docs/GUIA-USO-WIDGET.md +++ b/docs/GUIA-USO-WIDGET.md @@ -31,7 +31,7 @@ O **bootstrap** é um script leve (~5KB) que gerencia o carregamento dinâmico d @@ -189,7 +189,7 @@ interface WidgetConfig { - - - - - \ No newline at end of file + + + + Payment Widget - CDN Example + + + + + + + + +

Payment Widget - CDN Integration

+ +
+

1. Usando API Global (Recomendado)

+

+ O React está incluído no bundle, não precisa carregar + separadamente! +

+ +
+

Widget será montado aqui...

+
+ + + + + +
+ +
+

2. Via API Global Direta

+

Controlando via window.PaymentWidget_orderId

+ + + + +
+ + + + + + + diff --git a/public/examples/cloudfront-test.html b/public/examples/cloudfront-test.html index 7a3026e..e813631 100644 --- a/public/examples/cloudfront-test.html +++ b/public/examples/cloudfront-test.html @@ -1,423 +1,491 @@ - + - - - - - Teste CDN CloudFront - Cartão Simples Widget - - - - -
-

🚀 Teste CDN CloudFront

-

Cartão Simples Payment Widget

- -
- - Verificando conexão com CDN... -
- -
- CDN Base URL:
- https://d2x7cg3k3on9lk.cloudfront.net/ -
- -
-
- Distribution ID: - EOLJNTE5PW5O9 -
-
- Bootstrap: - Carregando... -
-
- CDN Bundle: - Carregando... -
-
- CSS: - Carregando... -
-
- - - - -

📝 Logs de Teste

-
-
- - - - - \ No newline at end of file + } + + async function runTests() { + const statusDiv = document.getElementById("status"); + const testBtn = document.getElementById("test-btn"); + + statusDiv.className = "status loading"; + statusDiv.innerHTML = + 'Executando testes...'; + testBtn.disabled = true; + + log("🚀 Iniciando testes do CDN...", "info"); + + const results = await Promise.all( + FILES_TO_TEST.map((file) => testFile(file)), + ); + const allPassed = results.every((result) => result === true); + + if (allPassed) { + statusDiv.className = "status success"; + statusDiv.innerHTML = + 'Todos os arquivos estão acessíveis!'; + log("🎉 Todos os testes passaram com sucesso!", "success"); + } else { + statusDiv.className = "status error"; + statusDiv.innerHTML = + 'Alguns arquivos não foram encontrados'; + log( + "⚠️ Alguns testes falharam. Verifique os logs acima.", + "error", + ); + } + + testBtn.disabled = false; + } + + function openWidget() { + log("🎨 Carregando widget do CDN...", "info"); + + // Criar um container para o widget + const widgetContainer = document.createElement("div"); + widgetContainer.id = "widget-test-container"; + widgetContainer.style.cssText = + "margin-top: 20px; padding: 20px; border: 2px dashed #667eea; border-radius: 8px; background: #f7fafc;"; + widgetContainer.innerHTML = + '

⏳ Carregando widget via CDN...

'; + + document + .querySelector(".container") + .appendChild(widgetContainer); + + // Carregar o CSS primeiro + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = `${CDN_BASE_URL}/widget.v1.min.css`; + link.onload = () => { + log("✅ CSS carregado", "success"); + }; + link.onerror = () => { + log("❌ Erro ao carregar CSS", "error"); + }; + document.head.appendChild(link); + + // Carregar o bootstrap JS + const script = document.createElement("script"); + script.src = `${CDN_BASE_URL}/widget-bootstrap.v1.min.js`; + script.async = true; + + script.onload = () => { + log("✅ Bootstrap carregado!", "success"); + widgetContainer.innerHTML = + '

✅ Widget carregado com sucesso via CDN!

'; + + // Verificar se o widget está disponível + setTimeout(() => { + if (window.PaymentWidget) { + log( + "✅ PaymentWidget disponível globalmente!", + "success", + ); + widgetContainer.innerHTML += + '

✅ API PaymentWidget detectada!

'; + widgetContainer.innerHTML += + '
' +
+                                "API disponível:\n" +
+                                JSON.stringify(
+                                    Object.keys(window.PaymentWidget),
+                                    null,
+                                    2,
+                                ) +
+                                "
"; + + // Adicionar botão para testar abertura + const testButton = document.createElement("button"); + testButton.textContent = + "🎨 Testar Abertura do Widget"; + testButton.style.cssText = + "margin-top: 15px; padding: 12px 20px; background: #667eea; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; width: 100%;"; + testButton.onclick = () => { + log("🚀 Inicializando widget...", "info"); + + window.PaymentWidget.init({ + orderId: "demo-merchant-id", + primaryColor: "#667eea", + environment: "staging", + autoOpen: true, + onOpen: () => { + log("✅ Widget aberto!", "success"); + }, + onClose: () => { + log("ℹ️ Widget fechado", "info"); + }, + onError: (error) => { + log( + `❌ Erro: ${error.message}`, + "error", + ); + }, + }); + }; + widgetContainer.appendChild(testButton); + } else if (window.CartaoSimplesWidget) { + log( + "✅ CartaoSimplesWidget (bundle direto) disponível!", + "success", + ); + widgetContainer.innerHTML += + '

✅ Bundle CDN carregado diretamente

'; + widgetContainer.innerHTML += + '
' +
+                                JSON.stringify(
+                                    Object.keys(window.CartaoSimplesWidget),
+                                    null,
+                                    2,
+                                ) +
+                                "
"; + } else { + log("⚠️ Nenhuma API de widget encontrada", "error"); + widgetContainer.innerHTML += + '

⚠️ Widget carregado mas objeto global não detectado

'; + widgetContainer.innerHTML += + '

Verifique se os arquivos estão sendo servidos corretamente

'; + } + }, 500); + }; + + script.onerror = () => { + log("❌ Erro ao carregar bootstrap", "error"); + widgetContainer.innerHTML = + '

❌ Erro ao carregar widget do CDN

'; + }; + + document.body.appendChild(script); + } + + // Executar testes automaticamente ao carregar a página + window.addEventListener("load", () => { + log("📡 Página carregada. Iniciando verificações...", "info"); + setTimeout(runTests, 1000); + }); + + + diff --git a/public/examples/exemplo-completo.html b/public/examples/exemplo-completo.html index 52dce92..c379e16 100644 --- a/public/examples/exemplo-completo.html +++ b/public/examples/exemplo-completo.html @@ -1,498 +1,589 @@ - + - - - - Exemplo Completo - Widget Cartão Simples - - - -
- -
-

🚀 Widget Cartão Simples - Demonstração Completa

-

- Teste todas as funcionalidades do widget de pagamento white-label. - ✓ CDN Ativo - Carregando... -

-
- - -
-

⚙️ Configuração do Widget

-
-
- - + + + + Exemplo Completo - Widget Cartão Simples + + + +
+ +
+

🚀 Widget Cartão Simples - Demonstração Completa

+

+ Teste todas as funcionalidades do widget de pagamento + white-label. + ✓ CDN Ativo + Carregando... +

+
+ + +
+

⚙️ Configuração do Widget

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+

💳 Pagamento Básico

+

+ Abre o widget com configuração padrão e tema + personalizado. +

+ +
+ +
+

🎨 Widget Customizado

+

+ Usa as configurações do painel acima para personalizar o + widget. +

+ +
+ +
+

🔧 Controle Programático

+

+ Demonstra controle via JavaScript: abrir, fechar e + destruir. +

+ +
+
+ + +
+

📋 Console de Eventos

+
+
+ [carregando] + Aguardando carregamento do widget... +
+
+
-
- - -
-
- - -
-
- - -
-
-
- - -
-
-

💳 Pagamento Básico

-

Abre o widget com configuração padrão e tema personalizado.

- -
- -
-

🎨 Widget Customizado

-

Usa as configurações do painel acima para personalizar o widget.

- -
- -
-

🔧 Controle Programático

-

Demonstra controle via JavaScript: abrir, fechar e destruir.

- -
-
- - -
-

📋 Console de Eventos

-
-
- [carregando] - Aguardando carregamento do widget... -
-
-
-
- - - - - + + - + + logContainer.appendChild(entry); + logContainer.scrollTop = logContainer.scrollHeight; + + // Manter apenas últimos 20 logs + while (logContainer.children.length > 20) { + logContainer.removeChild(logContainer.firstChild); + } + } + + // Verificar quando o widget carregar + const checkWidgetInterval = setInterval(() => { + if (typeof window.PaymentWidget !== "undefined") { + clearInterval(checkWidgetInterval); + document.getElementById("widget-status").textContent = + "✓ Widget Pronto"; + document.getElementById("widget-status").className = + "badge badge-success"; + addLog("✅ Widget carregado e pronto para uso!", "success"); + } + }, 100); + + // Timeout de 10 segundos + setTimeout(() => { + if (typeof window.PaymentWidget === "undefined") { + clearInterval(checkWidgetInterval); + document.getElementById("widget-status").textContent = + "✗ Erro ao Carregar"; + document.getElementById("widget-status").className = + "badge badge-error"; + addLog( + "❌ Timeout: Widget não carregou em 10 segundos", + "error", + ); + } + }, 10000); + + // Função: Widget Básico + function abrirWidgetBasico() { + addLog("🔄 Abrindo widget básico...", "info"); + + if (typeof window.PaymentWidget === "undefined") { + addLog( + "❌ Widget ainda não carregado. Aguarde...", + "error", + ); + alert( + "Widget ainda está carregando. Tente novamente em alguns segundos.", + ); + return; + } + + try { + window.PaymentWidget.init({ + orderId: "test-merchant-basic", + primaryColor: "#667eea", + secondaryColor: "#764ba2", + environment: "staging", + autoOpen: true, + + onSuccess: (data) => { + addLog( + `✅ Pagamento aprovado! ID: ${data.transactionId}`, + "success", + ); + console.log("Dados completos:", data); + }, + + onError: (error) => { + addLog( + `❌ Erro no pagamento: ${error.message}`, + "error", + ); + console.error("Erro completo:", error); + }, + + onOpen: () => { + addLog("📂 Widget aberto", "info"); + }, + + onClose: () => { + addLog("📁 Widget fechado", "info"); + }, + }); + + addLog("✅ Widget básico inicializado", "success"); + } catch (error) { + addLog(`❌ Erro ao inicializar: ${error.message}`, "error"); + } + } + + // Função: Widget Customizado + function abrirWidgetCustomizado() { + addLog("🎨 Abrindo widget customizado...", "info"); + + if (typeof window.PaymentWidget === "undefined") { + addLog("❌ Widget ainda não carregado", "error"); + alert( + "Widget ainda está carregando. Tente novamente em alguns segundos.", + ); + return; + } + + const config = { + orderId: document.getElementById("orderId").value, + primaryColor: document.getElementById("primaryColor").value, + secondaryColor: + document.getElementById("secondaryColor").value, + environment: document.getElementById("environment").value, + autoOpen: true, + + onSuccess: (data) => { + addLog( + `✅ Pagamento aprovado! ID: ${data.transactionId}`, + "success", + ); + addLog(`💰 Valor: R$ ${data.amount || "N/A"}`, "info"); + addLog( + `📊 Parcelas: ${data.installments || "N/A"}x`, + "info", + ); + }, + + onError: (error) => { + addLog(`❌ Erro: ${error.message}`, "error"); + }, + + onOpen: () => { + addLog("📂 Widget customizado aberto", "info"); + }, + + onClose: () => { + addLog("📁 Widget customizado fechado", "info"); + }, + }; + + try { + window.PaymentWidget.init(config); + addLog( + `✅ Widget configurado com merchant: ${config.orderId}`, + "success", + ); + addLog( + `🎨 Cores: ${config.primaryColor} / ${config.secondaryColor}`, + "info", + ); + } catch (error) { + addLog(`❌ Erro ao configurar: ${error.message}`, "error"); + } + } + + // Função: Controle Programático + async function demonstrarControle() { + addLog("🔧 Iniciando demonstração de controle...", "info"); + + if (typeof window.PaymentWidget === "undefined") { + addLog("❌ Widget não disponível", "error"); + return; + } + + try { + // 1. Inicializar sem auto-open + addLog( + "1️⃣ Inicializando widget (sem auto-open)...", + "info", + ); + window.PaymentWidget.init({ + orderId: "test-control", + primaryColor: "#4facfe", + autoOpen: false, + onSuccess: (data) => + addLog( + `✅ Transação: ${data.transactionId}`, + "success", + ), + onError: (error) => + addLog(`❌ ${error.message}`, "error"), + }); + + await sleep(1000); + + // 2. Abrir manualmente + addLog("2️⃣ Abrindo widget manualmente...", "info"); + window.PaymentWidget.open(); + + await sleep(2000); + + // 3. Fechar manualmente + addLog("3️⃣ Fechando widget...", "info"); + window.PaymentWidget.close(); + + await sleep(1000); + + // 4. Abrir novamente + addLog("4️⃣ Reabrindo widget...", "info"); + window.PaymentWidget.open(); + + addLog( + "✅ Demonstração completa! Feche o widget para continuar.", + "success", + ); + } catch (error) { + addLog( + `❌ Erro na demonstração: ${error.message}`, + "error", + ); + } + } + + // Helper: Sleep + function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + // Listeners globais + window.addEventListener("error", (e) => { + addLog(`⚠️ Erro global: ${e.message}`, "error"); + }); + + // Log inicial + addLog("🚀 Página carregada. Aguardando widget...", "info"); + + diff --git a/public/examples/html-integration.html b/public/examples/html-integration.html index 8fad57c..e0f47b7 100644 --- a/public/examples/html-integration.html +++ b/public/examples/html-integration.html @@ -1,394 +1,417 @@ - + - - - - - Exemplo - Cartão Simples Widget - - - - -
- - -

Cartão Simples

-

Parcele sem comprometer o limite do seu cartão

- -
-
MacBook Pro M3
-
R$ 12.999,00
-
ou até 12x de R$ 1.083,25 sem juros
-
- -
-
-
-
Aprovação instantânea
-
-
-
🔒
-
100% seguro
-
-
-
📱
-
Sem app
-
-
- - - -
- 🛡️ Seus dados estão protegidos com criptografia SSL -
- -
-
- - - - - - - - - - - - \ No newline at end of file + + + + Exemplo - Cartão Simples Widget + + + + +
+ + +

Cartão Simples

+

+ Parcele sem comprometer o limite do seu cartão +

+ +
+
MacBook Pro M3
+
R$ 12.999,00
+
+ ou até 12x de R$ 1.083,25 sem juros +
+
+ +
+
+
+
Aprovação instantânea
+
+
+
🔒
+
100% seguro
+
+
+
📱
+
Sem app
+
+
+ + + +
+ 🛡️ Seus dados estão protegidos com criptografia SSL +
+ +
+
+ + + + + + + + + + + diff --git a/public/examples/minimal.html b/public/examples/minimal.html index b0eef8a..287e66e 100644 --- a/public/examples/minimal.html +++ b/public/examples/minimal.html @@ -1,38 +1,39 @@ - + + + Minimal Test + + - - Minimal Test - - + +

Minimal Widget Test

+
+ + - -

Minimal Widget Test

-
- - + + - - - - \ No newline at end of file + function open() { + console.log("Opening..."); + if (widget) { + widget.open(); + } else { + alert("Mount first!"); + } + } + + + diff --git a/public/examples/test-fresh.html b/public/examples/test-fresh.html index c997f24..40fd110 100644 --- a/public/examples/test-fresh.html +++ b/public/examples/test-fresh.html @@ -1,367 +1,463 @@ - + + + + + Teste Fresh - Payment Widget + - - - -
-

🧪 Teste Fresh - Payment Widget

-

Deploy: 2 out 2025 10:09 UTC • CloudFront Invalidation: - ID8KFCQS5MU0O76ZJZGMFEDUJP

- -
- - - -
- -
- - -
-
- -
-

📊 Console de Debug

-
-
- - - - - \ No newline at end of file + + .log-entry.info { + color: #64b5f6; + } + + .status { + display: inline-block; + padding: 4px 12px; + border-radius: 12px; + font-size: 12px; + font-weight: 600; + margin-left: 10px; + } + + .status.ok { + background: #c8e6c9; + color: #2e7d32; + } + + .status.fail { + background: #ffcdd2; + color: #c62828; + } + + .status.pending { + background: #fff9c4; + color: #f57f17; + } + + code { + background: #f5f5f5; + padding: 2px 6px; + border-radius: 3px; + font-family: "SF Mono", monospace; + font-size: 12px; + } + + + + +
+

🧪 Teste Fresh - Payment Widget

+

+ Deploy: 2 out 2025 10:09 UTC • CloudFront + Invalidation: ID8KFCQS5MU0O76ZJZGMFEDUJP +

+ +
+ + + +
+ +
+ + +
+
+ +
+

📊 Console de Debug

+
+
+ + + + diff --git a/public/examples/test-modal-fix.html b/public/examples/test-modal-fix.html index 220b151..6b12123 100644 --- a/public/examples/test-modal-fix.html +++ b/public/examples/test-modal-fix.html @@ -1,257 +1,298 @@ - + - - - - - Teste Modal - Payment Widget - - - - -
-

🧪 Teste de Abertura do Modal

-

Este teste verifica se o modal abre corretamente após as correções no bootstrap.

- -
- - - - -
- -
-
- - - - - \ No newline at end of file + + + + Teste Modal - Payment Widget + + + + +
+

🧪 Teste de Abertura do Modal

+

+ Este teste verifica se o modal abre corretamente após as + correções no bootstrap. +

+ +
+ + + + +
+ +
+
+ + + + diff --git a/public/examples/test-shadow-dom.html b/public/examples/test-shadow-dom.html index fbb5c82..13482c6 100644 --- a/public/examples/test-shadow-dom.html +++ b/public/examples/test-shadow-dom.html @@ -1,505 +1,626 @@ - + + + + + Test Shadow DOM - Payment Widget + - - - -
-

🔍 Shadow DOM Test - Payment Widget

-

Teste e verifique se o Shadow DOM está funcionando corretamente

- - -
-

1️⃣ Detecção de Shadow DOM Aguardando...

-

Verifica se o navegador suporta Shadow DOM e se ele foi criado corretamente.

- -
- - -
-

2️⃣ Isolamento de Estilos Aguardando...

-

Testa se os estilos CSS estão isolados entre a página e o Shadow DOM.

- -
- - -
-

3️⃣ Inspeção do DOM Aguardando...

-

Mostra a estrutura do DOM e do Shadow DOM.

- -
- - -
- - - - - - -
- - -
-

📋 Console de Logs

-
-
- - -
-

💡 Como verificar no DevTools:

-
    -
  1. Abra o DevTools (F12 ou Cmd+Option+I no Mac)
  2. -
  3. Vá para a aba "Elements" ou "Inspetor"
  4. -
  5. Procure por um elemento com #shadow-root (open)
  6. -
  7. Expanda o shadow-root para ver o conteúdo isolado
  8. -
  9. Tente selecionar elementos dentro do Shadow DOM - eles estarão isolados
  10. -
-
-
- - - - - - \ No newline at end of file + result.style.display = "block"; + log("✅ Isolamento de estilos funcionando", "success"); + } else { + status.className = "status warning"; + status.textContent = "Parcial"; + result.innerHTML = + "⚠️ Elemento root não encontrado no Shadow DOM."; + result.style.display = "block"; + log("⚠️ Elemento root não encontrado", "warning"); + } + } + + function testDOMInspection() { + log("Test 3: Inspecionando estrutura do DOM...", "info"); + const status = document.getElementById("status-3"); + const result = document.getElementById("result-3"); + + const container = document.querySelector( + '[id^="__payment_widget_root__"]', + ); + + if (!container) { + status.className = "status warning"; + status.textContent = "Widget não encontrado"; + result.innerHTML = + "⚠️ Widget não inicializado."; + result.style.display = "block"; + log("⚠️ Widget não encontrado para inspeção", "warning"); + return; + } + + let structure = `Estrutura do DOM:

`; + structure += `div#${container.id} (Container principal)
`; + + if (container.shadowRoot) { + structure += `├─ #shadow-root (${container.shadowRoot.mode})
`; + + const children = Array.from(container.shadowRoot.children); + children.forEach((child, index) => { + const isLast = index === children.length - 1; + const prefix = isLast ? "└─" : "├─"; + structure += `│ ${prefix} ${child.tagName.toLowerCase()}#${child.id || "no-id"}
`; + }); + + log("✅ Estrutura do Shadow DOM mapeada", "success"); + } else { + const iframe = container.querySelector("iframe"); + if (iframe) { + structure += `└─ iframe#${iframe.id} (Fallback)
`; + log( + "✅ Estrutura do iframe fallback mapeada", + "success", + ); + } else { + structure += `└─ Nenhum conteúdo isolado encontrado
`; + log( + "⚠️ Nenhuma estrutura de isolamento encontrada", + "warning", + ); + } + } + + status.className = "status success"; + status.textContent = "✅ Mapeado"; + result.innerHTML = structure; + result.style.display = "block"; + } + + // Initial log + log("🎯 Página de teste do Shadow DOM carregada", "success"); + log('👉 Clique em "Inicializar Widget" para começar', "info"); + + + diff --git a/public/examples/test-simple.html b/public/examples/test-simple.html index 45344c4..8905d00 100644 --- a/public/examples/test-simple.html +++ b/public/examples/test-simple.html @@ -1,91 +1,92 @@ - + + + + + Test Widget CDN + + + - #container { - border: 2px dashed #ccc; - padding: 20px; - margin: 20px 0; - min-height: 50px; - } - - + +

Test Widget

- -

Test Widget

+
+

Widget container

+
-
-

Widget container

-
+ + + - - - + - + - - - \ No newline at end of file + function testClose() { + console.log("Closing widget..."); + if (widget) { + widget.close(); + } else { + alert("Mount widget first!"); + } + } + + + diff --git a/public/examples/test-staging.html b/public/examples/test-staging.html index 5aae6b6..cd74b6c 100644 --- a/public/examples/test-staging.html +++ b/public/examples/test-staging.html @@ -1,550 +1,634 @@ - + + + + + 🧪 Teste Staging S3 - Payment Widget + - - - -
-
-
-
🧪 STAGING ENVIRONMENT
-

Payment Widget - Teste S3 Direto

-

- Testando direto do bucket S3 staging (sem CloudFront)
- Timestamp: -

-
- -
-

📦 Configuração

-

- • Bootstrap URL: - https://cartao-simples-widget-staging.s3.us-east-1.amazonaws.com/widget-bootstrap.v1.min.js
- • Bundle URL: - https://cartao-simples-widget-staging.s3.us-east-1.amazonaws.com/widget.v1.min.js
- • Environment: staging (detectado automaticamente) -

-
- -
- - - -
- -
- - -
-
- -
-
-
-
📊 Console de Debug
- + .status-indicator.info { + background: #63b3ed; + } + + @keyframes pulse { + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } + } + + .loading { + animation: pulse 2s ease-in-out infinite; + } + + + + +
+
+
+
🧪 STAGING ENVIRONMENT
+

Payment Widget - Teste S3 Direto

+

+ Testando direto do bucket S3 staging (sem CloudFront)
+ Timestamp: +

+
+ +
+

📦 Configuração

+

+ • Bootstrap URL: + https://cartao-simples-widget-staging.s3.us-east-1.amazonaws.com/widget-bootstrap.v1.min.js
+ • Bundle URL: + https://cartao-simples-widget-staging.s3.us-east-1.amazonaws.com/widget.v1.min.js
+ • Environment: staging (detectado + automaticamente) +

+
+ +
+ + + +
+ +
+ + +
+
+ +
+
+
+
📊 Console de Debug
+ +
+
+
+
-
-
-
-
- - - - - \ No newline at end of file + + + + diff --git a/public/examples/teste-widget-direto.html b/public/examples/teste-widget-direto.html index 4f73ba1..a52c4b5 100644 --- a/public/examples/teste-widget-direto.html +++ b/public/examples/teste-widget-direto.html @@ -1,269 +1,300 @@ - + - - - - - Teste Widget Direto - CDN - - - - - - - - -
-

🎨 Teste Widget - CDN Direto

-

- Teste de carregamento direto do bundle CDN -

- -
- ⏳ Carregando widget do CDN... -
- -
- -
- - - -
- -

📦 Container do Widget

-
- -

🔍 Debug Info

-
- Aguardando carregamento... -
-
- - - - - + + - - - \ No newline at end of file + document.getElementById("btn-init").disabled = false; + + debugInfo.api = { + methods: Object.keys(window.CartaoSimplesWidget), + hasMount: + typeof window.CartaoSimplesWidget.mount === + "function", + }; + } else { + updateStatus( + "❌ Erro: CartaoSimplesWidget não foi carregado", + "error", + ); + debugInfo.error = + "CartaoSimplesWidget não encontrado no window"; + } + + updateDebug(debugInfo); + } + + function initWidget() { + if (!window.CartaoSimplesWidget) { + alert("❌ CartaoSimplesWidget não disponível"); + return; + } + + try { + const container = document.getElementById("widget-root"); + + updateStatus("🔄 Montando widget no container...", "info"); + + const config = { + orderId: "demo-merchant-123", + primaryColor: "#667eea", + secondaryColor: "#764ba2", + environment: "staging", + onClose: () => { + updateStatus( + "ℹ️ Widget fechado pelo usuário", + "info", + ); + }, + onError: (error) => { + updateStatus(`❌ Erro: ${error.message}`, "error"); + }, + }; + + widgetInstance = window.CartaoSimplesWidget.mount( + container, + config, + ); + + updateStatus( + "✅ Widget inicializado com sucesso!", + "success", + ); + + document.getElementById("btn-open").disabled = false; + document.getElementById("btn-close").disabled = false; + document.getElementById("btn-init").disabled = true; + + updateDebug({ + status: "initialized", + instance: { + hasOpen: typeof widgetInstance?.open === "function", + hasClose: + typeof widgetInstance?.close === "function", + methods: widgetInstance + ? Object.keys(widgetInstance) + : [], + }, + config: config, + }); + } catch (error) { + updateStatus( + `❌ Erro ao inicializar: ${error.message}`, + "error", + ); + console.error("Erro detalhado:", error); + } + } + + function openWidget() { + if (!widgetInstance) { + alert( + '❌ Widget não foi inicializado. Clique em "Inicializar Widget" primeiro.', + ); + return; + } + + try { + widgetInstance.open(); + updateStatus("✅ Widget aberto!", "success"); + } catch (error) { + updateStatus(`❌ Erro ao abrir: ${error.message}`, "error"); + } + } + + function closeWidget() { + if (!widgetInstance) { + alert("❌ Widget não foi inicializado."); + return; + } + + try { + widgetInstance.close(); + updateStatus("ℹ️ Widget fechado", "info"); + } catch (error) { + updateStatus( + `❌ Erro ao fechar: ${error.message}`, + "error", + ); + } + } + + // Verificar carregamento após um delay + window.addEventListener("load", () => { + setTimeout(checkWidgetLoaded, 500); + }); + + + diff --git a/public/examples/vue-integration.js b/public/examples/vue-integration.js index 8672321..ad95dc9 100644 --- a/public/examples/vue-integration.js +++ b/public/examples/vue-integration.js @@ -1,6 +1,15 @@ - @@ -36,8 +45,8 @@ let widgetInstance = null // Configuração do widget const widgetConfig = { - merchantId: import.meta.env.VITE_CARTAO_SIMPLES_MERCHANT_ID, - primaryColor: '#FF6600', + orderId: import.meta.env.VITE_CARTAO_SIMPLES_MERCHANT_ID, + primaryColor: '#3a36f5', secondaryColor: '#0A0A0A', logoUrl: '/logo.png', environment: import.meta.env.VITE_APP_ENV === 'production' ? 'production' : 'staging', @@ -65,10 +74,10 @@ const loadWidget = () => { const openPaymentWidget = async () => { try { loading.value = true - + // Carregar widget se ainda não estiver carregado await loadWidget() - + // Inicializar widget widgetInstance = window.PaymentWidget.init({ ...widgetConfig, @@ -77,10 +86,10 @@ const openPaymentWidget = async () => { onOpen: handleWidgetOpen, onClose: handleWidgetClose }) - + // Abrir widget widgetInstance.open() - + } catch (error) { console.error('Erro ao carregar widget:', error) handlePaymentError({ message: 'Erro ao carregar widget de pagamento' }) @@ -91,7 +100,7 @@ const openPaymentWidget = async () => { const handlePaymentSuccess = (data) => { console.log('Pagamento realizado com sucesso:', data) - + // Redirecionar para página de sucesso router.push({ name: 'PaymentSuccess', @@ -105,11 +114,11 @@ const handlePaymentSuccess = (data) => { const handlePaymentError = (error) => { console.error('Erro no pagamento:', error) - + // Mostrar notificação de erro // Aqui você pode usar sua biblioteca de notificações favorita alert(`Erro: ${error.message}`) - + // Ou redirecionar para página de erro router.push({ name: 'PaymentError', @@ -235,14 +244,14 @@ onMounted(async () => { .payment-integration { padding: 16px; } - + .product-card { padding: 20px; } - + .payment-button { font-size: 14px; padding: 14px 20px; } } - \ No newline at end of file + diff --git a/src/App.tsx b/src/App.tsx index 290e112..b387341 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { PaymentWidget } from "./components/PaymentWidget"; +import { PaymentWidget } from "./components/payment-widget"; import type { WidgetConfig } from "./types"; import "./styles/widget.css"; @@ -7,11 +7,11 @@ function App() { const [isWidgetOpen, setIsWidgetOpen] = useState(false); const widgetConfig: WidgetConfig = { - merchantId: "demo-merchant-123", + orderId: "demo-merchant-123", theme: { - primaryColor: "#ff6600", + primaryColor: "#3a36f5", secondaryColor: "#0a0a0a", - borderRadius: "md", + borderRadius: "12px", }, logoUrl: "https://www.cartaosimples.com.br/logo-extendida.svg", environment: "staging", diff --git a/src/bootstrap/index.ts b/src/bootstrap/index.ts index b2d3318..940f86d 100644 --- a/src/bootstrap/index.ts +++ b/src/bootstrap/index.ts @@ -48,20 +48,18 @@ class PaymentWidgetBootstrap { async init(config: WidgetConfig): Promise { try { // Valida configuração mínima - if (!config.merchantId) { - throw new Error("merchantId é obrigatório"); + if (!config.orderId) { + throw new Error("orderId é obrigatório"); } - // Verifica se já existe uma instância para este merchantId - if (this.state.instances.has(config.merchantId)) { - logger.warn( - `Widget já inicializado para merchantId: ${config.merchantId}` - ); + // Verifica se já existe uma instância para este orderId + if (this.state.instances.has(config.orderId)) { + logger.warn(`Widget já inicializado para orderId: ${config.orderId}`); return; } // Cria container isolado - const container = this.createContainer(config.merchantId); + const container = this.createContainer(config.orderId); // Cria Shadow DOM (com fallback para iframe) const shadowRoot = this.createShadowDOM(container); @@ -74,7 +72,7 @@ class PaymentWidgetBootstrap { api: null, }; - this.state.instances.set(config.merchantId, instance); + this.state.instances.set(config.orderId, instance); // Carrega UI bundle assincronamente se necessário await this.loadUIBundle(); @@ -84,7 +82,7 @@ class PaymentWidgetBootstrap { // Auto-open se configurado if (config.autoOpen) { - this.open(config.merchantId); + this.open(config.orderId); } // Callback onOpen se definido @@ -105,19 +103,17 @@ class PaymentWidgetBootstrap { /** * Abre o modal do widget */ - open(merchantId?: string): void { - const targetMerchantId = merchantId || this.getFirstMerchantId(); + open(orderId?: string): void { + const targetOrderId = orderId || this.getFirstOrderId(); - if (!targetMerchantId) { + if (!targetOrderId) { logger.error("Nenhum widget inicializado"); return; } - const instance = this.state.instances.get(targetMerchantId); + const instance = this.state.instances.get(targetOrderId); if (!instance?.api) { - logger.error( - `Widget não encontrado para merchantId: ${targetMerchantId}` - ); + logger.error(`Widget não encontrado para orderId: ${targetOrderId}`); return; } @@ -130,14 +126,14 @@ class PaymentWidgetBootstrap { /** * Fecha o modal do widget */ - close(merchantId?: string): void { - const targetMerchantId = merchantId || this.getFirstMerchantId(); + close(orderId?: string): void { + const targetOrderId = orderId || this.getFirstOrderId(); - if (!targetMerchantId) { + if (!targetOrderId) { return; } - const instance = this.state.instances.get(targetMerchantId); + const instance = this.state.instances.get(targetOrderId); if (instance?.api) { instance.api.close(); @@ -149,14 +145,14 @@ class PaymentWidgetBootstrap { /** * Destroi uma instância do widget */ - destroy(merchantId?: string): void { - const targetMerchantId = merchantId || this.getFirstMerchantId(); + destroy(orderId?: string): void { + const targetOrderId = orderId || this.getFirstOrderId(); - if (!targetMerchantId) { + if (!targetOrderId) { return; } - const instance = this.state.instances.get(targetMerchantId); + const instance = this.state.instances.get(targetOrderId); if (instance) { // Cleanup da instância if (instance.api?.destroy) { @@ -169,15 +165,15 @@ class PaymentWidgetBootstrap { } // Remove da state - this.state.instances.delete(targetMerchantId); + this.state.instances.delete(targetOrderId); } } /** * Cria container isolado no DOM */ - private createContainer(merchantId: string): HTMLElement { - const containerId = `${CONTAINER_ID_PREFIX}${merchantId}`; + private createContainer(orderId: string): HTMLElement { + const containerId = `${CONTAINER_ID_PREFIX}${orderId}`; // Verifica se já existe const existing = document.getElementById(containerId); @@ -395,9 +391,9 @@ class PaymentWidgetBootstrap { } /** - * Obtém o primeiro merchantId disponível + * Obtém o primeiro orderId disponível */ - private getFirstMerchantId(): string | undefined { + private getFirstOrderId(): string | undefined { return Array.from(this.state.instances.keys())[0]; } @@ -405,9 +401,9 @@ class PaymentWidgetBootstrap { * Obtém o estado público do widget */ getState(): WidgetState { - const firstMerchantId = this.getFirstMerchantId(); - const instance = firstMerchantId - ? this.state.instances.get(firstMerchantId) + const firstOrderId = this.getFirstOrderId(); + const instance = firstOrderId + ? this.state.instances.get(firstOrderId) : null; // Obtém estado da API do widget @@ -441,9 +437,9 @@ const bootstrap = new PaymentWidgetBootstrap(); // API pública global window.PaymentWidget = { init: (config: WidgetConfig) => bootstrap.init(config), - open: (merchantId?: string) => bootstrap.open(merchantId), - close: (merchantId?: string) => bootstrap.close(merchantId), - destroy: (merchantId?: string) => bootstrap.destroy(merchantId), + open: (orderId?: string) => bootstrap.open(orderId), + close: (orderId?: string) => bootstrap.close(orderId), + destroy: (orderId?: string) => bootstrap.destroy(orderId), getState: () => bootstrap.getState(), }; @@ -453,7 +449,7 @@ document.addEventListener("DOMContentLoaded", () => { const currentScript = document.currentScript as HTMLScriptElement; if (currentScript) { const config = extractConfigFromDataAttributes(currentScript); - if (config.merchantId) { + if (config.orderId) { bootstrap.init(config); } } @@ -473,7 +469,7 @@ function extractConfigFromDataAttributes( const dataset = script.dataset; return { - merchantId: dataset.merchantId || "", + orderId: dataset.orderId || "", theme: { primaryColor: dataset.primary || dataset.primaryColor, secondaryColor: dataset.secondary || dataset.secondaryColor, diff --git a/src/cdn/index.ts b/src/cdn/index.ts index bebf620..126764f 100644 --- a/src/cdn/index.ts +++ b/src/cdn/index.ts @@ -1,10 +1,10 @@ // CDN Entry Point - Bundle completo com React incluído import React from "react"; import ReactDOM from "react-dom/client"; -import { PaymentWidget } from "../components/PaymentWidget"; import type { WidgetConfig } from "../types"; import "../styles/widget.css"; import "../styles/widget-native.css"; +import { PaymentWidget } from "@/components/payment-widget"; // Garante que React está disponível globalmente ( diff --git a/src/components/icons/amex.tsx b/src/components/icons/amex.tsx new file mode 100644 index 0000000..37f5d4b --- /dev/null +++ b/src/components/icons/amex.tsx @@ -0,0 +1,18 @@ +export const AmexIcon = () => ( + + + + +); diff --git a/src/components/icons/diners.tsx b/src/components/icons/diners.tsx new file mode 100644 index 0000000..8bc6c4b --- /dev/null +++ b/src/components/icons/diners.tsx @@ -0,0 +1,26 @@ +export const DinersIcon = () => ( + + + + + + +) diff --git a/src/components/icons/elo.tsx b/src/components/icons/elo.tsx new file mode 100644 index 0000000..8589c33 --- /dev/null +++ b/src/components/icons/elo.tsx @@ -0,0 +1,27 @@ +export const EloIcon = () => ( + + + + + + + +) diff --git a/src/components/icons/hipercard.tsx b/src/components/icons/hipercard.tsx new file mode 100644 index 0000000..ed4419b --- /dev/null +++ b/src/components/icons/hipercard.tsx @@ -0,0 +1,18 @@ +export const HipercardIcon = () => ( + + + + +) diff --git a/src/components/icons/mastercard.tsx b/src/components/icons/mastercard.tsx new file mode 100644 index 0000000..869f745 --- /dev/null +++ b/src/components/icons/mastercard.tsx @@ -0,0 +1,34 @@ +export const MastercardIcon = () => ( + + + + + + + + + + + + + + +); diff --git a/src/components/icons/visa.tsx b/src/components/icons/visa.tsx new file mode 100644 index 0000000..433e146 --- /dev/null +++ b/src/components/icons/visa.tsx @@ -0,0 +1,25 @@ +export const VisaIcon = () => ( + + + + + + + + + + + +); diff --git a/src/components/PaymentModal.tsx b/src/components/payment-modal.tsx similarity index 97% rename from src/components/PaymentModal.tsx rename to src/components/payment-modal.tsx index 8d5ef22..53205f1 100644 --- a/src/components/PaymentModal.tsx +++ b/src/components/payment-modal.tsx @@ -1,7 +1,7 @@ import { AnimatePresence, motion } from "framer-motion"; import type { ReactNode } from "react"; import { useEffect, useRef } from "react"; -import { useTheme } from "./ThemeProvider"; +import { useTheme } from "./theme-provider"; const FOCUS_DELAY_MS = 100; @@ -138,7 +138,7 @@ export function PaymentModal({