← Voltar ao blog

Boleto FEBRABAN em Node.js: gerar linha digitável para testes

·12 min de leitura

Boleto bancário é parte obrigatória de qualquer suite de testes que toque pagamentos brasileiros. O problema: a maioria das equipes copia números reais de produção para fixtures — o que viola a LGPD e cria dependências frágeis de dados externos. Este tutorial mostra como gerar uma linha digitável FEBRABAN válida do zero, em TypeScript, sem tocar em dados reais.

O que é a linha digitável e por que ela importa em testes

Diferença entre código de barras e linha digitável (FEBRABAN Manual de Normas 003.008)

O código de barras tem 44 dígitos e é lido opticamente. A linha digitável tem 47 dígitos (incluindo três dígitos verificadores de campo) e é o que o usuário digita quando o código de barras não escaneia. O Manual de Normas FEBRABAN 003.008 define os dois formatos e exige que ambos representem os mesmos dados — qualquer divergência entre eles deve ser rejeitada pelo banco cobrador.

Em testes, você precisa dos dois. Parsers de gateway geralmente aceitam qualquer um. Sistemas bancários legados aceitam só a linha digitável.

Campos obrigatórios: banco, agência, nosso número, vencimento, valor, dígito verificador

CampoPosição no código de barrasTamanhoExemplo
Código do banco1-33341 (Itaú)
Código da moeda419 (Real)
Dígito verificador geral51calculado
Fator de vencimento6-949999
Valor nominal10-19100000015000 (R$ 150,00)
Campo livre20-4425varia por banco

Por que testar com boletos reais é LGPD Art. 7º, IX — e quando deixa de ser

LGPD Art. 7º, IX permite tratamento de dados pessoais quando necessário para atender interesses legítimos do controlador. Isso não cobre fixtures de CI/CD: o CPF real do João está no seu banco de testes sem necessidade, sem base legal, e exposto a todos que têm acesso ao repositório. Use dados fictícios gerados algoritmicamente e a questão deixa de existir.

---

Estrutura do boleto FEBRABAN campo a campo

Campo livre e campos 1-2-3 (blocos 9.10 e 10.10 do Manual)

O campo livre (posições 20-44 do código de barras) é onde cada banco coloca seus dados internos: agência, conta, nosso número, carteira. Para montar a linha digitável, esse campo de 25 dígitos é dividido em três blocos:

  • Campo 1: banco(3) + moeda(1) + campo livre posições 1-5 = 9 dígitos + dígito verificador mod10
  • Campo 2: campo livre posições 6-15 = 10 dígitos + dígito verificador mod10
  • Campo 3: campo livre posições 16-25 = 10 dígitos + dígito verificador mod10

Fator de vencimento: cálculo a partir de 07/10/1997 (data base FEBRABAN)

O fator de vencimento é o número de dias corridos entre 07/10/1997 e a data de vencimento do boleto, com 4 dígitos. O valor máximo é 9999 (que corresponde a 18/02/2025 — data em que o ciclo recomeçou, passando a contar do fator 1000 de novo segundo atualização FEBRABAN de 2025). Boleto sem vencimento usa 0000.

Valor nominal: 10 dígitos sem separador decimal

R$ 150,00 vira 0000015000. R$ 1.234,56 vira 0000123456. Zero vírgulas, zero pontos, sempre 10 dígitos com padding esquerdo.

Dígito verificador geral: módulo 11 com pesos 2-9

O dígito geral (posição 5 do código de barras) é calculado sobre os outros 43 dígitos do código de barras (excluindo a própria posição 5). Os pesos ciclam de 2 a 9, da direita para a esquerda. Resto 0 e resto 1 retornam dígito 1 — essa é a exceção da spec FEBRABAN, diferente de outras implementações de módulo 11.

---

Algoritmo do dígito verificador passo a passo

Módulo 10 nos campos 1, 2 e 3

Cada campo da linha digitável termina com um dígito calculado via módulo 10:

1. Multiplique os dígitos alternando entre peso 2 (da direita) e peso 1. 2. Se o produto for maior que 9, some os algarismos do produto. 3. Some todos os resultados. 4. Dígito = 10 - (soma % 10). Se o resultado for 10, dígito = 0.

Módulo 11 no campo 4 (dígito geral do código de barras)

1. Concatene o código de barras sem a posição 5 (43 dígitos). 2. Multiplique da direita com pesos ciclando 2,3,4,5,6,7,8,9,2,3... 3. Some todos os produtos. 4. Resto = soma % 11. 5. Se resto == 0 ou resto == 1, dígito verificador = 1. 6. Caso contrário, dígito verificador = 11 - resto.

Casos especiais: resto 0 e resto 1 retornam dígito 1 (spec FEBRABAN)

Diferente do módulo 11 usado em CPF/CNPJ (onde resto 0 e 1 retornam 0), a spec do código de barras bancário retorna 1 nesses casos. Misturar as duas implementações é a fonte de erro mais comum.

---

Implementação em TypeScript: gerador de linha digitável

Tipos e interface BoletoParams

interface BoletoParams {
  bankCode: string;       // 3 dígitos: '001', '237', '341'
  currencyCode: string;   // sempre '9'
  freeField: string;      // 25 dígitos, específico por banco
  maturityFactor: string; // 4 dígitos, '0000' = sem vencimento
  value: string;          // 10 dígitos sem separador decimal
}

Função calcMod10(block: string): string

function calcMod10(block: string): string {
  let sum = 0;
  let weight = 2;
  for (let i = block.length - 1; i >= 0; i--) {
    let product = parseInt(block[i]) * weight;
    if (product > 9) product = Math.floor(product / 10) + (product % 10);
    sum += product;
    weight = weight === 2 ? 1 : 2;
  }
  const remainder = sum % 10;
  return remainder === 0 ? '0' : String(10 - remainder);
}

Função calcMod11Febraban(barcode: string): number

function calcMod11Febraban(barcode: string): number {
  // barcode aqui já exclui a posição 5 (43 dígitos)
  const weights = [2, 3, 4, 5, 6, 7, 8, 9];
  let sum = 0;
  let wi = 0;
  for (let i = barcode.length - 1; i >= 0; i--) {
    sum += parseInt(barcode[i]) * weights[wi % 8];
    wi++;
  }
  const remainder = sum % 11;
  if (remainder === 0 || remainder === 1) return 1;
  return 11 - remainder;
}

Função buildBarcode(params: BoletoParams): string

function buildBarcode(params: BoletoParams): string {
  const { bankCode, currencyCode, freeField, maturityFactor, value } = params;
  const withoutCheckDigit =
    bankCode + currencyCode + maturityFactor + value + freeField;
  const checkDigit = calcMod11Febraban(withoutCheckDigit);
  return bankCode + currencyCode + checkDigit + maturityFactor + value + freeField;
}

Função barcodeToDigitableLine(barcode: string): string

function barcodeToDigitableLine(barcode: string): string {
  const bank = barcode.slice(0, 3);
  const currency = barcode.slice(3, 4);
  const freeField = barcode.slice(19, 44);

  const block1Raw = bank + currency + freeField.slice(0, 5);
  const block1 = block1Raw + calcMod10(block1Raw);

  const block2Raw = freeField.slice(5, 15);
  const block2 = block2Raw + calcMod10(block2Raw);

  const block3Raw = freeField.slice(15, 25);
  const block3 = block3Raw + calcMod10(block3Raw);

  const checkDigit = barcode.slice(4, 5);
  const maturityAndValue = barcode.slice(5, 19);

  const f1 = `${block1.slice(0, 5)}.${block1.slice(5)}`;
  const f2 = `${block2.slice(0, 5)}.${block2.slice(5)}`;
  const f3 = `${block3.slice(0, 5)}.${block3.slice(5)}`;

  return `${f1} ${f2} ${f3} ${checkDigit} ${maturityAndValue}`;
}

Exemplo completo end-to-end:

const params: BoletoParams = {
  bankCode: '341',
  currencyCode: '9',
  freeField: '1234567890123456789012345',
  maturityFactor: '9999',
  value: '0000015000',
};

const barcode = buildBarcode(params);
const digitableLine = barcodeToDigitableLine(barcode);

console.log('Código de barras:', barcode);
console.log('Linha digitável: ', digitableLine);
// Linha digitável: 34191.23456 67890.123456 78901.234567 X 99990000015000

---

Gerando dados de suporte LGPD-safe para o boleto

CNPJ do cedente com /gerador-cnpj

O cedente precisa de CNPJ válido no campo livre de muitos bancos. Use a API REST do FakeForge para pegar um CNPJ fictício que passa no módulo 11:

curl "https://fakeforge.com.br/api/generate?type=cnpj&quantity=1&format=json"

A resposta inclui _meta.source e o CNPJ já validado. Passe direto para o campo livre do boleto sem nenhum pós-processamento.

CPF do pagador com /gerador-cpf

O nome e CPF do pagador aparecem no slip impresso mas não entram no cálculo do dígito verificador. Use /gerador-pessoa para gerar nome + CPF correlacionados — o mesmo endpoint gera primeiro nome, sobrenome e CPF coerentes com gênero e região.

Conta bancária e código de banco com /gerador-conta-bancaria

O campo livre varia por banco. Para Itaú (341), o campo livre inclui agência e conta no formato específico Itaú. Para Bradesco (237), é diferente. Use /gerador-conta-bancaria para obter agência, conta e código COMPE aleatórios de um dos 17 bancos reais suportados.

Por que usar dados fictícios correlacionados evita falsos positivos em CI

Um CNPJ aleatório inválido faz o validador de gateway rejeitar o boleto antes de chegar ao teste de pagamento. Um CPF real de produção faz o banco de dados de fraude sinalizar a transação em ambiente de staging. Dados fictícios válidos eliminam as duas falhas.

DICA: Gere os dados de suporte uma vez, commite no repositório como fixture JSON, e referencia o arquivo nos testes. Não faça chamada HTTP em tempo de teste — quebre o CI quando a rede não está disponível.

---

Validando a linha digitável gerada

Verificação manual via banco de testes (Banco do Brasil Sandbox, Sicoob Sandbox)

O Banco do Brasil disponibiliza sandbox em api.sandbox.bb.com.br. O Sicoob tem ambiente de homologação com documentação pública. Ambos aceitam boletos fictícios e retornam 200 se a linha digitável estiver correta.

Biblioteca boleto-utils como referência cruzada (sem dependência obrigatória)

O pacote boleto-utils (npm) implementa validação independente. Use como oráculo nos testes, não como dependência de produção:

describe('boleto gerado', () => {
  it('dígito verificador está correto', () => {
    const params: BoletoParams = {
      bankCode: '341',
      currencyCode: '9',
      freeField: '1234567890123456789012345',
      maturityFactor: '9999',
      value: '0000015000',
    };
    const barcode = buildBarcode(params);
    const line = barcodeToDigitableLine(barcode);

    // formato esperado: NNNNN.NNNNN NNNNN.NNNNNN NNNNN.NNNNNN N NNNNNNNNNNNNNN
    expect(line).toMatch(
      /^\d{5}\.\d{6} \d{5}\.\d{7} \d{5}\.\d{7} \d \d{14}$/
    );

    const checkDigit = parseInt(barcode[4]);
    const barcodeWithout = barcode.slice(0, 4) + barcode.slice(5);
    expect(calcMod11Febraban(barcodeWithout)).toBe(checkDigit);
  });
});

---

Integrando o gerador numa fixture de testes Node.js

Fixture com 3 boletos usando CNPJ/CPF fictícios + exportação JSON

const fixtures = [
  { bankCode: '341', value: '0000015000', maturityFactor: '9999' },
  { bankCode: '237', value: '0000250000', maturityFactor: '0000' }, // sem vencimento
  { bankCode: '001', value: '0000000000', maturityFactor: '9998' }, // valor livre
].map((params, i) => {
  const freeField = String(i + 1).padStart(25, '0');
  const full: BoletoParams = { ...params, currencyCode: '9', freeField };
  const barcode = buildBarcode(full);
  return {
    barcode,
    digitableLine: barcodeToDigitableLine(barcode),
    cnpjCedente: '11222333000181', // gerado via /gerador-cnpj
    cpfPagador:  '529.982.247-25', // gerado via /gerador-cpf
  };
});

console.log(JSON.stringify(fixtures, null, 2));

Seed determinístico: passando seed para reproduzir o mesmo conjunto em CI

O campo livre é derivado do índice no exemplo acima — determinístico por definição. Para dados de suporte (CNPJ, CPF), passe ?seed=42 na chamada à FakeForge API quando o endpoint suportar. Commite o resultado como arquivo .json em __fixtures__/boletos.json.

---

Casos de borda que quebram parsers de boleto

Fator de vencimento 0000 (boleto sem vencimento, FEBRABAN admite)

0000 é válido pela spec. Parsers que esperam fator >= 1000 rejeitam esse boleto silenciosamente. Inclua um caso com maturityFactor: '0000' na suite.

Valor 0000000000 (cobrança de valor livre)

Usado em carnês onde o pagador preenche o valor. Parsers que validam valor > 0 quebram aqui. Teste explicitamente.

Banco 001 vs 237 vs 341: variações no campo livre por instituição

O campo livre de 25 dígitos tem estrutura interna diferente por banco. Itaú usa os primeiros 3 dígitos para carteira. Bradesco usa os primeiros 3 para agência. Para testes de integração contra um gateway específico, construa o campo livre seguindo o manual do banco-alvo.

Boleto de convênio (campo livre diferente do modelo padrão)

Boletos de convênio (tributos, concessionárias) seguem o padrão 9NNNN, não o bancário. O cálculo do dígito verificador é diferente. Não misture os dois parsers.

---

Limites legais ao usar dados fictícios em testes

LGPD Art. 13 — anonimização como base legal para uso em pesquisa e teste

LGPD Art. 13 permite uso de dados anonimizados para pesquisa e desenvolvimento sem necessidade de consentimento. Dados gerados algoritmicamente (nunca associados a uma pessoa real) satisfazem esse critério. CPF gerado via /gerador-cpf não é dado pessoal — é um número que passa na validação de formato sem corresponder a nenhum titular.

Circular BACEN 3.952/2019 e o que ela diz sobre dados de pagamento em sandbox

A Circular BACEN 3.952/2019 regula o uso de dados de pagamento em ambiente de produção. Ambientes de sandbox com dados fictícios ficam fora do escopo — desde que os dados nunca transitem por infra de produção e não haja risco de confusão com transações reais.

O que não fazer: nunca usar CPF/CNPJ real em fixture de CI/CD

Um CPF real em um repositório Git é um dado pessoal exposto. Se o repo é público, é vazamento. Se é privado, ainda é tratamento sem base legal. Use /validar-cpf para confirmar que o CPF fictício passa na validação antes de commitar — e mantenha a origem fictícia documentada no próprio arquivo de fixture.

---

Resumo

  • Gere o código de barras com buildBarcode() e converta para linha digitável com barcodeToDigitableLine() — são funções puras, sem dependência externa.
  • Módulo 10 vai nos campos 1, 2 e 3 da linha digitável; módulo 11 com pesos 2-9 vai no dígito verificador geral (posição 5 do código de barras).
  • Resto 0 e resto 1 no módulo 11 FEBRABAN retornam dígito 1 — diferente de CPF/CNPJ.
  • Teste os três casos de borda obrigatórios: fator de vencimento 0000, valor 0000000000, e pelo menos dois bancos diferentes.
  • Use /gerador-cnpj, /gerador-cpf e /gerador-conta-bancaria para preencher CNPJ do cedente, CPF do pagador e campo livre sem tocar em dados reais.
  • Commite fixtures como JSON estático gerado com seed fixo — não faça chamadas HTTP durante a execução dos testes.

Perguntas frequentes

Como debugar se minha linha digitável foi rejeitada — erro no módulo 10 ou 11?+

Isolate. Se o gateway retornar 'invalid check digit', valide o módulo 11 do código de barras primeiro (posição 5). Se passar, erro está nos módulos 10 dos campos 1-2-3. Log cada componente antes de concatenar. Use parser online ou boleto-utils para cotejar.

Posso usar a mesma linha digitável em staging e produção?+

Não. Cada ambiente deve gerar boletos novos. Hardcodar linha em produção vincula ao cedente de staging ou expira. Gere dados por ambiente. Se reproduz em testes, passe os parâmetros e rederive localmente com seed fixo para reproducibilidade.

Minha linha digitável precisa bater com a do banco?+

Sim. Se discordam, há erro no campo livre ou dígito verificador. Teste contra sandbox do banco — retorna qual campo errou. Sem sandbox, coteje com boleto-utils. Se ambos divergem do seu cálculo, debug módulo 11 ou estrutura do campo livre.

Como construir o campo livre se o banco não está documentado?+

Consulte FEBRABAN 003.008 banco-específico. Se indisponível, reverta a engenharia observando boletos reais em SPED ou extratos. Campo livre contém agência, conta, carteira — varia por instituição. Último recurso: contacte suporte técnico do banco.

Se o vencimento for daqui 10 anos, qual fator uso?+

Máximo fator é 9999 (vence 18/fev/2025). Vencimentos posteriores usam `0000` com data em observação fiscal. FEBRABAN permite; bancos modernos aceitam. Ciclo reinicia conforme resoluções BACEN. Valide sempre contra documentação do banco-alvo.