Compare commits
3 Commits
262a64db16
...
4a7cdbf26d
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a7cdbf26d | |||
| 55d18adc74 | |||
| 33c930bf94 |
BIN
AoSelecionarPix.png
Normal file
BIN
AoSelecionarPix.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 392 KiB |
BIN
AoSelecionarPixDesativado.png
Normal file
BIN
AoSelecionarPixDesativado.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 449 KiB |
BIN
AoSelecionarWifi.png
Normal file
BIN
AoSelecionarWifi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 432 KiB |
89
Content/Tutoriais/como-criar-qr-code-pix.pt-BR.md
Normal file
89
Content/Tutoriais/como-criar-qr-code-pix.pt-BR.md
Normal file
@ -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.*
|
||||||
@ -131,6 +131,12 @@ namespace QRRapidoApp.Controllers
|
|||||||
if (stateData.Timestamp > maxAge)
|
if (stateData.Timestamp > maxAge)
|
||||||
{
|
{
|
||||||
returnUrl = stateData.ReturnUrl ?? "/";
|
returnUrl = stateData.ReturnUrl ?? "/";
|
||||||
|
|
||||||
|
// Prevent redirect loop to login page
|
||||||
|
if (returnUrl.Contains("/Account/Login", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
returnUrl = "/";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@ -88,6 +88,66 @@
|
|||||||
<data name="EmailType" xml:space="preserve">
|
<data name="EmailType" xml:space="preserve">
|
||||||
<value>Email</value>
|
<value>Email</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PIX" xml:space="preserve">
|
||||||
|
<value>PIX</value>
|
||||||
|
</data>
|
||||||
|
<data name="PIXQRDescription" xml:space="preserve">
|
||||||
|
<value>Crea un código QR PIX estático para recibir pagos instantáneos (Brasil).</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixKey" xml:space="preserve">
|
||||||
|
<value>Clave PIX</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixKeyType" xml:space="preserve">
|
||||||
|
<value>Tipo de Clave</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixKeyPhone" xml:space="preserve">
|
||||||
|
<value>Celular</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixKeyRandom" xml:space="preserve">
|
||||||
|
<value>Aleatoria</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixKeyPlaceholder" xml:space="preserve">
|
||||||
|
<value>Ingresa tu clave</value>
|
||||||
|
</data>
|
||||||
|
<data name="BeneficiaryName" xml:space="preserve">
|
||||||
|
<value>Nombre del Beneficiario</value>
|
||||||
|
</data>
|
||||||
|
<data name="BeneficiaryCity" xml:space="preserve">
|
||||||
|
<value>Ciudad del Beneficiario</value>
|
||||||
|
</data>
|
||||||
|
<data name="AmountOptional" xml:space="preserve">
|
||||||
|
<value>Monto (Opcional)</value>
|
||||||
|
</data>
|
||||||
|
<data name="DescriptionOptional" xml:space="preserve">
|
||||||
|
<value>Descripción (Opcional)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TxIDOptional" xml:space="preserve">
|
||||||
|
<value>Identificador (TxID) - Opcional</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixStep1" xml:space="preserve">
|
||||||
|
<value>Selecciona "PIX" en el menú</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixStep2" xml:space="preserve">
|
||||||
|
<value>Ingresa tu Clave PIX, Nombre y Ciudad (Obligatorios)</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixStep3" xml:space="preserve">
|
||||||
|
<value>Define un monto si deseas recibir una cantidad fija</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixUseCase1" xml:space="preserve">
|
||||||
|
<value>Recibir pagos en tiendas físicas</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixUseCase2" xml:space="preserve">
|
||||||
|
<value>Donaciones y recaudaciones</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixTip1" xml:space="preserve">
|
||||||
|
<value>Siempre prueba el código QR transfiriendo un monto pequeño primero</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixPreviewTitle" xml:space="preserve">
|
||||||
|
<value>Payload PIX Generado</value>
|
||||||
|
</data>
|
||||||
|
<data name="TypeGuidePIX" xml:space="preserve">
|
||||||
|
<value>💸 Para PIX, completa la clave, nombre y ciudad del receptor</value>
|
||||||
|
</data>
|
||||||
<data name="DynamicType" xml:space="preserve">
|
<data name="DynamicType" xml:space="preserve">
|
||||||
<value>QR Dinamico (Premium)</value>
|
<value>QR Dinamico (Premium)</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@ -147,6 +147,66 @@
|
|||||||
<data name="EmailType" xml:space="preserve">
|
<data name="EmailType" xml:space="preserve">
|
||||||
<value>Email</value>
|
<value>Email</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PIX" xml:space="preserve">
|
||||||
|
<value>PIX</value>
|
||||||
|
</data>
|
||||||
|
<data name="PIXQRDescription" xml:space="preserve">
|
||||||
|
<value>Crie um QR Code PIX estático para receber pagamentos instantâneos. Funciona em qualquer app de banco.</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixKey" xml:space="preserve">
|
||||||
|
<value>Chave PIX</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixKeyType" xml:space="preserve">
|
||||||
|
<value>Tipo de Chave</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixKeyPhone" xml:space="preserve">
|
||||||
|
<value>Celular</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixKeyRandom" xml:space="preserve">
|
||||||
|
<value>Aleatória</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixKeyPlaceholder" xml:space="preserve">
|
||||||
|
<value>Digite sua chave</value>
|
||||||
|
</data>
|
||||||
|
<data name="BeneficiaryName" xml:space="preserve">
|
||||||
|
<value>Nome do Beneficiário</value>
|
||||||
|
</data>
|
||||||
|
<data name="BeneficiaryCity" xml:space="preserve">
|
||||||
|
<value>Cidade do Beneficiário</value>
|
||||||
|
</data>
|
||||||
|
<data name="AmountOptional" xml:space="preserve">
|
||||||
|
<value>Valor (Opcional)</value>
|
||||||
|
</data>
|
||||||
|
<data name="DescriptionOptional" xml:space="preserve">
|
||||||
|
<value>Descrição (Opcional)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TxIDOptional" xml:space="preserve">
|
||||||
|
<value>Identificador (TxID) - Opcional</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixStep1" xml:space="preserve">
|
||||||
|
<value>Selecione "PIX" no menu</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixStep2" xml:space="preserve">
|
||||||
|
<value>Insira sua Chave PIX, Nome e Cidade (Obrigatórios)</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixStep3" xml:space="preserve">
|
||||||
|
<value>Defina um valor se desejar receber uma quantia fixa</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixUseCase1" xml:space="preserve">
|
||||||
|
<value>Receber pagamentos em lojas físicas</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixUseCase2" xml:space="preserve">
|
||||||
|
<value>Doações e arrecadações</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixTip1" xml:space="preserve">
|
||||||
|
<value>Sempre teste o QR Code transferindo um valor pequeno primeiro</value>
|
||||||
|
</data>
|
||||||
|
<data name="PixPreviewTitle" xml:space="preserve">
|
||||||
|
<value>Payload PIX Gerado</value>
|
||||||
|
</data>
|
||||||
|
<data name="TypeGuidePIX" xml:space="preserve">
|
||||||
|
<value>💸 Para PIX, preencha a chave, nome e cidade do recebedor</value>
|
||||||
|
</data>
|
||||||
<data name="DynamicType" xml:space="preserve">
|
<data name="DynamicType" xml:space="preserve">
|
||||||
<value>QR Dinamico (Premium)</value>
|
<value>QR Dinamico (Premium)</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@ -18,22 +18,13 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="text-center mb-4">
|
<div class="text-center mb-4">
|
||||||
<p class="text-muted">@Localizer["LoginAndGet"]</p>
|
<p class="text-muted">@Localizer["LoginAndGet"]</p>
|
||||||
<div class="row text-center">
|
|
||||||
<div class="col-12 mb-2">
|
<div class="alert alert-warning border-0 shadow-sm" style="background-color: #fff3cd;">
|
||||||
<div class="badge bg-success p-2 w-100">
|
<ul class="list-unstyled mb-0 text-start d-inline-block">
|
||||||
<i class="fas fa-crown"></i> @Localizer["ThirtyDaysNoAds"]
|
<li class="mb-2"><i class="fas fa-crown text-warning me-2"></i> @Localizer["ThirtyDaysNoAds"]</li>
|
||||||
</div>
|
<li class="mb-2"><i class="fas fa-infinity text-primary me-2"></i> @Localizer["FiftyQRCodesPerDay"]</li>
|
||||||
</div>
|
<li class="mb-0"><i class="fas fa-history text-info me-2"></i> @Localizer["QRCodeHistory"]</li>
|
||||||
<div class="col-12 mb-2">
|
</ul>
|
||||||
<div class="badge bg-primary p-2 w-100">
|
|
||||||
<i class="fas fa-infinity"></i> @Localizer["FiftyQRCodesPerDay"]
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 mb-2">
|
|
||||||
<div class="badge bg-info p-2 w-100">
|
|
||||||
<i class="fas fa-history"></i> @Localizer["QRCodeHistory"]
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -83,6 +83,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<select id="qr-type" class="form-select qr-type-highlight" required>
|
<select id="qr-type" class="form-select qr-type-highlight" required>
|
||||||
<option value="">@Localizer["SelectType"]</option>
|
<option value="">@Localizer["SelectType"]</option>
|
||||||
|
<option value="pix">💸 @Localizer["PIX"]</option>
|
||||||
<option value="url">🌐 @Localizer["URLLink"]</option>
|
<option value="url">🌐 @Localizer["URLLink"]</option>
|
||||||
<option value="text">📝 @Localizer["SimpleText"]</option>
|
<option value="text">📝 @Localizer["SimpleText"]</option>
|
||||||
<option value="wifi">📶 @Localizer["WiFi"]</option>
|
<option value="wifi">📶 @Localizer["WiFi"]</option>
|
||||||
@ -102,6 +103,7 @@
|
|||||||
<span data-type-guide-wifi>@Localizer["TypeGuideWiFi"]</span>
|
<span data-type-guide-wifi>@Localizer["TypeGuideWiFi"]</span>
|
||||||
<span data-type-guide-sms>@Localizer["TypeGuideSMS"]</span>
|
<span data-type-guide-sms>@Localizer["TypeGuideSMS"]</span>
|
||||||
<span data-type-guide-email>@Localizer["TypeGuideEmail"]</span>
|
<span data-type-guide-email>@Localizer["TypeGuideEmail"]</span>
|
||||||
|
<span data-type-guide-pix>@Localizer["TypeGuidePIX"]</span>
|
||||||
<span data-type-guide-text>@Localizer["TypeGuideText"]</span>
|
<span data-type-guide-text>@Localizer["TypeGuideText"]</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -504,29 +506,97 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Navigation Buttons (Global - appears for all QR types) -->
|
<!-- PIX Interface (dynamic) -->
|
||||||
<div class="row mb-4 opacity-controlled disabled-state" id="navigation-buttons">
|
<div id="pix-interface" class="mb-3 opacity-controlled disabled-state" style="display: none;">
|
||||||
<div class="col-md-6 mb-3">
|
<div class="alert alert-success border-success">
|
||||||
<div class="d-grid opacity-controlled disabled-state" id="next-button-group">
|
<div class="d-flex">
|
||||||
<button type="button" class="btn btn-success" id="next-btn">
|
<div class="me-3 display-4"><i class="fas fa-qrcode"></i></div>
|
||||||
<i class="fas fa-arrow-right"></i> Personalizar
|
<div>
|
||||||
</button>
|
<h5 class="alert-heading fw-bold">Gerador de PIX</h5>
|
||||||
|
<p class="mb-0">Crie QR Codes para receber pagamentos instantâneos. Compatível com todos os bancos brasileiros.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-warning border-warning d-flex align-items-center mb-3">
|
||||||
|
<i class="fas fa-store fa-2x me-3 text-warning"></i>
|
||||||
|
<div>
|
||||||
|
<strong>Vende muitos produtos?</strong>
|
||||||
|
<p class="mb-0 small">Assine nosso <a href="/Pagamento/SelecaoPlano" class="alert-link">plano mensal</a> e gerencie QR Codes exclusivos para cada produto do seu catálogo.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-semibold">@Localizer["PixKeyType"]</label>
|
||||||
|
<select id="pix-key-type" class="form-select">
|
||||||
|
<option value="cpf">CPF/CNPJ</option>
|
||||||
|
<option value="phone">@Localizer["PixKeyPhone"]</option>
|
||||||
|
<option value="email">Email</option>
|
||||||
|
<option value="random">@Localizer["PixKeyRandom"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8 mb-3">
|
||||||
|
<label class="form-label fw-semibold">@Localizer["PixKey"] *</label>
|
||||||
|
<input type="text" id="pix-key" class="form-control" placeholder="@Localizer["PixKeyPlaceholder"]" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 mb-3">
|
||||||
|
<label class="form-label fw-semibold">@Localizer["BeneficiaryName"] *</label>
|
||||||
|
<input type="text" id="pix-name" class="form-control" placeholder="Seu Nome ou Nome da Empresa" maxlength="25" required>
|
||||||
|
<div class="form-text">Máximo 25 caracteres (sem acentos recomendado)</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-semibold">@Localizer["BeneficiaryCity"] *</label>
|
||||||
|
<input type="text" id="pix-city" class="form-control" placeholder="Sua Cidade" maxlength="15" required>
|
||||||
|
<div class="form-text">Máximo 15 caracteres (Padrão BACEN)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-semibold">@Localizer["AmountOptional"]</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">R$</span>
|
||||||
|
<input type="text" id="pix-amount" class="form-control" placeholder="0,00" inputmode="decimal">
|
||||||
|
</div>
|
||||||
|
<div class="form-text">Deixe em branco para valor livre</div>
|
||||||
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="d-grid opacity-controlled disabled-state" id="button-gerar-quick-div">
|
<label class="form-label fw-semibold">@Localizer["TxIDOptional"]</label>
|
||||||
<button type="submit" class="btn btn-primary" id="generate-quick-btn">
|
<input type="text" id="pix-txid" class="form-control" placeholder="***">
|
||||||
<i class="fas fa-qrcode"></i> Gerar Rápido
|
<div class="form-text">Identificador da transação (padrão: ***)</div>
|
||||||
</button>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold">@Localizer["DescriptionOptional"]</label>
|
||||||
|
<input type="text" id="pix-description" class="form-control" placeholder="Pagamento referente a...">
|
||||||
|
<div class="form-text">Alguns bancos exibem esta mensagem na tela de confirmação</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pix-preview mt-3">
|
||||||
|
<h6 class="fw-bold text-success mb-2">
|
||||||
|
<i class="fas fa-eye"></i> @Localizer["PixPreviewTitle"]
|
||||||
|
</h6>
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<pre id="pix-preview-text" class="mb-0 small text-muted text-break">Preencha os campos obrigatórios para gerar o payload...</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Navigation Buttons REMOVED - Simplified Flow -->
|
||||||
|
|
||||||
<!-- Advanced customization (collapsible) -->
|
<!-- Advanced customization (collapsible) -->
|
||||||
<div class="accordion mb-3 opacity-controlled disabled-state" id="customization-accordion">
|
<div class="accordion mb-3 opacity-controlled disabled-state" id="customization-accordion">
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header">
|
<h2 class="accordion-header">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#customization-panel">
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#customization-panel" id="btn-customization-toggle">
|
||||||
<i class="fas fa-sliders-h me-2"></i> @Localizer["AdvancedCustomization"]
|
<i class="fas fa-sliders-h me-2"></i> @Localizer["AdvancedCustomization"]
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
@ -958,6 +1028,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- PIX QR Code -->
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#pixQR">
|
||||||
|
<i class="fas fa-qrcode text-success me-2"></i>
|
||||||
|
<strong>@Localizer["PIX"]</strong>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="pixQR" class="accordion-collapse collapse" data-bs-parent="#qrTypesAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6><i class="fas fa-question-circle text-info"></i> @Localizer["WhatIsIt"]</h6>
|
||||||
|
<p>@Localizer["PIXQRDescription"]</p>
|
||||||
|
|
||||||
|
<h6><i class="fas fa-play text-success"></i> @Localizer["HowToUse"]</h6>
|
||||||
|
<ol class="small">
|
||||||
|
<li>@Localizer["PixStep1"]</li>
|
||||||
|
<li>@Localizer["PixStep2"]</li>
|
||||||
|
<li>@Localizer["PixStep3"]</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6><i class="fas fa-lightbulb text-warning"></i> @Localizer["Tips"]</h6>
|
||||||
|
<ul class="small">
|
||||||
|
<li>@Localizer["PixTip1"]</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h6><i class="fas fa-users text-secondary"></i> @Localizer["UseCases"]</h6>
|
||||||
|
<ul class="small">
|
||||||
|
<li>@Localizer["PixUseCase1"]</li>
|
||||||
|
<li>@Localizer["PixUseCase2"]</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- SMS QR Code -->
|
<!-- SMS QR Code -->
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header">
|
<h2 class="accordion-header">
|
||||||
@ -1340,14 +1449,15 @@
|
|||||||
const previewColorizeText = document.getElementById('preview-colorize-text');
|
const previewColorizeText = document.getElementById('preview-colorize-text');
|
||||||
|
|
||||||
new SimpleOpacityController('#qr-type', '#quick-style-group');
|
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', '#dynamic-qr-section');
|
||||||
new SimpleOpacityController('#qr-type', '#url-preview');
|
new SimpleOpacityController('#qr-type', '#url-preview');
|
||||||
new SimpleOpacityController('#qr-type', '#vcard-interface');
|
new SimpleOpacityController('#qr-type', '#vcard-interface');
|
||||||
new SimpleOpacityController('#qr-type', '#wifi-interface');
|
new SimpleOpacityController('#qr-type', '#wifi-interface');
|
||||||
new SimpleOpacityController('#qr-type', '#sms-interface');
|
new SimpleOpacityController('#qr-type', '#sms-interface');
|
||||||
new SimpleOpacityController('#qr-type', '#email-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', '#customization-accordion');
|
||||||
new SimpleOpacityController('#qr-type', '#button-gerar-div');
|
new SimpleOpacityController('#qr-type', '#button-gerar-div');
|
||||||
|
|
||||||
|
|||||||
@ -457,7 +457,7 @@
|
|||||||
<script src="~/js/telegram-fab.js" asp-append-version="true" defer></script>
|
<script src="~/js/telegram-fab.js" asp-append-version="true" defer></script>
|
||||||
<script src="~/js/rating.js" asp-append-version="true" defer></script>
|
<script src="~/js/rating.js" asp-append-version="true" defer></script>
|
||||||
|
|
||||||
@if (isDevelopment)
|
@if (isDevelopment || true) // FORCE INDIVIDUAL SCRIPTS FOR NOW TO ENSURE CHANGES ARE VISIBLE
|
||||||
{
|
{
|
||||||
<script src="~/js/simple-opcacity.js" asp-append-version="true" defer></script>
|
<script src="~/js/simple-opcacity.js" asp-append-version="true" defer></script>
|
||||||
<script src="~/js/test.js" asp-append-version="true" defer></script>
|
<script src="~/js/test.js" asp-append-version="true" defer></script>
|
||||||
@ -466,6 +466,7 @@
|
|||||||
<script src="~/js/theme-toggle.js" asp-append-version="true" defer></script>
|
<script src="~/js/theme-toggle.js" asp-append-version="true" defer></script>
|
||||||
<script src="~/js/cookie-consent.js" asp-append-version="true" defer></script>
|
<script src="~/js/cookie-consent.js" asp-append-version="true" defer></script>
|
||||||
<script src="~/js/performance-optimizations.js" asp-append-version="true" defer></script>
|
<script src="~/js/performance-optimizations.js" asp-append-version="true" defer></script>
|
||||||
|
<script src="~/js/pix-generator.js" asp-append-version="true" defer></script>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
40
crc_test.js
Normal file
40
crc_test.js
Normal file
@ -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");
|
||||||
21
crc_verify_new.js
Normal file
21
crc_verify_new.js
Normal file
@ -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");
|
||||||
210
wwwroot/js/pix-generator.js
Normal file
210
wwwroot/js/pix-generator.js
Normal file
@ -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, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -154,7 +154,8 @@ class QRRapidoGenerator {
|
|||||||
const fieldsToWatch = [
|
const fieldsToWatch = [
|
||||||
'qr-content', 'vcard-name', 'vcard-mobile', 'vcard-email',
|
'qr-content', 'vcard-name', 'vcard-mobile', 'vcard-email',
|
||||||
'wifi-ssid', 'wifi-password', 'sms-number', 'sms-message',
|
'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 => {
|
fieldsToWatch.forEach(id => {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
@ -188,24 +189,37 @@ class QRRapidoGenerator {
|
|||||||
saveBtn.addEventListener('click', this.saveToHistory.bind(this));
|
saveBtn.addEventListener('click', this.saveToHistory.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next button navigation
|
// Accordion State Persistence
|
||||||
const nextBtn = document.getElementById('next-btn');
|
this.initializeAccordionState();
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setupUrlFieldHandlers();
|
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() {
|
setupUrlFieldHandlers() {
|
||||||
const contentField = document.getElementById('qr-content');
|
const contentField = document.getElementById('qr-content');
|
||||||
if (!contentField) return;
|
if (!contentField) return;
|
||||||
@ -661,6 +675,13 @@ class QRRapidoGenerator {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
} else if (qrType === 'pix') {
|
||||||
|
const errors = window.pixGenerator.validatePixData();
|
||||||
|
if (errors.length > 0) {
|
||||||
|
this.showError(errors.join('<br>'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal validation for other types
|
// Normal validation for other types
|
||||||
@ -720,6 +741,9 @@ class QRRapidoGenerator {
|
|||||||
} else if (type === 'email') {
|
} else if (type === 'email') {
|
||||||
content = window.emailGenerator.generateEmailString();
|
content = window.emailGenerator.generateEmailString();
|
||||||
actualType = 'text';
|
actualType = 'text';
|
||||||
|
} else if (type === 'pix') {
|
||||||
|
content = window.pixGenerator.generatePixPayload();
|
||||||
|
actualType = 'text'; // Pix is treated as text
|
||||||
} else if (type === 'vcard') {
|
} else if (type === 'vcard') {
|
||||||
if (!window.vcardGenerator) {
|
if (!window.vcardGenerator) {
|
||||||
throw new Error('VCard generator não está disponível');
|
throw new Error('VCard generator não está disponível');
|
||||||
@ -880,6 +904,8 @@ class QRRapidoGenerator {
|
|||||||
return window.smsGenerator?.generateSMSString() || '';
|
return window.smsGenerator?.generateSMSString() || '';
|
||||||
} else if (type === 'email') {
|
} else if (type === 'email') {
|
||||||
return window.emailGenerator?.generateEmailString() || '';
|
return window.emailGenerator?.generateEmailString() || '';
|
||||||
|
} else if (type === 'pix') {
|
||||||
|
return window.pixGenerator?.generatePixPayload() || '';
|
||||||
} else {
|
} else {
|
||||||
return document.getElementById('qr-content').value || '';
|
return document.getElementById('qr-content').value || '';
|
||||||
}
|
}
|
||||||
@ -1043,14 +1069,20 @@ class QRRapidoGenerator {
|
|||||||
if (!hintsElement || !type) return;
|
if (!hintsElement || !type) return;
|
||||||
|
|
||||||
// Show/hide VCard interface based on type
|
// Show/hide VCard interface based on type
|
||||||
if (type === 'vcard') {
|
if (type === 'vcard' || type === 'wifi' || type === 'sms' || type === 'email' || type === 'pix') {
|
||||||
if (vcardInterface) vcardInterface.style.display = 'block';
|
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) {
|
if (contentTextarea) {
|
||||||
contentTextarea.style.display = 'none';
|
contentTextarea.style.display = 'none';
|
||||||
contentTextarea.removeAttribute('required');
|
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 {
|
} else {
|
||||||
if (vcardInterface) vcardInterface.style.display = 'none';
|
if (vcardInterface) vcardInterface.style.display = 'none';
|
||||||
if (contentTextarea) {
|
if (contentTextarea) {
|
||||||
@ -1951,63 +1983,66 @@ class QRRapidoGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enableContentFields(type) {
|
enableContentFields(type) {
|
||||||
|
console.log('Enabling fields for type:', type);
|
||||||
const contentGroup = document.getElementById('content-group');
|
const contentGroup = document.getElementById('content-group');
|
||||||
const vcardInterface = document.getElementById('vcard-interface');
|
const vcardInterface = document.getElementById('vcard-interface');
|
||||||
const wifiInterface = document.getElementById('wifi-interface');
|
const wifiInterface = document.getElementById('wifi-interface');
|
||||||
const smsInterface = document.getElementById('sms-interface');
|
const smsInterface = document.getElementById('sms-interface');
|
||||||
const emailInterface = document.getElementById('email-interface');
|
const emailInterface = document.getElementById('email-interface');
|
||||||
|
const pixInterface = document.getElementById('pix-interface');
|
||||||
const dynamicQRSection = document.getElementById('dynamic-qr-section');
|
const dynamicQRSection = document.getElementById('dynamic-qr-section');
|
||||||
const urlPreview = document.getElementById('url-preview');
|
const urlPreview = document.getElementById('url-preview');
|
||||||
|
|
||||||
// Hide all interfaces by default
|
// Helper to safely hide
|
||||||
if (vcardInterface) vcardInterface.style.display = 'none';
|
const safeHide = (el) => { if (el) el.style.display = 'none'; };
|
||||||
if (wifiInterface) wifiInterface.style.display = 'none';
|
const safeShow = (el) => { if (el) el.style.display = 'block'; };
|
||||||
if (smsInterface) smsInterface.style.display = 'none';
|
|
||||||
if (emailInterface) emailInterface.style.display = 'none';
|
// 1. Hide EVERYTHING specific first
|
||||||
if (dynamicQRSection) dynamicQRSection.style.display = 'none';
|
safeHide(vcardInterface);
|
||||||
if (urlPreview) urlPreview.style.display = 'none';
|
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';
|
if (contentGroup) contentGroup.style.display = 'block';
|
||||||
|
|
||||||
|
// 3. Specific logic
|
||||||
if (type === 'vcard') {
|
if (type === 'vcard') {
|
||||||
// Para vCard, ocultar textarea e mostrar interface específica
|
|
||||||
if (contentGroup) contentGroup.style.display = 'none';
|
if (contentGroup) contentGroup.style.display = 'none';
|
||||||
if (vcardInterface) {
|
safeShow(vcardInterface);
|
||||||
vcardInterface.style.display = 'block';
|
|
||||||
this.enableVCardFields();
|
this.enableVCardFields();
|
||||||
}
|
}
|
||||||
} else if (type === 'wifi') {
|
else if (type === 'wifi') {
|
||||||
// Para WiFi, ocultar textarea e mostrar interface específica
|
|
||||||
if (contentGroup) contentGroup.style.display = 'none';
|
if (contentGroup) contentGroup.style.display = 'none';
|
||||||
if (wifiInterface) {
|
safeShow(wifiInterface);
|
||||||
wifiInterface.style.display = 'block';
|
|
||||||
}
|
}
|
||||||
} else if (type === 'sms') {
|
else if (type === 'sms') {
|
||||||
// Para SMS, ocultar textarea e mostrar interface específica
|
|
||||||
if (contentGroup) contentGroup.style.display = 'none';
|
if (contentGroup) contentGroup.style.display = 'none';
|
||||||
if (smsInterface) {
|
safeShow(smsInterface);
|
||||||
smsInterface.style.display = 'block';
|
|
||||||
}
|
}
|
||||||
} else if (type === 'email') {
|
else if (type === 'email') {
|
||||||
// Para Email, ocultar textarea e mostrar interface específica
|
|
||||||
if (contentGroup) contentGroup.style.display = 'none';
|
if (contentGroup) contentGroup.style.display = 'none';
|
||||||
if (emailInterface) {
|
safeShow(emailInterface);
|
||||||
emailInterface.style.display = 'block';
|
|
||||||
}
|
}
|
||||||
} else if (type === 'url') {
|
else if (type === 'pix') {
|
||||||
if (dynamicQRSection) dynamicQRSection.style.display = 'block';
|
console.log('Showing PIX interface');
|
||||||
if (urlPreview) urlPreview.style.display = 'block';
|
if (contentGroup) contentGroup.style.display = 'none';
|
||||||
// CRITICAL FIX: Enable content field for URL type
|
safeShow(pixInterface);
|
||||||
|
}
|
||||||
|
else if (type === 'url') {
|
||||||
|
safeShow(dynamicQRSection);
|
||||||
|
safeShow(urlPreview);
|
||||||
|
// URL needs content field
|
||||||
const qrContent = document.getElementById('qr-content');
|
const qrContent = document.getElementById('qr-content');
|
||||||
if(qrContent) {
|
if(qrContent) qrContent.disabled = false;
|
||||||
qrContent.disabled = false;
|
|
||||||
}
|
}
|
||||||
} else {
|
else {
|
||||||
// Para outros tipos, mostrar textarea
|
// Text or others - Keep content group
|
||||||
if (contentGroup) contentGroup.style.display = 'block';
|
|
||||||
const qrContent = document.getElementById('qr-content');
|
const qrContent = document.getElementById('qr-content');
|
||||||
if(qrContent) {
|
if(qrContent) qrContent.disabled = false;
|
||||||
qrContent.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2165,12 +2200,8 @@ class QRRapidoGenerator {
|
|||||||
|
|
||||||
updateGenerateButton() {
|
updateGenerateButton() {
|
||||||
const generateBtn = document.getElementById('generate-btn');
|
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;
|
let isValid = false;
|
||||||
const type = this.selectedType;
|
const type = this.selectedType;
|
||||||
@ -2224,6 +2255,9 @@ class QRRapidoGenerator {
|
|||||||
} else if (type === 'email') {
|
} else if (type === 'email') {
|
||||||
const data = window.emailGenerator.collectEmailData();
|
const data = window.emailGenerator.collectEmailData();
|
||||||
isValid = data.to.trim() !== '' && data.subject.trim() !== '';
|
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"
|
// Controle do botão principal "Gerar QR Code"
|
||||||
@ -2237,31 +2271,6 @@ class QRRapidoGenerator {
|
|||||||
generateBtn.classList.add('btn-secondary', 'disabled');
|
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"
|
// Validação mais permissiva para o botão "Próximo"
|
||||||
@ -2304,6 +2313,12 @@ class QRRapidoGenerator {
|
|||||||
const to = document.getElementById('email-to')?.value || '';
|
const to = document.getElementById('email-to')?.value || '';
|
||||||
const subject = document.getElementById('email-subject')?.value || '';
|
const subject = document.getElementById('email-subject')?.value || '';
|
||||||
return to.includes('@') && subject.trim().length >= 1;
|
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;
|
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',
|
'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',
|
'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)',
|
'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'
|
'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.wifiGenerator = new WiFiQRGenerator();
|
||||||
window.smsGenerator = new SMSQRGenerator();
|
window.smsGenerator = new SMSQRGenerator();
|
||||||
window.emailGenerator = new EmailQRGenerator();
|
window.emailGenerator = new EmailQRGenerator();
|
||||||
|
window.pixGenerator = new PixQRGenerator();
|
||||||
window.dynamicQRManager = new DynamicQRManager();
|
window.dynamicQRManager = new DynamicQRManager();
|
||||||
|
|
||||||
// Initialize AdSense if necessary
|
// Initialize AdSense if necessary
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user