Cloudflare Worker que gera QR Codes de pagamento PIX a partir da URL.
Tenho uma planilha com as contas que pago todo mes, cada linha com a
chave PIX e o valor. Montando um link pixlink por linha, o pagamento
vira um clique: abro o link, aponto o celular para o QR Code e pago.
Sem digitar chave nem valor, sem erro de transcricao.
Usando a URL .png da para incorporar o QR Code direto numa celula da
planilha (via =IMAGE(...) no Google Sheets, por exemplo) e escanear
sem precisar sair dela.
/{chave-pix}/{valor}?d={descricao}
- Telefone —
11912345678,5511912345678ou+5511912345678 - CPF —
111.444.777-35ou11144477735 - CNPJ —
12.345.678/0001-95ou12345678000195 - E-mail —
joana@example.com - Chave aleatoria — UUID, com ou sem hifens:
123e4567-e89b-12d3-a456-426614174000ou123e4567e89b12d3a456426614174000
O parser é permissivo:
- Se contém
@→ e-mail. Validacao basica:localpart@dominio.tld(rejeita@,foo@,@bar,foo@barsem TLD). - Se bate com o formato UUID (36 chars com hifens ou 32 hex) → chave aleatória, normalizada para lowercase com hifens.
- Caso contrário, tudo que não for dígito é removido (pontuação,
espaços,
+, parênteses) e o que sobra é classificado pelo tamanho — 11 dígitos = CPF, 14 = CNPJ, 10–13 com ou sem DDI 55 = telefone. CPF e CNPJ são validados pelos dígitos verificadores.
A barra do CNPJ quebraria o roteamento, então existe uma rota auxiliar que aceita o CNPJ com a barra literal e redireciona (301) para a forma canônica com os dois segmentos unidos:
/12.345.678/0001-95/25000 → /12.345.6780001-95/25000
Assim o usuário pode colar o CNPJ formatado direto na URL sem precisar
escapar o / como %2F.
Também aceita o payload EMV inteiro (o "copia e cola" gerado por apps de banco e marketplaces) como path único:
/{payload-emv-completo}
O parser valida o CRC16 e confere o GUI br.gov.bcb.pix antes de
aceitar. A página renderiza direto naquele caminho preservando o
payload original no QR Code e no botão copiar — isso mantém o
txid (campo 62.05), o merchant name e a cidade intactos, essenciais
para marketplaces como Mercado Pago reconciliarem o pagamento com o
pedido específico.
Sem valor (valor a definir) a página também renderiza direto, com
"Valor a definir no app" no lugar do total — o pagador escolhe o valor
no app do banco na hora de pagar. Importante: nunca caímos pra
calculadora nesse caso, mesmo que pareça "completar" o pagamento. A
calculadora regeraria o payload do zero e perderia o txid — pior do
que não suportar o caso. Pagamento com txid preserva txid; ponto.
A forma curta /{chave}/{valor} continua disponível e útil pra envio
avulso, mas o QR gerado por ela perde o txid (regenera tudo). Use o
payload completo quando o pagamento precisa estar amarrado a uma
transação específica. Páginas servidas a partir do payload completo
não declaram <link rel="canonical"> curto justamente pra não induzir
crawlers a indexar a forma lossy.
O valor usa , como separador de centavos. Ambos os formatos sao
aceitos sem redirect, mas a forma normalizada gerada pelo form na home
e {reais},{centavos}
com dois digitos:
| Entrada na URL | Centavos | Formatado |
|---|---|---|
/50,00 |
5000 | R$ 50,00 |
/50 |
5000 | R$ 50,00 |
/0,50 |
50 | R$ 0,50 |
/0,01 |
1 | R$ 0,01 |
/50000,00 |
5000000 | R$ 50.000,00 |
/R$50 |
5000 | R$ 50,00 |
/$50,00 |
5000 | R$ 50,00 |
/R$1.000,50 |
100050 | R$ 1.000,50 |
Heuristica: se o valor terminar com . + 1 ou 2 digitos (ex:
50.00, 50.5), tratamos esse ponto como virgula — conveniencia para
quem cola valores em formato em ingles. /50.00 equivale a /50,00 e
/50.5 equivale a /50,50.
Prefixo R$ ou $: opcional, com ou sem espaco apos. /R$50,
/R$ 50, /$50, /$ 50 e o equivalente URL-encoded /R%2450 sao
todos aceitos e equivalentes a /50. Util pra colar o valor exatamente
como veio de uma planilha ou nota fiscal. Caveat: a combinacao
%24 (=$) + %20 (=espaco) e bloqueada no nivel de routing pelo
adapter Cloudflare antes de chegar no app — pra valor com espaco use o
form da home, ou monte a URL sem espaco (/R$50).
Invalidos (retornam HTTP 400 com pagina estilizada):
- Zero ou negativo (
/0,/0,00,/-50) - Caracteres nao numericos alem de
R$/$,,ou do ponto final (/50abc,/USD50) - Ponto no meio que nao seja o final com 1-2 digitos (
/50.000) - Valor acima de
Number.MAX_SAFE_INTEGERcentavos (~R$ 90 trilhoes) — acima disso o parseFloat perde precisao.
O campo de valor no form da home e mais permissivo que a URL: aceita qualquer formatacao e gera a URL canonica. A regra e "so digitos e virgula importam, o resto e ignorado":
| Entrada no form | URL gerada |
|---|---|
50 |
/50,00 |
50,00 |
/50,00 |
R$ 50,00 |
/50,00 |
R$50 |
/50,00 |
$50 |
/50,00 |
50.00 |
/50,00 |
50.000,00 |
/50000,00 |
-50,00 |
/50,00 |
0,015 |
/0,02 |
Detalhes:
,separa os centavos; se ausente, o valor e tratado como reais inteiros.- Tudo que nao for digito ou
,e removido (inclusive-, entao valores negativos viram positivos). - Como na URL,
.no final seguido de 1 ou 2 digitos vira,(50.00=50,00,50.5=50,50). - Fracoes menores que 1 centavo sao arredondadas para o inteiro mais
proximo (
0,015→ 2 centavos).
Query param ?d=Almoco — incluida no payload PIX.
A descricao e sanitizada antes de entrar no QR Code: acentos sao
transliterados (café → cafe) e caracteres nao-ASCII (emojis,
simbolos Unicode, controles) sao removidos. A especificacao EMV usa
encoding restrito e bancos podem rejeitar o QR Code silenciosamente
sem esse passo. O texto maximo no payload e 72 caracteres (o que
passar disso e truncado).
Adicione .png ao valor para receber apenas a imagem do QR Code (1024x1024):
/11912345678/50,00.png
Todas as paginas incluem <link rel="canonical"> apontando para a home.
As URLs de pagamento sao entradas do servico, e nao paginas independentes
para indexacao. og:url aponta para a pagina compartilhada, para que o
preview abra o pagamento correto.
A pagina do PIX inclui Open Graph (Facebook, WhatsApp, Telegram, LinkedIn,
Discord e Slack) e Twitter Cards (X), renderizados no servidor. O titulo
usa R$ 50,00 / {CHAVE}, ou Valor a definir / {CHAVE}. A descricao usa
Pix de R$ 50,00 para chave {CHAVE}: {descricao}; sem descricao do pagamento,
omite os dois-pontos finais. Sem valor, usa Pix com valor a definir para chave {CHAVE}.
A imagem social e um PNG de 1200x630 com o QR Code centralizado em fundo
branco e margem de quatro modulos. O QR cabe em recortes centrais quadrados
e 2:1. A rota /preview.png?v=1&payload=... valida o payload e o preserva
integralmente, incluindo txid, nome e cidade, mesmo sem valor definido.
O PNG avulso /{chave}/{valor}.png continua em 1024x1024.
A home usa um QR que abre o servico; os links com apenas a chave usam um QR que abre a calculadora correspondente. Esses previews nao inventam um pagamento antes de o valor ser escolhido.
As imagens incluem dimensoes, tipo e texto alternativo nas metatags. O tamanho e a exibicao finais dependem de cada plataforma e de seu cache; metatags nao obrigam uma rede a mostrar ou atualizar o preview.
https://pix.peleteiro.net/llms.txt documenta a API de URL no formato
llms.txt — versao concisa e em ingles otimizada
pra agentes que precisam construir links pixlink (ex: bots de WhatsApp,
assistentes financeiros, agentes que ajudam usuarios a cobrar via PIX).
| URL | Resultado |
|---|---|
/11912345678/50,00 |
Pagina com QR Code de R$ 50,00 |
/11912345678/50 |
Mesmo resultado (forma compacta) |
/111.444.777-35/15 |
QR Code para CPF, R$ 15,00 |
/12.345.678%2F0001-95/250,00 |
QR Code para CNPJ, R$ 250,00 |
/123e4567-e89b-12d3-a456-426614174000/50 |
QR Code para chave aleatoria, R$ 50,00 |
/joana@example.com/100?d=Consultoria |
QR Code com descricao |
/11912345678/50,00.png |
Imagem PNG do QR Code |
/11912345678/R$50 |
Prefixo R$ aceito (= R$ 50,00) |
/00020126...DA46 |
Renderiza o payload "copia e cola" |
src/
├── components/ # CopyButton, ShareButton, PixForm, PixCalculator (React);
│ # ErrorPage, GoogleAnalytics, PixPage (Astro)
├── layouts/ # Base.astro — shell com head/meta/GA
├── lib/pix/ # dominio: chave, valor, payload EMV (gerar + parsear),
│ # qrcode, montarDadosPix
├── pages/ # rotas: /, /[chave] (copia e cola + calculadora),
│ # /[chave]/[valor], /[chave]/[valor].png,
│ # rota auxiliar /[chave1]/[chave2]/[valor] (CNPJ com barra)
└── styles/ # global.css (Tailwind 4 + btn utilities)
public/ # favicon.svg, robots.txt
Imports usam o alias @/ para src/ (ex: @/lib/pix, @/layouts/Base.astro).
pnpm install # ja instala os git hooks (lefthook) via `prepare`
tilt up # servidor dev na porta 3000Configurados em lefthook.yml:
- pre-commit —
prettier --writenos arquivos staged (auto-fix e re-stage) +vitest runrapido. - pre-push —
mise run check(verificacao completa de CI).
mise run deploy # build + deploy Cloudflare Workers
mise run lint # format + type check
mise run test # roda todas as suites de teste
mise run test:unit # roda os testes unitarios
mise run test:integration # roda os testes de integracao
mise run check # verificacao CI (format + types + testes)
mise run clean # limpar geradosDNS e Worker custom domain gerenciados via OpenTofu em sysadmin/tofu/.
mise run tofu:plan # ver mudancasAplicacao de mudancas de infra fica fora do fluxo normal de desenvolvimento.
Neste repositorio, agentes nao devem executar tofu apply/terraform apply,
e deploy do app continua separado via mise run deploy.
- Astro 7 (SSR) + React 19 + Tailwind CSS 4
- Cloudflare Workers
- qrcode-svg + @resvg/resvg-wasm (PNG)
- OpenTofu (infra)