diff --git a/AoSelecionarPix.png b/AoSelecionarPix.png new file mode 100644 index 0000000..026d694 Binary files /dev/null and b/AoSelecionarPix.png differ diff --git a/AoSelecionarPixDesativado.png b/AoSelecionarPixDesativado.png new file mode 100644 index 0000000..c963a5d Binary files /dev/null and b/AoSelecionarPixDesativado.png differ diff --git a/AoSelecionarWifi.png b/AoSelecionarWifi.png new file mode 100644 index 0000000..2558141 Binary files /dev/null and b/AoSelecionarWifi.png differ diff --git a/Content/Tutoriais/como-criar-qr-code-pix.pt-BR.md b/Content/Tutoriais/como-criar-qr-code-pix.pt-BR.md new file mode 100644 index 0000000..909abd8 --- /dev/null +++ b/Content/Tutoriais/como-criar-qr-code-pix.pt-BR.md @@ -0,0 +1,89 @@ +--- +title: "Como Criar QR Code PIX Estático" +description: "Aprenda a criar um QR Code PIX estático para receber pagamentos instantâneos de forma fácil e segura. Guia completo com o QR Rapido." +keywords: "qr code pix, gerar qr code pix, pix estático, criar qr code pagamento, pix qr code gratuito, qr code pix sem taxa" +author: "QR Rapido" +date: 2026-01-24 +lastmod: 2026-01-24 +image: "/images/tutoriais/pix-qr-hero.jpg" +--- + +# Como Criar QR Code PIX Estático: O Guia Completo + +O **PIX** revolucionou a forma como fazemos pagamentos no Brasil. E para quem vende ou recebe doações, o **QR Code PIX** é a ferramenta essencial. Neste tutorial, você vai aprender como gerar um **QR Code PIX Estático** gratuitamente usando o **QR Rapido**, garantindo agilidade e segurança nas suas transações. + +## 💸 O que é o QR Code PIX Estático? + +O QR Code Estático do PIX é ideal para quem deseja receber múltiplos pagamentos de um mesmo valor ou valores variados usando um único código. Ele "aponta" sempre para a mesma conta bancária (sua chave PIX) e pode conter informações adicionais como o nome do recebedor e a cidade. + +**Principais vantagens:** +- **Não expira:** Use o mesmo código indefinidamente. +- **Sem taxas:** A geração é gratuita e não depende de intermediários (gateways). +- **Versátil:** Pode ter um valor fixo definido ou deixar o valor em aberto para o pagador preencher. +- **Ideal para:** Lojistas, autônomos, doações, vaquinhas e prestadores de serviço. + +## 🎯 Passo a Passo para Gerar no QR Rapido + +O **QR Rapido** agora possui uma ferramenta nativa e segura para gerar seu código PIX. Siga os passos: + +### 1. Acesse o Gerador + +Abra o [QR Rapido](https://qrrapido.site) e no menu de tipos de QR Code, selecione a opção **"💸 PIX"**. + +### 2. Preencha os Dados Obrigatórios + +Para que o código funcione em qualquer banco, o padrão do Banco Central exige três informações: + +- **Chave PIX:** Pode ser seu CPF, CNPJ, Email, Celular ou Chave Aleatória. +- **Nome do Beneficiário:** Seu nome ou da sua empresa (deve ser o mesmo da conta bancária). +- **Cidade:** A cidade onde a conta foi aberta ou onde você reside. + +> **⚠️ Atenção:** Preencha os dados exatamente como estão cadastrados no seu banco para evitar erros na hora do pagamento. + +### 3. Defina Valor e Descrição (Opcional) + +- **Valor:** Se você vende um produto de preço fixo (ex: "Pastel R$ 10,00"), preencha o campo de valor. Se for uma doação ou pagamento variável, deixe em branco para que o cliente digite o valor. +- **Identificador (TxID):** Um código opcional para você identificar o pagamento no seu extrato (ex: "RIFA01"). Se não preencher, o sistema usará o padrão `***`. +- **Descrição:** Uma mensagem que pode aparecer na tela de confirmação do pagador (ex: "Pagamento Serviços"). + +### 4. Gere e Personalize + +Clique em **"Gerar QR Code"**. Você pode personalizar a cor e o estilo (cantos arredondados, por exemplo) para combinar com sua marca, mas lembre-se: **Mantenha o contraste alto** (preferencialmente preto no branco) para garantir que qualquer celular consiga ler. + +### 5. Faça o Download + +Baixe a imagem em **PNG** para usar nas redes sociais ou **PDF/SVG** para imprimir com alta qualidade em placas e cartazes. + +## 🏪 Dica para Lojistas e Vendedores + +Se você vende **muitos produtos diferentes** e quer agilizar o pagamento no caixa ou nas prateleiras, o QR Rapido tem a solução ideal: + +> **Assine nosso Plano Mensal!** +> Com o plano Premium, você pode criar e gerenciar um **QR Code exclusivo para cada produto** do seu catálogo. Assim, o cliente escaneia o código do produto específico e o valor já aparece preenchido corretamente, evitando erros e agilizando a venda. [Saiba mais sobre o plano Premium](/Pagamento/SelecaoPlano). + +## 💡 Dicas Profissionais para seu PIX + +### Imprima com Qualidade +Se for colocar o QR Code no balcão da sua loja, imprima em tamanho legível (pelo menos 5x5 cm). Proteja o papel com plastificação ou use um display de acrílico. + +### Teste Antes de Divulgar +Antes de imprimir 1000 panfletos ou postar no Instagram, faça um teste real! Abra o app do seu banco, leia o QR Code gerado e transfira um valor simbólico (R$ 0,01) para garantir que os dados estão corretos e o dinheiro cai na conta certa. + +### Segurança +O QR Rapido gera o código diretamente no seu navegador. Nós **não** temos acesso à sua conta bancária e **não** intermediamos o dinheiro. O pagamento vai direto do cliente para sua conta. + +## 🚀 Por que usar o QR Rapido para PIX? + +- **Conformidade EMVCo:** Utilizamos o padrão oficial internacional, garantindo compatibilidade com NuBank, Itaú, Bradesco, Inter, Banco do Brasil e todos os outros. +- **Privacidade:** Seus dados sensíveis não são armazenados. +- **Velocidade:** Gere seu código em segundos, sem cadastros demorados. + +## Conclusão + +Ter um QR Code PIX à mão agiliza o atendimento e passa profissionalismo. Com o **QR Rapido**, você cria o seu de graça, personaliza e já sai recebendo. + +**Pronto para receber pagamentos?** [Gere seu QR Code PIX agora →](https://qrrapido.site) + +--- + +*Dúvidas sobre a geração? [Fale conosco](https://qrrapido.site/pt-BR/Contact) ou consulte nosso FAQ.* diff --git a/Controllers/AccountController.cs b/Controllers/AccountController.cs index 8459cb3..a5ce319 100644 --- a/Controllers/AccountController.cs +++ b/Controllers/AccountController.cs @@ -131,6 +131,12 @@ namespace QRRapidoApp.Controllers if (stateData.Timestamp > maxAge) { returnUrl = stateData.ReturnUrl ?? "/"; + + // Prevent redirect loop to login page + if (returnUrl.Contains("/Account/Login", StringComparison.OrdinalIgnoreCase)) + { + returnUrl = "/"; + } } else { diff --git a/Resources/SharedResource.es.resx b/Resources/SharedResource.es.resx index 621ee8a..3c0a60f 100644 --- a/Resources/SharedResource.es.resx +++ b/Resources/SharedResource.es.resx @@ -88,6 +88,66 @@ Email + + PIX + + + Crea un código QR PIX estático para recibir pagos instantáneos (Brasil). + + + Clave PIX + + + Tipo de Clave + + + Celular + + + Aleatoria + + + Ingresa tu clave + + + Nombre del Beneficiario + + + Ciudad del Beneficiario + + + Monto (Opcional) + + + Descripción (Opcional) + + + Identificador (TxID) - Opcional + + + Selecciona "PIX" en el menú + + + Ingresa tu Clave PIX, Nombre y Ciudad (Obligatorios) + + + Define un monto si deseas recibir una cantidad fija + + + Recibir pagos en tiendas físicas + + + Donaciones y recaudaciones + + + Siempre prueba el código QR transfiriendo un monto pequeño primero + + + Payload PIX Generado + + + 💸 Para PIX, completa la clave, nombre y ciudad del receptor + QR Dinamico (Premium) diff --git a/Resources/SharedResource.pt-BR.resx b/Resources/SharedResource.pt-BR.resx index 3b9c1b2..6163c52 100644 --- a/Resources/SharedResource.pt-BR.resx +++ b/Resources/SharedResource.pt-BR.resx @@ -147,6 +147,66 @@ Email + + PIX + + + Crie um QR Code PIX estático para receber pagamentos instantâneos. Funciona em qualquer app de banco. + + + Chave PIX + + + Tipo de Chave + + + Celular + + + Aleatória + + + Digite sua chave + + + Nome do Beneficiário + + + Cidade do Beneficiário + + + Valor (Opcional) + + + Descrição (Opcional) + + + Identificador (TxID) - Opcional + + + Selecione "PIX" no menu + + + Insira sua Chave PIX, Nome e Cidade (Obrigatórios) + + + Defina um valor se desejar receber uma quantia fixa + + + Receber pagamentos em lojas físicas + + + Doações e arrecadações + + + Sempre teste o QR Code transferindo um valor pequeno primeiro + + + Payload PIX Gerado + + + 💸 Para PIX, preencha a chave, nome e cidade do recebedor + QR Dinamico (Premium) diff --git a/Views/Account/Login.cshtml b/Views/Account/Login.cshtml index 93a2ca6..c9df6a7 100644 --- a/Views/Account/Login.cshtml +++ b/Views/Account/Login.cshtml @@ -18,22 +18,13 @@

@Localizer["LoginAndGet"]

-
-
-
- @Localizer["ThirtyDaysNoAds"] -
-
-
-
- @Localizer["FiftyQRCodesPerDay"] -
-
-
-
- @Localizer["QRCodeHistory"] -
-
+ +
+
    +
  • @Localizer["ThirtyDaysNoAds"]
  • +
  • @Localizer["FiftyQRCodesPerDay"]
  • +
  • @Localizer["QRCodeHistory"]
  • +
diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml index 3165967..4c38b06 100644 --- a/Views/Home/Index.cshtml +++ b/Views/Home/Index.cshtml @@ -83,6 +83,7 @@ + + + + + +
+
+ + +
+
+ +
+
+ + +
Máximo 25 caracteres (sem acentos recomendado)
+
+
+ + +
Máximo 15 caracteres (Padrão BACEN)
+
+
+ +
+
+ +
+ R$ + +
+
Deixe em branco para valor livre
+
+
+ + +
Identificador da transação (padrão: ***)
+
+
+ +
+ + +
Alguns bancos exibem esta mensagem na tela de confirmação
+
+ +
+
+ @Localizer["PixPreviewTitle"] +
+
+
+
Preencha os campos obrigatórios para gerar o payload...
+
+ + +

-

@@ -958,6 +1028,45 @@
+ +
+

+ +

+
+
+
+
+
@Localizer["WhatIsIt"]
+

@Localizer["PIXQRDescription"]

+ +
@Localizer["HowToUse"]
+
    +
  1. @Localizer["PixStep1"]
  2. +
  3. @Localizer["PixStep2"]
  4. +
  5. @Localizer["PixStep3"]
  6. +
+
+
+
@Localizer["Tips"]
+
    +
  • @Localizer["PixTip1"]
  • +
+ +
@Localizer["UseCases"]
+
    +
  • @Localizer["PixUseCase1"]
  • +
  • @Localizer["PixUseCase2"]
  • +
+
+
+
+
+
+

@@ -1340,14 +1449,15 @@ const previewColorizeText = document.getElementById('preview-colorize-text'); new SimpleOpacityController('#qr-type', '#quick-style-group'); - new SimpleOpacityController('#qr-type', '#content-group'); + // new SimpleOpacityController('#qr-type', '#content-group'); // Reverted to avoid confusion new SimpleOpacityController('#qr-type', '#dynamic-qr-section'); new SimpleOpacityController('#qr-type', '#url-preview'); new SimpleOpacityController('#qr-type', '#vcard-interface'); new SimpleOpacityController('#qr-type', '#wifi-interface'); new SimpleOpacityController('#qr-type', '#sms-interface'); new SimpleOpacityController('#qr-type', '#email-interface'); - new SimpleOpacityController('#qr-type', '#navigation-buttons'); + new SimpleOpacityController('#qr-type', '#pix-interface'); + // new SimpleOpacityController('#qr-type', '#navigation-buttons'); // Element Removed new SimpleOpacityController('#qr-type', '#customization-accordion'); new SimpleOpacityController('#qr-type', '#button-gerar-div'); diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index 07c2e50..56fea90 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -457,7 +457,7 @@ - @if (isDevelopment) + @if (isDevelopment || true) // FORCE INDIVIDUAL SCRIPTS FOR NOW TO ENSURE CHANGES ARE VISIBLE { @@ -466,6 +466,7 @@ + } else { diff --git a/crc_test.js b/crc_test.js new file mode 100644 index 0000000..7efab1b --- /dev/null +++ b/crc_test.js @@ -0,0 +1,40 @@ +function generateCRC16(payload) { + let crc = 0xFFFF; + for (let i = 0; i < payload.length; i++) { + let c = payload.charCodeAt(i); + crc ^= (c << 8); + for (let j = 0; j < 8; j++) { + if ((crc & 0x8000) !== 0) { + crc = (crc << 1) ^ 0x1021; + } else { + crc = crc << 1; + } + } + // Force 16-bit behavior for JS integers + crc = crc & 0xFFFF; + } + return crc.toString(16).toUpperCase().padStart(4, '0'); +} + +// Previous implementation to check if it matches the user's output +function generateCRC16_Old(payload) { + let crc = 0xFFFF; + for (let i = 0; i < payload.length; i++) { + let c = payload.charCodeAt(i); + crc ^= (c << 8); + for (let j = 0; j < 8; j++) { + if ((crc & 0x8000) !== 0) { + crc = (crc << 1) ^ 0x1021; + } else { + crc = crc << 1; + } + } + } + return (crc & 0xFFFF).toString(16).toUpperCase().padStart(4, '0'); +} + +const payload = "00020126330014br.gov.bcb.pix0111119615342255204000053039865802BR5916Ricardo Carneiro6012Sao Bernardo62070503***6304"; +console.log("Payload:", payload); +console.log("CRC New (Masked):", generateCRC16(payload)); +console.log("CRC Old (Original):", generateCRC16_Old(payload)); +console.log("User Reported CRC:", "B716"); diff --git a/crc_verify_new.js b/crc_verify_new.js new file mode 100644 index 0000000..e77f0eb --- /dev/null +++ b/crc_verify_new.js @@ -0,0 +1,21 @@ +function generateCRC16(payload) { + let crc = 0xFFFF; + for (let i = 0; i < payload.length; i++) { + let c = payload.charCodeAt(i); + crc ^= (c << 8); + for (let j = 0; j < 8; j++) { + if ((crc & 0x8000) !== 0) { + crc = (crc << 1) ^ 0x1021; + } else { + crc = crc << 1; + } + } + crc = crc & 0xFFFF; + } + return crc.toString(16).toUpperCase().padStart(4, '0'); +} + +const payload = "00020126360014br.gov.bcb.pix0114+5511961534225520400005303986540520.005802BR5917Ricardo Goncalves6021Sao Bernardo do campo62070503***6304"; +console.log("Payload:", payload); +console.log("Calculated CRC:", generateCRC16(payload)); +console.log("Expected CRC:", "407E"); diff --git a/wwwroot/js/pix-generator.js b/wwwroot/js/pix-generator.js new file mode 100644 index 0000000..99f8f5f --- /dev/null +++ b/wwwroot/js/pix-generator.js @@ -0,0 +1,210 @@ +class PixQRGenerator { + constructor() { + this.initializePixInterface(); + } + + initializePixInterface() { + // Listeners for realtime preview + const fieldsToWatch = [ + 'pix-key', + 'pix-key-type', + 'pix-name', + 'pix-city', + 'pix-amount', + 'pix-description', + 'pix-txid' + ]; + + fieldsToWatch.forEach(fieldId => { + const element = document.getElementById(fieldId); + if (element) { + element.addEventListener('input', () => this.updatePreview()); + element.addEventListener('change', () => this.updatePreview()); + } + }); + + // Add currency mask to amount field + const amountInput = document.getElementById('pix-amount'); + if (amountInput) { + amountInput.addEventListener('input', (e) => { + let value = e.target.value.replace(/\D/g, ''); + if (value) { + value = (parseInt(value) / 100).toFixed(2) + ''; + value = value.replace('.', ','); + value = value.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1.'); + e.target.value = value; + } + this.updatePreview(); + }); + } + } + + updatePreview() { + const data = this.collectPixData(); + const previewElement = document.getElementById('pix-preview-text'); + + if (previewElement) { + if (!data.key || !data.name || !data.city) { + previewElement.textContent = "Preencha os campos obrigatórios (Chave, Nome, Cidade) para ver o preview."; + return; + } + + try { + const payload = this.generatePixPayload(); + previewElement.textContent = payload; + } catch (e) { + previewElement.textContent = "Erro ao gerar payload: " + e.message; + } + } + } + + collectPixData() { + return { + keyType: document.getElementById('pix-key-type')?.value || 'cpf', + key: document.getElementById('pix-key')?.value || '', + name: document.getElementById('pix-name')?.value || '', + city: document.getElementById('pix-city')?.value || '', + amount: document.getElementById('pix-amount')?.value || '', + description: document.getElementById('pix-description')?.value || '', + txid: document.getElementById('pix-txid')?.value || '' + }; + } + + validatePixData() { + const data = this.collectPixData(); + const errors = []; + + if (!data.key.trim()) errors.push('Chave PIX é obrigatória'); + if (!data.name.trim()) errors.push('Nome do beneficiário é obrigatório'); + if (!data.city.trim()) errors.push('Cidade do beneficiário é obrigatória'); + + if (data.name.length > 25) errors.push('Nome deve ter no máximo 25 caracteres'); + if (data.city.length > 15) errors.push('Cidade deve ter no máximo 15 caracteres (Regra do Banco Central)'); + + if (data.amount) { + // Remove thousands separators (.) and replace decimal comma (,) with dot (.) + const amount = parseFloat(data.amount.replace(/\./g, '').replace(',', '.')); + if (isNaN(amount) || amount <= 0) { + errors.push('Valor deve ser um número maior que zero'); + } + } + + return errors; + } + + generateCRC16(payload) { + let crc = 0xFFFF; + const polynomial = 0x1021; + + for (let i = 0; i < payload.length; i++) { + crc ^= payload.charCodeAt(i) << 8; + for (let j = 0; j < 8; j++) { + if ((crc & 0x8000) !== 0) { + crc = ((crc << 1) ^ polynomial) & 0xFFFF; + } else { + crc = (crc << 1) & 0xFFFF; + } + } + } + + return crc.toString(16).toUpperCase().padStart(4, '0'); + } + + formatField(id, value) { + const valStr = value.toString(); + const len = valStr.length.toString().padStart(2, '0'); + return `${id}${len}${valStr}`; + } + + formatKey(type, key) { + if (!key) return ''; + const cleanKey = key.trim(); + + switch(type) { + case 'phone': + // Remove non-digits + let nums = cleanKey.replace(/\D/g, ''); + // If it starts with 55 and is long enough, keep it + // If 10 or 11 digits (DDD+Number), add +55 + if ((nums.length === 10 || nums.length === 11) && !nums.startsWith('55')) { + return `+55${nums}`; + } + // If user typed 55..., ensure + + if (nums.startsWith('55') && !cleanKey.startsWith('+')) { + return `+${nums}`; + } + // If user typed +..., keep it + if (cleanKey.startsWith('+')) { + return cleanKey; // Assume correct + } + return `+${nums}`; // Fallback + + case 'cpf': + case 'cnpj': + // Numbers only + return cleanKey.replace(/\D/g, ''); + + case 'email': + return cleanKey.toLowerCase(); + + case 'random': + default: + return cleanKey; + } + } + + generatePixPayload() { + const data = this.collectPixData(); + + // Format key based on type + const key = this.formatKey(data.keyType, data.key); + + const name = this.removeAccents(data.name.trim()).substring(0, 25); + const city = this.removeAccents(data.city.trim()).substring(0, 15); // Standard limit 15 chars + const amount = data.amount ? parseFloat(data.amount.replace(/\./g, '').replace(',', '.')).toFixed(2) : null; + const description = this.removeAccents(data.description.trim()) || ''; + const txid = this.removeAccents(data.txid.trim()) || '***'; + + let payload = this.formatField('00', '01'); + + const gui = this.formatField('00', 'br.gov.bcb.pix'); + const keyField = this.formatField('01', key); + + // Field 26: Merchant Account Information + // Subfields: 00 (GUI), 01 (Key), 02 (Description - optional/standard varies) + // Standard EMVCo for PIX puts Description in field 02 of ID 26? + // Actually, BR Code standard puts "InfoAdicional" in Field 26, ID 02. + + let merchantAccountInfo = gui + keyField; + if (description) { + merchantAccountInfo += this.formatField('02', description); + } + payload += this.formatField('26', merchantAccountInfo); + + payload += this.formatField('52', '0000'); // Merchant Category Code + payload += this.formatField('53', '986'); // Transaction Currency (BRL) + + if (amount) { + payload += this.formatField('54', amount); // Transaction Amount + } + + payload += this.formatField('58', 'BR'); // Country Code + payload += this.formatField('59', name); // Merchant Name + payload += this.formatField('60', city); // Merchant City + + // Field 62: Additional Data Field Template + // Subfields: 05 (Reference Label / TxID) + const txidField = this.formatField('05', txid); + payload += this.formatField('62', txidField); + + payload += '6304'; // CRC16 ID + Length + + const crc = this.generateCRC16(payload); + + return payload + crc; + } + + removeAccents(str) { + return str.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); + } +} \ No newline at end of file diff --git a/wwwroot/js/qr-speed-generator.js b/wwwroot/js/qr-speed-generator.js index 80bb6be..c376e2a 100644 --- a/wwwroot/js/qr-speed-generator.js +++ b/wwwroot/js/qr-speed-generator.js @@ -154,7 +154,8 @@ class QRRapidoGenerator { const fieldsToWatch = [ 'qr-content', 'vcard-name', 'vcard-mobile', 'vcard-email', 'wifi-ssid', 'wifi-password', 'sms-number', 'sms-message', - 'email-to', 'email-subject', 'email-body' + 'email-to', 'email-subject', 'email-body', + 'pix-key', 'pix-name', 'pix-city', 'pix-amount' ]; fieldsToWatch.forEach(id => { const el = document.getElementById(id); @@ -188,24 +189,37 @@ class QRRapidoGenerator { saveBtn.addEventListener('click', this.saveToHistory.bind(this)); } - // Next button navigation - const nextBtn = document.getElementById('next-btn'); - if (nextBtn) { - nextBtn.addEventListener('click', this.handleNextButtonClick.bind(this)); - } - - // Generate quick button (same functionality as main generate button) - const generateQuickBtn = document.getElementById('generate-quick-btn'); - if (generateQuickBtn) { - generateQuickBtn.addEventListener('click', (e) => { - e.preventDefault(); - this.generateQRWithTimer(e); - }); - } + // Accordion State Persistence + this.initializeAccordionState(); this.setupUrlFieldHandlers(); } + initializeAccordionState() { + const accordionBtn = document.getElementById('btn-customization-toggle'); + const accordionPanel = document.getElementById('customization-panel'); + + if (accordionBtn && accordionPanel) { + // Check saved state + const isOpen = localStorage.getItem('qr_customization_open') === 'true'; + + if (isOpen) { + accordionBtn.classList.remove('collapsed'); + accordionBtn.setAttribute('aria-expanded', 'true'); + accordionPanel.classList.add('show'); + } + + // Save state on toggle + accordionPanel.addEventListener('shown.bs.collapse', () => { + localStorage.setItem('qr_customization_open', 'true'); + }); + + accordionPanel.addEventListener('hidden.bs.collapse', () => { + localStorage.setItem('qr_customization_open', 'false'); + }); + } + } + setupUrlFieldHandlers() { const contentField = document.getElementById('qr-content'); if (!contentField) return; @@ -661,6 +675,13 @@ class QRRapidoGenerator { return false; } return true; + } else if (qrType === 'pix') { + const errors = window.pixGenerator.validatePixData(); + if (errors.length > 0) { + this.showError(errors.join('
')); + return false; + } + return true; } // Normal validation for other types @@ -720,6 +741,9 @@ class QRRapidoGenerator { } else if (type === 'email') { content = window.emailGenerator.generateEmailString(); actualType = 'text'; + } else if (type === 'pix') { + content = window.pixGenerator.generatePixPayload(); + actualType = 'text'; // Pix is treated as text } else if (type === 'vcard') { if (!window.vcardGenerator) { throw new Error('VCard generator não está disponível'); @@ -880,6 +904,8 @@ class QRRapidoGenerator { return window.smsGenerator?.generateSMSString() || ''; } else if (type === 'email') { return window.emailGenerator?.generateEmailString() || ''; + } else if (type === 'pix') { + return window.pixGenerator?.generatePixPayload() || ''; } else { return document.getElementById('qr-content').value || ''; } @@ -1043,14 +1069,20 @@ class QRRapidoGenerator { if (!hintsElement || !type) return; // Show/hide VCard interface based on type - if (type === 'vcard') { - if (vcardInterface) vcardInterface.style.display = 'block'; + if (type === 'vcard' || type === 'wifi' || type === 'sms' || type === 'email' || type === 'pix') { + if (type === 'vcard' && vcardInterface) vcardInterface.style.display = 'block'; + + // For these specific types, we always hide the main content textarea + // because they have their own specialized interfaces if (contentTextarea) { contentTextarea.style.display = 'none'; contentTextarea.removeAttribute('required'); } - hintsElement.textContent = 'Preencha os campos acima para criar seu cartão de visita digital'; - return; // Skip normal hints for VCard + + // Update hints text if needed + if (type === 'vcard') hintsElement.textContent = 'Preencha os campos acima para criar seu cartão de visita digital'; + // For other types, hints are less relevant as they have dedicated forms, + // but we keep the logic below for consistency } else { if (vcardInterface) vcardInterface.style.display = 'none'; if (contentTextarea) { @@ -1951,63 +1983,66 @@ class QRRapidoGenerator { } enableContentFields(type) { + console.log('Enabling fields for type:', type); const contentGroup = document.getElementById('content-group'); const vcardInterface = document.getElementById('vcard-interface'); const wifiInterface = document.getElementById('wifi-interface'); const smsInterface = document.getElementById('sms-interface'); const emailInterface = document.getElementById('email-interface'); + const pixInterface = document.getElementById('pix-interface'); const dynamicQRSection = document.getElementById('dynamic-qr-section'); const urlPreview = document.getElementById('url-preview'); - // Hide all interfaces by default - if (vcardInterface) vcardInterface.style.display = 'none'; - if (wifiInterface) wifiInterface.style.display = 'none'; - if (smsInterface) smsInterface.style.display = 'none'; - if (emailInterface) emailInterface.style.display = 'none'; - if (dynamicQRSection) dynamicQRSection.style.display = 'none'; - if (urlPreview) urlPreview.style.display = 'none'; + // Helper to safely hide + const safeHide = (el) => { if (el) el.style.display = 'none'; }; + const safeShow = (el) => { if (el) el.style.display = 'block'; }; + + // 1. Hide EVERYTHING specific first + safeHide(vcardInterface); + safeHide(wifiInterface); + safeHide(smsInterface); + safeHide(emailInterface); + safeHide(pixInterface); + safeHide(dynamicQRSection); + safeHide(urlPreview); + + // 2. Default: Show content group (hidden later if specific) if (contentGroup) contentGroup.style.display = 'block'; + // 3. Specific logic if (type === 'vcard') { - // Para vCard, ocultar textarea e mostrar interface específica if (contentGroup) contentGroup.style.display = 'none'; - if (vcardInterface) { - vcardInterface.style.display = 'block'; - this.enableVCardFields(); - } - } else if (type === 'wifi') { - // Para WiFi, ocultar textarea e mostrar interface específica + safeShow(vcardInterface); + this.enableVCardFields(); + } + else if (type === 'wifi') { if (contentGroup) contentGroup.style.display = 'none'; - if (wifiInterface) { - wifiInterface.style.display = 'block'; - } - } else if (type === 'sms') { - // Para SMS, ocultar textarea e mostrar interface específica + safeShow(wifiInterface); + } + else if (type === 'sms') { if (contentGroup) contentGroup.style.display = 'none'; - if (smsInterface) { - smsInterface.style.display = 'block'; - } - } else if (type === 'email') { - // Para Email, ocultar textarea e mostrar interface específica + safeShow(smsInterface); + } + else if (type === 'email') { if (contentGroup) contentGroup.style.display = 'none'; - if (emailInterface) { - emailInterface.style.display = 'block'; - } - } else if (type === 'url') { - if (dynamicQRSection) dynamicQRSection.style.display = 'block'; - if (urlPreview) urlPreview.style.display = 'block'; - // CRITICAL FIX: Enable content field for URL type + safeShow(emailInterface); + } + else if (type === 'pix') { + console.log('Showing PIX interface'); + if (contentGroup) contentGroup.style.display = 'none'; + safeShow(pixInterface); + } + else if (type === 'url') { + safeShow(dynamicQRSection); + safeShow(urlPreview); + // URL needs content field const qrContent = document.getElementById('qr-content'); - if(qrContent) { - qrContent.disabled = false; - } - } else { - // Para outros tipos, mostrar textarea - if (contentGroup) contentGroup.style.display = 'block'; + if(qrContent) qrContent.disabled = false; + } + else { + // Text or others - Keep content group const qrContent = document.getElementById('qr-content'); - if(qrContent) { - qrContent.disabled = false; - } + if(qrContent) qrContent.disabled = false; } } @@ -2165,12 +2200,8 @@ class QRRapidoGenerator { updateGenerateButton() { const generateBtn = document.getElementById('generate-btn'); - const generateQuickBtn = document.getElementById('generate-quick-btn'); - const nextGroup = document.getElementById('next-button-group'); - const nextBtn = document.getElementById('next-btn'); - const quickGroup = document.getElementById('button-gerar-quick-div'); - if (!generateBtn && !generateQuickBtn) return; + if (!generateBtn) return; let isValid = false; const type = this.selectedType; @@ -2224,6 +2255,9 @@ class QRRapidoGenerator { } else if (type === 'email') { const data = window.emailGenerator.collectEmailData(); isValid = data.to.trim() !== '' && data.subject.trim() !== ''; + } else if (type === 'pix') { + const errors = window.pixGenerator.validatePixData(); + isValid = errors.length === 0; } // Controle do botão principal "Gerar QR Code" @@ -2237,31 +2271,6 @@ class QRRapidoGenerator { generateBtn.classList.add('btn-secondary', 'disabled'); } } - - // Controle do botão "Gerar Rápido" com a mesma validação - if (generateQuickBtn) { - generateQuickBtn.disabled = !isValid; - if (quickGroup) { - if (!isValid) { - quickGroup.classList.add('disabled-state'); - } else { - quickGroup.classList.remove('disabled-state'); - } - } - } - - // Controle do botão "Próximo/Personalizar" com validação mais permissiva - const isNextValid = this.isValidForNext(type); - if (nextBtn) { - nextBtn.disabled = !isNextValid; - if (nextGroup) { - if (!isNextValid) { - nextGroup.classList.add('disabled-state'); - } else { - nextGroup.classList.remove('disabled-state'); - } - } - } } // Validação mais permissiva para o botão "Próximo" @@ -2304,6 +2313,12 @@ class QRRapidoGenerator { const to = document.getElementById('email-to')?.value || ''; const subject = document.getElementById('email-subject')?.value || ''; return to.includes('@') && subject.trim().length >= 1; + + } else if (type === 'pix') { + const key = document.getElementById('pix-key')?.value || ''; + const name = document.getElementById('pix-name')?.value || ''; + const city = document.getElementById('pix-city')?.value || ''; + return key.trim().length >= 1 && name.trim().length >= 1 && city.trim().length >= 1; } return false; @@ -2410,6 +2425,7 @@ class QRRapidoGenerator { 'wifi': document.querySelector('[data-type-guide-wifi]')?.textContent || '📶 Para WiFi, informe nome da rede, senha e tipo de segurança', 'sms': document.querySelector('[data-type-guide-sms]')?.textContent || '💬 Para SMS, digite o número do destinatário e a mensagem', 'email': document.querySelector('[data-type-guide-email]')?.textContent || '📧 Para email, preencha destinatário, assunto e mensagem (opcional)', + 'pix': document.querySelector('[data-type-guide-pix]')?.textContent || '💸 Para PIX, preencha a chave, nome e cidade do recebedor', 'text': document.querySelector('[data-type-guide-text]')?.textContent || '📝 Para texto livre, digite qualquer conteúdo que desejar' }; } @@ -2793,6 +2809,7 @@ document.addEventListener('DOMContentLoaded', () => { window.wifiGenerator = new WiFiQRGenerator(); window.smsGenerator = new SMSQRGenerator(); window.emailGenerator = new EmailQRGenerator(); + window.pixGenerator = new PixQRGenerator(); window.dynamicQRManager = new DynamicQRManager(); // Initialize AdSense if necessary