feat: pix!!!

This commit is contained in:
Ricardo Carneiro 2026-01-25 00:05:26 -03:00
parent 33c930bf94
commit 55d18adc74
14 changed files with 726 additions and 121 deletions

BIN
AoSelecionarPix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

BIN
AoSelecionarWifi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

View 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.*

View File

@ -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
{ {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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="col-md-6 mb-3">
<div class="d-grid opacity-controlled disabled-state" id="button-gerar-quick-div"> <div class="alert alert-warning border-warning d-flex align-items-center mb-3">
<button type="submit" class="btn btn-primary" id="generate-quick-btn"> <i class="fas fa-store fa-2x me-3 text-warning"></i>
<i class="fas fa-qrcode"></i> Gerar Rápido <div>
</button> <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">
<label class="form-label fw-semibold">@Localizer["TxIDOptional"]</label>
<input type="text" id="pix-txid" class="form-control" placeholder="***">
<div class="form-text">Identificador da transação (padrão: ***)</div>
</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');

View File

@ -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
View 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
View 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
View 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, "");
}
}

View File

@ -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 === 'pix') {
} else if (type === 'url') { console.log('Showing PIX interface');
if (dynamicQRSection) dynamicQRSection.style.display = 'block'; if (contentGroup) contentGroup.style.display = 'none';
if (urlPreview) urlPreview.style.display = 'block'; safeShow(pixInterface);
// CRITICAL FIX: Enable content field for URL type }
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 { // Text or others - Keep content group
// Para outros tipos, mostrar textarea
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