<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Victor Augusto</title>
    <description>The latest articles on DEV Community by Victor Augusto (@victoraugustovalle).</description>
    <link>https://dev.to/victoraugustovalle</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4099459%2F1dd91232-393d-4731-ab4b-7b08a8d99ae4.jpg</url>
      <title>DEV Community: Victor Augusto</title>
      <link>https://dev.to/victoraugustovalle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/victoraugustovalle"/>
    <language>en</language>
    <item>
      <title>Pix sem gateway: BR Code, CRC16 e as 3 armadilhas que os validadores online não pegam</title>
      <dc:creator>Victor Augusto</dc:creator>
      <pubDate>Fri, 28 Aug 2026 23:37:09 +0000</pubDate>
      <link>https://dev.to/victoraugustovalle/pix-sem-gateway-br-code-crc16-e-as-3-armadilhas-que-os-validadores-online-nao-pegam-2aki</link>
      <guid>https://dev.to/victoraugustovalle/pix-sem-gateway-br-code-crc16-e-as-3-armadilhas-que-os-validadores-online-nao-pegam-2aki</guid>
      <description>&lt;p&gt;Montei um gerador de "Pix copia e cola" seguindo o Manual do BR Code do Banco Central e alguns repositórios de referência. Joguei o payload em três validadores online: todos aprovaram. CRC batendo, campos no lugar, estrutura TLV correta.&lt;/p&gt;

&lt;p&gt;Aí abri o app do banco pra ler o QR de verdade: &lt;strong&gt;"QR Code inválido"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Este post é sobre as duas coisas que saíram daí:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;O formato do Pix copia e cola é público e cabe em &lt;strong&gt;~120 linhas de C#&lt;/strong&gt;, sem gateway e sem biblioteca (só uma de QR pra virar imagem).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;As três coisas que passam em todo validador genérico e mesmo assim o banco recusa:&lt;/strong&gt; a caixa da GUI &lt;code&gt;br.gov.bcb.pix&lt;/code&gt;, o formato da chave no DICT e o campo &lt;code&gt;01&lt;/code&gt;. A próxima seção resolve cada uma; o resto do post é como cheguei lá.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;E fica a suspeita que me trouxe até aqui: se o meu passou em três validadores e mesmo assim quebrou num banco de verdade, &lt;strong&gt;quantos QR Pix "quase válidos" estão circulando por aí agora?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  As três armadilhas, direto ao ponto
&lt;/h2&gt;

&lt;p&gt;Se você caiu aqui procurando "QR Code Pix inválido" e tem um payload que parece certo, é quase certeza que é uma destas três. O detalhamento, com código e fontes, está mais abaixo; isto aqui já desbloqueia:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A caixa da GUI.&lt;/strong&gt; O identificador do arranjo, &lt;code&gt;br.gov.bcb.pix&lt;/code&gt;, aparece em minúsculo nos manuais do Banco Central, mas o app do meu banco só aceitava em &lt;strong&gt;maiúsculo&lt;/strong&gt;, &lt;code&gt;BR.GOV.BCB.PIX&lt;/code&gt;. O checksum fecha dos dois jeitos, então nenhum validador acusa. Regra prática: gere um copia e cola pelo app do próprio banco de quem vai receber e use exatamente a caixa que ele usa.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;O formato da chave.&lt;/strong&gt; O banco de quem paga busca a chave no DICT (o diretório de chaves do BC). Se ela não estiver no formato exato (telefone como &lt;code&gt;+5531999998888&lt;/code&gt;, e-mail em minúsculo, CPF só com os 11 dígitos), ele não encontra e recusa, mesmo com o BR Code impecável no resto.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;O campo &lt;code&gt;01&lt;/code&gt; (Point of Initiation Method).&lt;/strong&gt; Num QR estático, declarar &lt;code&gt;12&lt;/code&gt; (o valor de QR dinâmico) quebra; &lt;code&gt;11&lt;/code&gt; funciona e omitir também. Nunca mande &lt;code&gt;12&lt;/code&gt; num QR reutilizável.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O resto do post: o que é o BR Code, como montar o payload do zero e como testar sem ficar abrindo o app do banco a cada mudança.&lt;/p&gt;

&lt;h2&gt;
  
  
  O contexto
&lt;/h2&gt;

&lt;p&gt;Eu estava construindo um bot de Telegram que divide as contas de um grupo (viagem, república, happy hour) e no fim calcula quem transfere quanto pra quem. Faltava a última peça: gerar o Pix de cada transferência ali no chat. Integrar um gateway (Mercado Pago, Asaas, Toku, Gerencianet) pede conta, credencial, OAuth, webhook público e normalmente uma taxa por transação. É desproporcional pra gerar um QR estático de "me paga R$ 42,50". E o "Pix copia e cola" que o app do banco me dá pra receber é só uma string: &lt;code&gt;00020126...6304XXXX&lt;/code&gt;. Se é só texto, dá pra gerar.&lt;/p&gt;

&lt;p&gt;("Pix copia e cola", &lt;strong&gt;BR Code&lt;/strong&gt; e &lt;strong&gt;QR Code Pix&lt;/strong&gt; são o mesmo objeto: um texto no formato &lt;strong&gt;EMV® MPM&lt;/strong&gt; (&lt;em&gt;Merchant-Presented Mode&lt;/em&gt;) do EMVCo, que o Banco Central adaptou. A diferença é só se você mostra a string ou a imagem; a imagem é a mesma string codificada num QR.)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;O código completo está em &lt;a href="https://github.com/victoraugustovalle/RachaContaBot" rel="noopener noreferrer"&gt;RachaContaBot&lt;/a&gt; (&lt;code&gt;RachaContas.Bot/Services/PixBrCodeGenerator.cs&lt;/code&gt;). É C# / .NET 10, mas o formato não tem nada de específico de linguagem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(Testei os payloads gerados nos apps de &lt;strong&gt;Banco do Brasil, Bradesco, Itaú, Nubank e Inter&lt;/strong&gt; em &lt;strong&gt;19 de junho de 2026&lt;/strong&gt;, e os cinco leram e liquidaram. Comportamento de PSP muda sem aviso; se você está lendo isto bem depois e algo não bater, o método de teste no fim do post é o que vale.)&lt;/p&gt;

&lt;h2&gt;
  
  
  O que é um "Pix copia e cola"
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Em uma frase:&lt;/strong&gt; é uma string no padrão &lt;strong&gt;BR Code&lt;/strong&gt; (a adaptação brasileira do &lt;strong&gt;EMV® MPM&lt;/strong&gt;, &lt;em&gt;Merchant-Presented Mode&lt;/em&gt;: o comerciante apresenta o código, o cliente escaneia) que carrega, em texto, todos os dados de uma cobrança: chave, valor, nome e cidade de quem recebe, um identificador e um checksum. Nada aí é assinado ou secreto. É o mesmo padrão do QR de maquininha lá fora.&lt;/p&gt;

&lt;p&gt;Duas documentações do Banco Central cobrem isso, e vale ter as duas abertas na hora de implementar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.bcb.gov.br/content/estabilidadefinanceira/ativosdosite/Manual%20do%20BR%20Code.pdf" rel="noopener noreferrer"&gt;&lt;strong&gt;Manual do BR Code&lt;/strong&gt;&lt;/a&gt;: a adaptação brasileira do padrão EMVCo. Define a estrutura TLV, a lista de campos e o cálculo do CRC16.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.bcb.gov.br/content/estabilidadefinanceira/pix/Regulamento_Pix/II_ManualdePadroesparaIniciacaodoPix.pdf" rel="noopener noreferrer"&gt;&lt;strong&gt;Manual de Padrões para Iniciação do Pix&lt;/strong&gt;&lt;/a&gt; (testei contra a versão 2.9.0): o perfil específico do Pix em cima do BR Code. É aqui que estão a identificação &lt;code&gt;br.gov.bcb.pix&lt;/code&gt;, os tipos de QR (estático, dinâmico, composto), o formato das chaves e a convenção do &lt;code&gt;txid&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A estrutura é um &lt;strong&gt;TLV&lt;/strong&gt; (&lt;em&gt;Type-Length-Value&lt;/em&gt;). Cada campo é:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ID (2 dígitos) + TAMANHO (2 dígitos) + VALOR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Concatenados, sem separador. Então &lt;code&gt;5802BR&lt;/code&gt; quer dizer: campo &lt;code&gt;58&lt;/code&gt; (país), tamanho &lt;code&gt;02&lt;/code&gt;, valor &lt;code&gt;BR&lt;/code&gt;. Simples assim, e recursivo: alguns campos têm subcampos no mesmo formato dentro do valor.&lt;/p&gt;

&lt;p&gt;Um Pix copia e cola estático decodificado fica assim:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqn5a2p8rupq1gfnju2fx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqn5a2p8rupq1gfnju2fx.png" alt="Anatomia de um Pix copia e cola estático: cada campo do BR Code com seu ID, tamanho e valor, e os campos aninhados 26 (identidade Pix) e 62 (dados adicionais)" width="800" height="736"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Em texto puro, sem os rótulos, é isso aqui concatenado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00  02  01                              Payload Format Indicator (sempre "01")
26  58  Merchant Account Info (Pix)
        00  14  BR.GOV.BCB.PIX          identificador do arranjo Pix
        01  36  1ade9854-82d1-04cd...   a chave Pix (aqui uma aleatória / EVP)
52  04  0000                            Merchant Category Code (0000 = não informado)
53  03  986                             moeda (986 = BRL, ISO 4217)
54  05  45.00                           valor da transação
58  02  BR                              país
59  25  Maria Ferreira Silva Dias       nome de quem recebe
60  09  SAO PAULO                       cidade de quem recebe
62  14  Additional Data
        05  10  9kQSS8TQqj              txid (identificador da transação)
63  04  198F                            CRC16 do payload inteiro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É isso. Nenhum campo aí é secreto ou precisa ser assinado por ninguém. Vale fixar dois papéis que voltam o tempo todo daqui pra frente: o &lt;strong&gt;PSP recebedor&lt;/strong&gt; é a instituição de quem gera o QR (a que emite a chave); o &lt;strong&gt;PSP pagador&lt;/strong&gt; é o app de quem vai pagar. A "autenticação" do Pix acontece no &lt;strong&gt;PSP pagador&lt;/strong&gt;: é ele que lê a string, resolve a chave no DICT e mostra o nome do recebedor pra confirmação. O gerador só monta texto; quem aceita ou recusa é o PSP pagador.&lt;/p&gt;

&lt;h2&gt;
  
  
  O essencial: o payload em ~60 segundos
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Esta é a mecânica de montagem, enxuta. Se você já tem um payload e só quer saber por que ele é recusado, &lt;strong&gt;pule pra "Válido" não é "aceito"&lt;/strong&gt;. Se quer o passeio campo a campo, o &lt;a href="https://github.com/victoraugustovalle/RachaContaBot" rel="noopener noreferrer"&gt;&lt;code&gt;PixBrCodeGenerator.cs&lt;/code&gt;&lt;/a&gt; e o Manual do BR Code cobrem cada byte.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;O coração do gerador são duas funções: uma &lt;code&gt;Tlv&lt;/code&gt;, que formata um campo, e uma &lt;code&gt;GerarPayload&lt;/code&gt;, que concatena os campos na ordem e fecha com o CRC.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Tlv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Campo &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: valor com &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; chars estoura o tamanho de 2 dígitos do EMV."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;D2&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GerarPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;chavePix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;nomeRecebedor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;txId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;cidade&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SAO PAULO"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;nome&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Sanitizar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nomeRecebedor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cidadeSanitizada&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Sanitizar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cidade&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;referencia&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;SanitizarTxId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Campo 26 é aninhado: identificador do arranjo + a chave Pix&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;merchantAccountInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Tlv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"BR.GOV.BCB.PIX"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;Tlv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chavePix&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;additionalData&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Tlv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"05"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;referencia&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;semCrc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nf"&gt;Tlv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;                                              &lt;span class="c1"&gt;// Payload Format Indicator&lt;/span&gt;
        &lt;span class="nf"&gt;Tlv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"26"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;merchantAccountInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;                               &lt;span class="c1"&gt;// Merchant Account Info (Pix)&lt;/span&gt;
        &lt;span class="nf"&gt;Tlv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"52"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"0000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;                                            &lt;span class="c1"&gt;// Merchant Category Code&lt;/span&gt;
        &lt;span class="nf"&gt;Tlv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"53"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"986"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;                                             &lt;span class="c1"&gt;// moeda (BRL)&lt;/span&gt;
        &lt;span class="nf"&gt;Tlv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"54"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"F2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CultureInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvariantCulture&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;// valor&lt;/span&gt;
        &lt;span class="nf"&gt;Tlv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"58"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"BR"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;                                              &lt;span class="c1"&gt;// país&lt;/span&gt;
        &lt;span class="nf"&gt;Tlv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"59"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;                                              &lt;span class="c1"&gt;// nome do recebedor&lt;/span&gt;
        &lt;span class="nf"&gt;Tlv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"60"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cidadeSanitizada&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;                                  &lt;span class="c1"&gt;// cidade do recebedor&lt;/span&gt;
        &lt;span class="nf"&gt;Tlv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"62"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;additionalData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;                                    &lt;span class="c1"&gt;// Additional Data (txid)&lt;/span&gt;
        &lt;span class="s"&gt;"6304"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                                        &lt;span class="c1"&gt;// id + tamanho do CRC; o valor vem já já&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;semCrc&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;CalcularCrc16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;semCrc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse é o maior bloco de código do post; daqui pra frente são funções de 3 a 10 linhas.&lt;/p&gt;

&lt;p&gt;Cinco detalhes que quebram o payload sem gerar erro nenhum:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;54&lt;/code&gt; (valor)&lt;/strong&gt;: ponto decimal e sempre 2 casas (&lt;code&gt;45.00&lt;/code&gt;, nunca &lt;code&gt;45,00&lt;/code&gt;). Daí o &lt;code&gt;CultureInfo.InvariantCulture&lt;/code&gt;: numa máquina em &lt;code&gt;pt-BR&lt;/code&gt;, &lt;code&gt;ToString("F2")&lt;/code&gt; sozinho devolve vírgula e o payload quebra.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;53&lt;/code&gt; (moeda)&lt;/strong&gt;: &lt;code&gt;986&lt;/code&gt;, o código ISO 4217 numérico do real. Não é &lt;code&gt;BRL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;52&lt;/code&gt; (MCC)&lt;/strong&gt;: &lt;code&gt;0000&lt;/code&gt; serve quando você não é um estabelecimento categorizado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;26&lt;/code&gt; (o bloco Pix)&lt;/strong&gt;: o guard de 99 chars na &lt;code&gt;Tlv&lt;/code&gt; é o limite genérico do EMV; o &lt;strong&gt;perfil Pix aperta o campo &lt;code&gt;26&lt;/code&gt; inteiro pra 77 caracteres&lt;/strong&gt;. GUI (14) + chave EVP (36) + overhead cabe folgado; uma chave de e-mail comprida pode estourar, então vale um teste pra esse caso.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;"6304"&lt;/code&gt; no fim&lt;/strong&gt;: campo &lt;code&gt;63&lt;/code&gt; (CRC) + tamanho &lt;code&gt;04&lt;/code&gt;; o valor de 4 hex é calculado a seguir, &lt;strong&gt;incluindo esses &lt;code&gt;"6304"&lt;/code&gt; no cálculo&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O CRC exigido pelo Manual do BR Code é o &lt;strong&gt;CRC-16/CCITT-FALSE&lt;/strong&gt;: polinômio &lt;code&gt;0x1021&lt;/code&gt;, valor inicial &lt;code&gt;0xFFFF&lt;/code&gt;, sem reflexão de bits, sem XOR final, sobre o payload inteiro. É um loop bit a bit. Se a matemática não te interessa, copie a função, confirme que ela passa no vetor de teste logo abaixo e &lt;strong&gt;pule pra "Válido" não é "aceito"&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;CalcularCrc16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;ushort&lt;/span&gt; &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0xFFFF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="p"&gt;^=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ushort&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
            &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="m"&gt;0x8000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
                &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ushort&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;^&lt;/span&gt; &lt;span class="m"&gt;0x1021&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ushort&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;crc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X4"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 4 dígitos hex, MAIÚSCULO&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ele tem um &lt;strong&gt;vetor de teste canônico&lt;/strong&gt;: &lt;code&gt;123456789&lt;/code&gt; tem que dar &lt;code&gt;0x29B1&lt;/code&gt;. Se o seu bate com isso, o algoritmo está certo, seja qual for o resto do payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Crc16_VetorPadrao&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"29B1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PixBrCodeGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalcularCrc16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"123456789"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  "Válido" não é "aceito": as armadilhas
&lt;/h2&gt;

&lt;p&gt;O resumo das três está lá em cima. Aqui é o porquê de cada uma, com o código e as fontes. É onde eu perdi tempo, e é o motivo real deste post existir.&lt;/p&gt;

&lt;p&gt;O que destravou foi parar de validar contra ferramentas genéricas e comparar byte a byte com um &lt;strong&gt;copia e cola real gerado pelo meu próprio banco&lt;/strong&gt;. Três diferenças apareceram.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpjcjzizc1m92ggedl4i3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpjcjzizc1m92ggedl4i3.png" alt="À esquerda, o app do banco recusando o QR com a mensagem Este QR Code é inválido. À direita, a tela de confirmação de pagamento de R$ 5,00 depois de ajustar a GUI e o formato da chave" width="800" height="629"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Armadilha 1: a caixa da GUI do arranjo importa na prática
&lt;/h3&gt;

&lt;p&gt;O campo &lt;code&gt;26&lt;/code&gt;, subcampo &lt;code&gt;00&lt;/code&gt;, é a string que identifica o arranjo de pagamento: &lt;code&gt;br.gov.bcb.pix&lt;/code&gt;. O &lt;strong&gt;Manual do BR Code e o Manual de Padrões para Iniciação do Pix escrevem essa GUI em minúsculo&lt;/strong&gt; nos exemplos, e há implementações que validam esperando exatamente minúsculo (veja a &lt;a href="https://github.com/bacen/pix-api/issues/189" rel="noopener noreferrer"&gt;issue #189 do &lt;code&gt;bacen/pix-api&lt;/code&gt;&lt;/a&gt;). O CRC não liga pra caixa: &lt;code&gt;br.gov.bcb.pix&lt;/code&gt; e &lt;code&gt;BR.GOV.BCB.PIX&lt;/code&gt; têm o mesmo tamanho, então o payload continua "estruturalmente válido" e o checksum fecha nos dois casos.&lt;/p&gt;

&lt;p&gt;Só que o copia e cola que o &lt;strong&gt;meu&lt;/strong&gt; banco gerava usava &lt;code&gt;BR.GOV.BCB.PIX&lt;/code&gt; em maiúsculo, e o leitor de QR dele parecia comparar essa string de forma sensível a caixa pra reconhecer que aquilo era um Pix. Trocar pra maiúsculo foi o que fez o "QR Code inválido" virar a tela de confirmação de pagamento.&lt;/p&gt;

&lt;p&gt;Não tenho como afirmar qual caixa é "a certa" pra todo PSP: a spec exibe minúsculo, o leitor do meu banco exigiu maiúsculo. Depois de trocar, testei o mesmo payload nos apps de Banco do Brasil, Bradesco, Itaú, Nubank e Inter: os cinco leram e pagaram. A regra que funcionou pra mim: &lt;strong&gt;gere um copia e cola pelo app do próprio banco do recebedor e espelhe exatamente a caixa que ele usa na GUI.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;O &lt;code&gt;PixBrCodeGenerator&lt;/code&gt; fixa &lt;code&gt;BR.GOV.BCB.PIX&lt;/code&gt; em maiúsculo, porque foi o que funcionou aqui. Mas o teste que vale não é "tem que ser maiúsculo": é "tem que bater com o que o banco do recebedor gera". Então eu extraio a GUI de um copia e cola real do banco alvo e comparo com isso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Gui_BateComOCopiaEColaDeReferencia&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// subcampo 00 do campo 26, extraído de um "copia e cola" gerado pelo app do banco alvo&lt;/span&gt;
    &lt;span class="c1"&gt;// (id "00" + tamanho "14" + valor):&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;guiReferencia&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0014BR.GOV.BCB.PIX"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PixBrCodeGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GerarPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"chave@exemplo.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Fulano"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"x1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guiReferencia&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se o banco do seu recebedor gerar a GUI em minúsculo, troque a &lt;code&gt;guiReferencia&lt;/code&gt; e o teste continua correto: ele valida consistência com a fonte real, não uma caixa fixa.&lt;/p&gt;

&lt;h3&gt;
  
  
  Armadilha 2: a chave precisa estar no formato exato do DICT
&lt;/h3&gt;

&lt;p&gt;O subcampo &lt;code&gt;01&lt;/code&gt; do campo &lt;code&gt;26&lt;/code&gt; é a chave Pix. Como o &lt;strong&gt;PSP pagador&lt;/strong&gt; é quem resolve a chave (ver a distinção lá em cima), é ele quem vai pegar essa string e procurar no DICT, o diretório de chaves do Banco Central. Se o formato não for exatamente o que o DICT guarda, ele não acha a chave e recusa, mesmo com o BR Code perfeito no resto.&lt;/p&gt;

&lt;p&gt;Os formatos, que estão na seção de chaves do Manual de Padrões para Iniciação do Pix:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tipo&lt;/th&gt;
&lt;th&gt;Formato exigido&lt;/th&gt;
&lt;th&gt;Exemplo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Telefone&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;+55&lt;/code&gt; seguido de DDD e número, &lt;strong&gt;sem&lt;/strong&gt; espaço, parênteses ou traço&lt;/td&gt;
&lt;td&gt;&lt;code&gt;+5531999998888&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPF&lt;/td&gt;
&lt;td&gt;só os 11 dígitos, sem pontuação&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12345678901&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CNPJ&lt;/td&gt;
&lt;td&gt;só os 14 dígitos, sem pontuação&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12345678000199&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E-mail&lt;/td&gt;
&lt;td&gt;minúsculo&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fulano@exemplo.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aleatória (EVP)&lt;/td&gt;
&lt;td&gt;o UUID como veio, minúsculo&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1ade9854-82d1-...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Por isso o bot tem um &lt;code&gt;ChavePixParser&lt;/code&gt; que normaliza o que a pessoa digita antes de salvar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// "telefone (31) 99999-8888"  -&amp;gt;  "+5531999998888"&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;NormalizarTelefone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;digitos&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsDigit&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;digitos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="m"&gt;12&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="m"&gt;13&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="n"&gt;digitos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"55"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"+"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;digitos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="m"&gt;11&lt;/span&gt;                               &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"+55"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;digitos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;                                      &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Armadilha 3: campo &lt;code&gt;01&lt;/code&gt; (Point of Initiation Method), omita
&lt;/h3&gt;

&lt;p&gt;O EMV tem um campo &lt;code&gt;01&lt;/code&gt; no nível raiz (não confundir com o &lt;code&gt;01&lt;/code&gt; de dentro do &lt;code&gt;26&lt;/code&gt;) chamado &lt;em&gt;Point of Initiation Method&lt;/em&gt;: &lt;code&gt;11&lt;/code&gt; = QR estático (reutilizável), &lt;code&gt;12&lt;/code&gt; = dinâmico (uso único).&lt;/p&gt;

&lt;p&gt;O Manual do BR Code trata esse campo como condicional, e pro QR estático vários geradores de referência simplesmente não colocam. Declarar &lt;code&gt;12&lt;/code&gt; num QR que é estático é pedir problema. &lt;strong&gt;Declarar &lt;code&gt;11&lt;/code&gt; explícito também é aceito&lt;/strong&gt;: é o valor certo pra estático, só redundante. Eu omito e nunca deu ruim; se preferir ser explícito, use &lt;code&gt;11&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Point of Initiation Method (01) fica de fora de propósito: condicional pra&lt;/span&gt;
&lt;span class="c1"&gt;// estático (11 explícito também vale), e os geradores de referência que eu&lt;/span&gt;
&lt;span class="c1"&gt;// comparei também omitem.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bônus: nome, cidade e txid não são texto livre
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nome (&lt;code&gt;59&lt;/code&gt;)&lt;/strong&gt; e &lt;strong&gt;cidade (&lt;code&gt;60&lt;/code&gt;)&lt;/strong&gt;: ASCII imprimível, sem acento, com limite de tamanho (25 e 15). &lt;code&gt;"João Cão"&lt;/code&gt; vira &lt;code&gt;"JOAO CAO"&lt;/code&gt;. Passar um acento aqui é outro jeito de gerar um payload que "quase" funciona.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;txid (subcampo &lt;code&gt;05&lt;/code&gt; do campo &lt;code&gt;62&lt;/code&gt;)&lt;/strong&gt;: só alfanumérico, até 25 chars. Quando você não tem um identificador específico pra transação, a convenção do Manual de Padrões é mandar &lt;code&gt;***&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Sanitizar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;texto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;texto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NormalizationForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FormD&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CharUnicodeInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUnicodeCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;UnicodeCategory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NonSpacingMark&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToUpperInvariant&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[..&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Virando imagem
&lt;/h2&gt;

&lt;p&gt;O payload é o "copia e cola". Pra virar QR Code é uma lib de imagem. Aqui, &lt;a href="https://github.com/codebude/QRCoder" rel="noopener noreferrer"&gt;QRCoder&lt;/a&gt;, 100% local:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;GerarPng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;gen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;QRCodeGenerator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateQrCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QRCodeGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ECCLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PngByteQRCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;GetGraphic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 20 px por módulo&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nível de correção de erro &lt;strong&gt;M&lt;/strong&gt; (~15%) já é suficiente pra Pix e é o que costuma ser recomendado. Não precisa de &lt;code&gt;Q&lt;/code&gt; ou &lt;code&gt;H&lt;/code&gt;, que só incham o QR.&lt;/p&gt;

&lt;p&gt;No contexto do bot, essas duas peças (&lt;code&gt;PixBrCodeGenerator&lt;/code&gt; e &lt;code&gt;PixQrCodeImageService&lt;/code&gt;) são as duas últimas etapas de um fluxo que roda inteiro na sua máquina:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj2sdgrls8lcp7seht28x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj2sdgrls8lcp7seht28x.png" alt="Fluxo do RachaContas: /fechar calcula as transferências, /pagar cria as cobranças, PixBrCodeGenerator monta o TLV e o CRC16, PixQrCodeImageService renderiza o PNG e o Telegram entrega o QR e o copia e cola, tudo local" width="800" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Como testar sem ficar abrindo o app do banco
&lt;/h2&gt;

&lt;p&gt;A estratégia que funcionou pra mim tem três camadas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Vetor canônico do CRC&lt;/strong&gt;: &lt;code&gt;123456789&lt;/code&gt; vira &lt;code&gt;29B1&lt;/code&gt;. Valida o algoritmo isolado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BR Codes reais conhecidamente válidos&lt;/strong&gt;: existem listas públicas de payloads Pix usadas justamente pra testar validadores. Recalcular o CRC de um deles e conferir com o valor publicado valida a sua implementação contra um caso real.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Um copia e cola gerado pelo seu banco&lt;/strong&gt;: cola num teste, recalcula o CRC, confere. Esse é o que pega as armadilhas de caixa e formato, porque é a "verdade" que o leitor real aceita.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Crc_DeUmCopiaECola_Real&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. gere um "copia e cola" no app do seu banco&lt;/span&gt;
    &lt;span class="c1"&gt;// 2. cole aqui SEM os 4 últimos caracteres (que são o CRC)&lt;/span&gt;
    &lt;span class="c1"&gt;// 3. ponha esses 4 caracteres em crcDoBanco&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;semCrc&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;&amp;lt; seu copia e cola, menos os 4 últimos chars &amp;gt;&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;crcDoBanco&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"XXXX"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crcDoBanco&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PixBrCodeGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalcularCrc16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;semCrc&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E, no fim, o teste que não dá pra automatizar: gerar um payload de R$ 0,01 pra você mesmo e pagar. Uma vez. Vale o centavo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quando isso &lt;strong&gt;não&lt;/strong&gt; é suficiente
&lt;/h2&gt;

&lt;p&gt;Antes: as duas formas de QR Pix, lado a lado.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;QR estático&lt;/th&gt;
&lt;th&gt;QR dinâmico&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Campo &lt;code&gt;01&lt;/code&gt; (Point of Initiation Method)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;11&lt;/code&gt; ou omitido&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Onde o valor mora&lt;/td&gt;
&lt;td&gt;no próprio payload (campo &lt;code&gt;54&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;numa cobrança criada via API do PSP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;O que o campo &lt;code&gt;26&lt;/code&gt; carrega&lt;/td&gt;
&lt;td&gt;a chave Pix&lt;/td&gt;
&lt;td&gt;uma URL (&lt;code&gt;location&lt;/code&gt;) que aponta pra cobrança&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Precisa de PSP / credencial&lt;/td&gt;
&lt;td&gt;não&lt;/td&gt;
&lt;td&gt;sim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Confirmação de pagamento&lt;/td&gt;
&lt;td&gt;nenhuma (você não sabe se foi pago)&lt;/td&gt;
&lt;td&gt;notificação/webhook quando a cobrança liquida&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;txid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;opcional (convenção: &lt;code&gt;***&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;obrigatório e único por cobrança&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serve pra&lt;/td&gt;
&lt;td&gt;"me paga R$ X" fixo, P2P, portfólio&lt;/td&gt;
&lt;td&gt;e-commerce, SaaS, cobrança recorrente&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Este post é só sobre o &lt;strong&gt;estático&lt;/strong&gt;, o que dá pra montar sozinho.&lt;/p&gt;

&lt;p&gt;Pix estático resolve "gerar uma cobrança". Ele &lt;strong&gt;não&lt;/strong&gt; resolve "saber que foi paga". Não tem webhook, não tem conciliação: o dinheiro cai na conta e ninguém te avisa programaticamente.&lt;/p&gt;

&lt;p&gt;No bot eu contornei com um botão &lt;strong&gt;"Já paguei"&lt;/strong&gt; que marca a cobrança manualmente. Pra um grupo de amigos, tudo bem: é confiança mútua e o valor é baixo.&lt;/p&gt;

&lt;p&gt;Se você precisa de &lt;strong&gt;baixa automática&lt;/strong&gt; (um SaaS cobrando mensalidade, um e-commerce), aí a história muda:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pix dinâmico&lt;/strong&gt; via API do seu PSP: em vez da chave, o campo &lt;code&gt;26&lt;/code&gt; carrega uma URL (&lt;code&gt;location&lt;/code&gt;) que aponta pra uma cobrança que você criou na API do PSP. Como essa cobrança é um recurso com &lt;code&gt;txid&lt;/code&gt; próprio e status no PSP, ele consegue te mandar um webhook no instante em que ela é liquidada. É isso que fecha a conciliação, e é justamente o que o estático não tem.&lt;/li&gt;
&lt;li&gt;Ou um &lt;strong&gt;gateway&lt;/strong&gt; que abstrai isso.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mas pra gerar um QR estático de "me paga X", o formato é público, cabe num arquivo, e você não precisa de mais nada além da chave de quem recebe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Perguntas frequentes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Preciso de um gateway pra gerar um Pix?&lt;/strong&gt;&lt;br&gt;
Não, pra um QR &lt;strong&gt;estático&lt;/strong&gt; (valor fixo, "me paga X"). O formato é público e você monta o payload com a chave de quem recebe. Gateway ou Pix &lt;strong&gt;dinâmico&lt;/strong&gt; via PSP só é necessário quando você precisa saber, programaticamente, que a cobrança foi paga.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A GUI &lt;code&gt;br.gov.bcb.pix&lt;/code&gt; é maiúscula ou minúscula?&lt;/strong&gt;&lt;br&gt;
Os manuais do Banco Central exibem em &lt;strong&gt;minúsculo&lt;/strong&gt;. Alguns leitores de QR reais comparam de forma sensível a caixa; o meu exigiu maiúsculo. A aposta segura é espelhar a caixa que o copia e cola do próprio banco do recebedor usa.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Como calcular o CRC16 do Pix?&lt;/strong&gt;&lt;br&gt;
CRC-16/CCITT-FALSE: polinômio &lt;code&gt;0x1021&lt;/code&gt;, valor inicial &lt;code&gt;0xFFFF&lt;/code&gt;, sem reflexão de bits, sem XOR final, sobre o payload inteiro incluindo os &lt;code&gt;6304&lt;/code&gt; finais. Vetor de teste: &lt;code&gt;123456789&lt;/code&gt; vira &lt;code&gt;29B1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Por que meu Pix copia e cola é "válido" mas o banco recusa?&lt;/strong&gt;&lt;br&gt;
As três causas mais comuns: a caixa da GUI &lt;code&gt;br.gov.bcb.pix&lt;/code&gt;, a chave fora do formato exato do DICT (o banco de quem paga não a encontra) e o campo &lt;code&gt;01&lt;/code&gt; (Point of Initiation Method) declarado errado num QR estático.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qual a diferença entre Pix copia e cola estático e dinâmico?&lt;/strong&gt;&lt;br&gt;
O &lt;strong&gt;estático&lt;/strong&gt; carrega a chave e (opcionalmente) o valor no próprio texto, não precisa de PSP e não avisa quando é pago. O &lt;strong&gt;dinâmico&lt;/strong&gt; carrega uma URL (&lt;code&gt;location&lt;/code&gt;) pra uma cobrança criada via API do PSP, exige credencial e devolve notificação na liquidação. Comparação campo a campo na seção "Quando isso não é suficiente".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qual o formato da chave de telefone no payload?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;+55&lt;/code&gt; seguido de DDD e número, sem espaço, parênteses ou traço: &lt;code&gt;+5531999998888&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qual valor usar no &lt;code&gt;txid&lt;/code&gt; quando não tenho um identificador?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;***&lt;/code&gt;, pela convenção do Manual de Padrões para Iniciação do Pix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referências
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.bcb.gov.br/content/estabilidadefinanceira/ativosdosite/Manual%20do%20BR%20Code.pdf" rel="noopener noreferrer"&gt;Manual do BR Code&lt;/a&gt; (Banco Central): estrutura TLV, lista de campos, CRC16.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.bcb.gov.br/content/estabilidadefinanceira/pix/Regulamento_Pix/II_ManualdePadroesparaIniciacaodoPix.pdf" rel="noopener noreferrer"&gt;Manual de Padrões para Iniciação do Pix&lt;/a&gt; (Banco Central): perfil Pix, GUI &lt;code&gt;br.gov.bcb.pix&lt;/code&gt;, tipos de QR, formato de chave, &lt;code&gt;txid&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bacen/pix-api/issues/189" rel="noopener noreferrer"&gt;Issue #189 do &lt;code&gt;bacen/pix-api&lt;/code&gt;&lt;/a&gt;: discussão sobre validação de CRC16 e EMV entre PSPs.&lt;/li&gt;
&lt;li&gt;Especificação EMVCo MPM: &lt;a href="https://www.emvco.com/emv-technologies/qr-codes/" rel="noopener noreferrer"&gt;EMV QR Code Specification for Payment Systems, Merchant-Presented Mode&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;A lição que fica: &lt;strong&gt;"válido" e "aceito" são coisas diferentes quando tem um banco de verdade lendo.&lt;/strong&gt; O CRC e a estrutura TLV você fecha com o manual. A caixa da GUI, o formato da chave no DICT e o campo &lt;code&gt;01&lt;/code&gt; só aparecem quando você compara byte a byte com um copia e cola gerado pelo app do banco do recebedor. Esse é o teste que importa, e é grátis.&lt;/p&gt;

&lt;p&gt;Você já viu um Pix "válido" ser recusado? Qual banco? Comenta aí; quero mapear se a caixa da GUI foi o meu banco sendo específico ou um padrão.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Código completo, com os testes: &lt;a href="https://github.com/victoraugustovalle/RachaContaBot" rel="noopener noreferrer"&gt;github.com/victoraugustovalle/RachaContaBot&lt;/a&gt;, arquivo &lt;code&gt;RachaContas.Bot/Services/PixBrCodeGenerator.cs&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Esse projeto faz parte do meu portfólio. Se quiser ver os outros (autenticação feita a sério, tempo real com SignalR, integrações de pagamento), está tudo em &lt;a href="https://victoraugusto.dev" rel="noopener noreferrer"&gt;victoraugusto.dev&lt;/a&gt;. Comentário e crítica são bem-vindos.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Testado contra Banco do Brasil, Bradesco, Itaú, Nubank e Inter em 19 de junho de 2026. Regras de PSP mudam sem aviso. Se você está lendo isto muito depois, revalide com o método da seção "Como testar".&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pix</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
