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 —
21992446550,5521992446550ou+5521992446550 - CPF —
052.868.827-81ou05286882781 - CNPJ —
12.345.678/0001-95ou12345678000195 - E-mail —
jose@peleteiro.net - 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.
O valor usa , como separador de centavos. Ambos os formatos sao
aceitos sem redirect, mas a forma canonica (usada na tag <link rel="canonical"> e 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 |
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.
Invalidos (retornam HTTP 400 com pagina estilizada):
- Zero ou negativo (
/0,/0,00,/-50) - Caracteres nao numericos alem de
,ou do ponto final (/R$50,/50abc) - 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):
/21992446550/50,00.png
A pagina inclui <link rel="canonical"> apontando para a forma
preferida (/{chave-normalizada}/{reais,centavos}). Query params de
tracking (UTMs, etc.) sao descartados — so a descricao ?d=, que
participa do conteudo do QR Code, entra na canonical. Evita que
/21992446550/50 e /+5521992446550/50,00?utm_source=x contem como
paginas diferentes pra crawlers.
A página do PIX inclui meta tags Open Graph e Twitter Card, então ao
colar o link no WhatsApp, Telegram, Slack, etc. o preview mostra o QR
Code (via endpoint .png), o valor e a chave.
| URL | Resultado |
|---|---|
/21992446550/50,00 |
Pagina com QR Code de R$ 50,00 |
/21992446550/50 |
Mesmo resultado (forma compacta) |
/052.868.827-81/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 |
/jose@peleteiro.net/100?d=Consultoria |
QR Code com descricao |
/21992446550/50,00.png |
Imagem PNG do QR Code |
src/
├── components/ # CopyButton, ShareButton, PixForm (React);
│ # ErrorPage, GoogleAnalytics (Astro)
├── layouts/ # Base.astro — shell com head/meta/GA
├── lib/pix/ # dominio: chave, valor, payload EMV, qrcode, montarDadosPix
├── pages/ # rotas: /, /[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 6 (SSR) + React 19 + Tailwind CSS 4
- Cloudflare Workers
- qrcode-svg + @resvg/resvg-wasm (PNG)
- OpenTofu (infra)