Algoritmo Luhn para cartões de crédito: validação passo a passo
Algoritmo Luhn para cartões de crédito: validação passo a passo
O algoritmo de Luhn é a razão pela qual seu e-commerce rejeita "4111 1111 1111 1112" antes mesmo de consultar qualquer gateway. Uma soma de dígitos simples, calculada em menos de um milissegundo, filtra a maioria dos erros de digitação e impede que requisições inválidas cheguem à rede de pagamentos. Este artigo explica o algoritmo, mostra a implementação em TypeScript e cobre como gerar números de teste que passam na validação sem usar dados reais.
O que é o algoritmo de Luhn e por que todo número de cartão depende dele
Origem e propósito (Hans Peter Luhn, IBM, 1954)
Hans Peter Luhn, engenheiro da IBM, patenteou o algoritmo em 1954 como método de detecção de erros para sequências numéricas. O objetivo era simples: detectar erros de transcrição em um único passo aritmético, sem necessidade de consultar banco de dados ou rede. A patente expirou e o algoritmo passou a ser adotado como padrão pela ISO/IEC 7812, que regula a identificação de cartões financeiros.
Onde o algoritmo é aplicado hoje
Além de cartões Visa, Mastercard, Elo, Hipercard e Amex, o Luhn aparece em números de IMEI de celulares, tokens NFC e alguns formatos de código de rastreamento. No contexto de pagamentos, qualquer formulário de checkout minimamente correto roda o Luhn no cliente antes de fazer qualquer requisição ao backend.
O que o Luhn detecta e o que ele não detecta
O Luhn captura erros de digitação simples: um dígito trocado, dois dígitos adjacentes invertidos (exceto 09 ↔ 90). Ele não detecta fraude, não verifica se o cartão existe, não confirma limite disponível. Um número que passa no Luhn pode ser sinteticamente gerado, pertencer a um cartão cancelado, ou ter CVV e data de validade incorretos. A validação Luhn é uma pré-filtragem, não uma autorização.
Anatomia de um número de cartão de crédito
IIN/BIN: os 6 primeiros dígitos e o que identificam
O Issuer Identification Number (IIN), historicamente chamado BIN, ocupa os primeiros 6 dígitos. Eles identificam a bandeira, o banco emissor e o tipo de produto. A ISO/IEC 7812-1 expandiu o IIN para 8 dígitos em 2017, mas na prática os sistemas ainda usam os 6 primeiros como identificador primário de roteamento.
Dígitos de conta: posições 7 a 15
Os dígitos entre o BIN e o verificador final formam o número de conta do portador dentro do emissor. São gerados pelo banco, não pela bandeira.
Dígito verificador: posição final calculada pelo Luhn
O último dígito é calculado pelo algoritmo de Luhn sobre todos os dígitos anteriores. Não carrega informação de conta: serve exclusivamente para validação de integridade.
Diferença entre cartões de 16 dígitos e 15 dígitos
Visa, Mastercard, Elo e Hipercard usam 16 dígitos. Amex usa 15 dígitos com formato de exibição 4-6-5 (ex.: 3782 822463 10005). Essa diferença impacta a implementação do gerador: o tamanho total precisa ser parametrizado.
O algoritmo de Luhn passo a passo
Passo 1 — dobrar os dígitos alternados da direita para a esquerda
Partindo do penúltimo dígito (segundo a partir da direita), dobre cada dígito em posição par (contando da direita, a partir de 1). Os dígitos em posição ímpar permanecem inalterados.
Passo 2 — subtrair 9 quando o dobro excede 9
Se o dobro de um dígito resultar em valor maior que 9, subtraia 9. Isso equivale a somar os dois dígitos do resultado (ex.: 8 × 2 = 16 → 1 + 6 = 7, ou 16 - 9 = 7).
Passo 3 — somar todos os dígitos
Some todos os valores: os dígitos dobrados (após ajuste) e os dígitos que permaneceram inalterados.
Passo 4 — verificar se a soma é divisível por 10
Se soma % 10 === 0, o número é válido pelo Luhn.
Exemplo manual com número fictício (16 dígitos)
Número: 4539 1488 0343 6467
| Posição (da direita) | Dígito | Dobrado? | Valor após ajuste |
|---|---|---|---|
| 1 | 7 | não | 7 |
| 2 | 6 | sim | 12 → 3 |
| 3 | 4 | não | 4 |
| 4 | 6 | sim | 12 → 3 |
| 5 | 3 | não | 3 |
| 6 | 4 | sim | 8 |
| 7 | 3 | não | 3 |
| 8 | 0 | sim | 0 |
| 9 | 8 | não | 8 |
| 10 | 8 | sim | 16 → 7 |
| 11 | 4 | não | 4 |
| 12 | 1 | sim | 2 |
| 13 | 9 | não | 9 |
| 14 | 3 | sim | 6 |
| 15 | 5 | não | 5 |
| 16 | 4 | sim | 8 |
Soma: 7+3+4+3+3+8+3+0+8+7+4+2+9+6+5+8 = 80. Divisível por 10: número válido.
Implementação de validação em TypeScript
Função luhnCheck(card: string): boolean
function luhnCheck(card: string): boolean {
const digits = card.replace(/\D/g, "").split("").map(Number);
if (digits.length < 13 || digits.length > 19) return false;
let sum = 0;
let double = false;
for (let i = digits.length - 1; i >= 0; i--) {
let d = digits[i];
if (double) {
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
double = !double;
}
return sum % 10 === 0;
}Tratamento de entrada: espaços, hífens, caracteres não numéricos
A linha card.replace(/\D/g, "") remove espaços, hífens e qualquer caractere não numérico antes do cálculo. Isso cobre os formatos comuns de exibição (4539-1488-0343-6467, 4539 1488 0343 6467) sem exigir sanitização prévia por quem chama a função.
Testes unitários com vitest
import { describe, it, expect } from "vitest";
import { luhnCheck } from "./luhn";
describe("luhnCheck", () => {
it("valida número Visa fictício", () => {
expect(luhnCheck("4539148803436467")).toBe(true);
});
it("valida número Mastercard fictício", () => {
expect(luhnCheck("5500005555555559")).toBe(true);
});
it("valida número Elo fictício", () => {
expect(luhnCheck("6363680000000007")).toBe(true);
});
it("valida número Amex fictício (15 dígitos)", () => {
expect(luhnCheck("378282246310005")).toBe(true);
});
it("rejeita número com dígito verificador errado", () => {
expect(luhnCheck("4539148803436468")).toBe(false);
});
it("aceita entrada formatada com espaços", () => {
expect(luhnCheck("4539 1488 0343 6467")).toBe(true);
});
it("rejeita entrada muito curta", () => {
expect(luhnCheck("4111")).toBe(false);
});
});Geração de números válidos para testes
Estratégia: fixar BIN real, gerar dígitos aleatórios, calcular verificador
Para gerar um número que passe no Luhn, o processo é o inverso da validação: escolha um BIN, preencha os dígitos intermediários com valores aleatórios e calcule o dígito verificador que torna a soma divisível por 10.
Função generateCardNumber(bin: string, length: number): string
function generateCardNumber(bin: string, length: number): string {
const partial = bin.split("").map(Number);
while (partial.length < length - 1) {
partial.push(Math.floor(Math.random() * 10));
}
let sum = 0;
let double = true;
for (let i = partial.length - 1; i >= 0; i--) {
let d = partial[i];
if (double) {
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
double = !double;
}
const checkDigit = (10 - (sum % 10)) % 10;
return [...partial, checkDigit].join("");
}
// Exemplos de uso
generateCardNumber("4", 16); // Visa
generateCardNumber("51", 16); // Mastercard
generateCardNumber("636368", 16); // Elo
generateCardNumber("37", 15); // AmexBINs de teste mais usados por bandeira
| Bandeira | BIN de teste | Dígitos totais |
|---|---|---|
| Visa | 4 | 16 |
| Mastercard (clássico) | 51, 52, 53, 54, 55 | 16 |
| Mastercard (IIN moderno) | 2221 a 2720 | 16 |
| Elo | 636368 | 16 |
| Hipercard | 6062 | 16 |
| Amex | 34, 37 | 15 |
Por que número válido pelo Luhn não é um cartão real
Um número gerado por generateCardNumber passa na validação de formato. Não passa na autorização de nenhum gateway em produção porque o emissor precisa reconhecer o PAN em seus registros internos. Número sintético não existe nesses registros. Em sandbox, gateways como Cielo e Mercado Pago fornecem seus próprios conjuntos de cartões de teste que retornam respostas simuladas predefinidas.
AVISO: Nunca use números gerados sinteticamente em ambiente de produção. Mesmo que o gateway rejeite a transação, o número pode aparecer em logs de auditoria e ser associado a um titular real se coincidir com um PAN ativo.
Diferenças de implementação por bandeira
Visa: sempre 16 dígitos, BIN começa com 4
Todo cartão Visa começa com 4 e tem 16 dígitos. Cartões Visa de 13 dígitos saíram de circulação ativa em 2006.
Mastercard: 16 dígitos, BINs 2221-2720 e 51-55
A faixa 2221-2720 foi adicionada em 2017 para expandir a capacidade de emissão. Implementações antigas que só verificam 51-55 rejeitam cartões Mastercard modernos.
Elo: 16 dígitos, múltiplos BIN ranges emitidos por bancos brasileiros
Elo é uma bandeira brasileira gerida pela Elo Serviços S.A., com BINs emitidos por Bradesco, Caixa e Banco do Brasil (BACEN). Os ranges incluem 636368, 509091 e 627780, entre outros. Para testes, 636368 é o mais documentado publicamente.
Hipercard: 16 dígitos, BIN 6062
Hipercard pertence ao Itaú Unibanco. O BIN 6062 é o mais associado à bandeira em ambientes de teste.
Amex: 15 dígitos, BIN 34 ou 37, formato 4-6-5
Amex exibe o número no formato 3782 822463 10005. Os 15 dígitos e o BIN diferenciado exigem tratamento específico nas máscaras de input e nos validadores de comprimento.
Casos de uso legítimos em testes de software
Ambientes de homologação de gateways
Cielo, PagSeguro e Mercado Pago mantêm sandboxes com cartões de teste documentados que simulam aprovação, recusa por saldo, recusa por dados inválidos e timeout. Nesses ambientes, números gerados sinteticamente costumam retornar "cartão inválido" porque o sandbox valida o PAN contra uma lista interna.
Cobertura de formulários
O principal uso de cartões sintéticos é testar o formulário em si: validação de máscara, comportamento ao digitar caracteres inválidos, mensagem de erro ao falhar no Luhn, detecção automática de bandeira pelo BIN.
Pipelines de CI/CD: fixtures determinísticas
// fixtures/cards.ts
export const TEST_CARDS = {
visa: "4539148803436467",
mastercard: "5500005555555559",
elo: "6363680000000007",
amex: "378282246310005",
invalid: "4539148803436468",
} as const;Fixtures fixas garantem que o mesmo número seja usado em cada execução do CI, sem gerar valores novos que possam coincidir com PANs ativos.
Fuzz testing em campos de pagamento
Gere centenas de números que passam no Luhn e centenas que falham, e verifique que frontend e backend tratam cada caso de forma consistente. Bibliotecas como fast-check permitem declarar propriedades: "qualquer string que passe no Luhn deve ser aceita pelo campo de entrada sem erro de formato".
LGPD e o uso de dados de cartão em testes
Por que dados de cartão real em ambiente de teste violam a LGPD Art. 46 e PCI DSS 6.3.2
A LGPD Art. 46 exige que dados pessoais sejam protegidos por medidas técnicas adequadas em todas as fases do tratamento. Dados de cartão real em ambiente de desenvolvimento tipicamente ficam em bancos sem os controles de produção, acessíveis a mais pessoas e sem auditoria equivalente. O PCI DSS 6.3.2 proíbe explicitamente o uso de dados de produção em ambientes de desenvolvimento e teste sem pseudonimização ou tokenização.
Dado gerado algoritmicamente não é dado pessoal
A LGPD Art. 5º, I define dado pessoal como informação relacionada a pessoa natural identificada ou identificável. Um número gerado sinteticamente sem vínculo com titular real não é dado pessoal. Seu armazenamento e uso em testes não está sujeito às obrigações do Capítulo II da LGPD.
Obrigação de pseudonimização e como dados sintéticos cumprem essa exigência
A pseudonimização (LGPD Art. 12) substitui informações identificadoras por tokens ou valores artificiais. Dados sintéticos vão além: não partem de dados reais, eliminando o risco de re-identificação. Para fins de conformidade em ambiente de teste, dado sintético é preferível a pseudonimizado.
BACEN Resolução 4.893/2021
A Resolução 4.893/2021 do BACEN exige segregação entre ambientes de desenvolvimento e produção e controles equivalentes para dados sensíveis de clientes em instituições financeiras reguladas. Dados sintéticos cumprem essa exigência sem overhead operacional.
Armadilhas comuns ao gerar cartões de teste
Usar BINs de cartões reais ativos em produção
Se o BIN gerado coincidir com um emissor ativo e o número coincidir com um PAN real, o valor pode aparecer em logs de sistemas legados que fazem lookup por PAN parcial. Use BINs documentados como reservados para testes quando disponíveis.
Esquecer que CVV e data de validade são independentes do Luhn
O algoritmo valida apenas o PAN. CVV, data de validade e nome do titular são campos independentes, verificados pelo emissor durante a autorização. Número válido pelo Luhn com CVV errado passa na validação de formato e falha na autorização.
Hardcodar números no repositório
Fixtures de cartão no repositório aparecem em git log para sempre, mesmo após remoção. Se alguém processar o histórico em busca de PANs, os fixtures serão sinalizados por scanners de PCI. Prefira gerar os números no setup do teste ou carregá-los de variáveis de ambiente.
Confundir "válido pelo Luhn" com "cartão aprovado"
Nenhum gateway aprova uma transação com base apenas no Luhn. A validação Luhn é cliente-side, pré-rede. A aprovação depende de autorização online com o emissor.
Como o FakeForge BR gera cartões de teste
O gerador de cartões cobre cinco bandeiras: Visa, Mastercard, Elo, Hipercard e Amex.
BINs usados por bandeira e aleatorização dos dígitos intermediários
Internamente, o gerador escolhe um BIN por bandeira, preenche os dígitos intermediários com valores aleatórios via crypto.getRandomValues e calcula o verificador pelo método descrito acima. Nenhum BIN ativo de emissor real em produção é usado como padrão.
Campos retornados: número, titular, validade, CVV, bandeira
Cada cartão gerado inclui número, titular (nome fictício do gerador de pessoa), data de validade (MM/AA entre 1 e 5 anos no futuro), CVV (3 dígitos, 4 para Amex) e bandeira.
Export via API REST com format=json, csv ou sql
curl "https://fakeforge.com.br/api/generate?type=cartao&quantity=5&format=json"Resposta JSON:
{
"_meta": {
"source": "FakeForge BR",
"url": "https://fakeforge.com.br",
"generated_at": "2026-06-13T14:00:00Z",
"license": "CC0 (public domain test data)"
},
"type": "cartao",
"quantity": 5,
"data": [
{
"numero": "4539148803436467",
"titular": "Carlos Eduardo Mendes",
"validade": "09/29",
"cvv": "372",
"bandeira": "Visa"
}
]
}Para seeds de banco, use format=sql:
-- Generated by FakeForge BR (https://fakeforge.com.br)
-- Generated at: 2026-06-13T14:00:00Z
-- License: CC0 (public domain test data)
CREATE TABLE IF NOT EXISTS cartoes (
numero VARCHAR(19),
titular VARCHAR(100),
validade VARCHAR(7),
cvv VARCHAR(4),
bandeira VARCHAR(20)
);
INSERT INTO cartoes (numero, titular, validade, cvv, bandeira) VALUES
('4539148803436467', 'Carlos Eduardo Mendes', '09/29', '372', 'Visa'),
('5500005555555559', 'Ana Beatriz Silva', '03/28', '841', 'Mastercard');A documentação completa da API cobre todos os parâmetros e formatos de saída. Para uso em pipelines de CI/CD com volume acima de 100 chamadas por dia, veja os planos disponíveis.
Resumo
- O algoritmo de Luhn valida integridade de formato, não autorização. Um número válido pelo Luhn pode ser sintético, cancelado ou inexistente no emissor.
- A implementação em TypeScript itera da direita para a esquerda, dobra posições alternadas, subtrai 9 se o resultado exceder 9 e verifica se a soma é divisível por 10. A função cabe em menos de 15 linhas.
- Para gerar números de teste, fixe um BIN por bandeira, randomize os dígitos intermediários e calcule o verificador. Nunca use BINs de produção ativos sem necessidade documentada e nunca faça commit de PANs no repositório.
- Dados de cartão real em ambiente de teste violam a LGPD Art. 46 e PCI DSS 6.3.2. Dados sintéticos não são dados pessoais (LGPD Art. 5º, I) e eliminam esse risco sem custo operacional adicional.
- CVV e data de validade são independentes do Luhn e precisam ser tratados como campos separados nos testes de formulário e integração.
- O FakeForge BR gera cartões sintéticos por bandeira com export JSON, CSV e SQL, prontos para fixtures de CI/CD, seeds de banco e testes de formulário sem dados reais.
Perguntas frequentes
Preciso validar o algoritmo de Luhn no servidor se já valido no cliente?+
Sim, sempre. Cliente pode ser manipulado via DevTools. Servidor deve validar todo input antes de encaminhar ao gateway, independente de ter passado no Luhn. Luhn é apenas pré-filtragem: detecta typos, não fraude. Gateway também faz suas próprias validações (CVV, autorização, limite). Nunca confie apenas em validação client-side para dados sensíveis.
Se meu gerador criar um número que coincida com um cartão real, posso ser processado?+
Improvável, mas não zero. Números gerados sinteticamente sem vínculo intencional com PAN real não configuram crime (Lei 12.737/2012 exige propósito). Porém, é razão para usar BINs de teste documentados (4111, 5555, etc) em ambientes de teste. Nunca comita números em repositório: aparecem em git log permanentemente e configuram negligência.
Como importar o validador de Luhn no meu projeto Node/TypeScript?+
O gerador do FakeForge pode ser usado via REST API (`/api/generate`). Para integração local, copie a função `luhnCheck` do artigo e salve em `src/utils/luhn.ts`. Importe com `import { luhnCheck } from '@/utils/luhn'`. Se preferir package externo, `credit-card-type` (npm) combina Luhn + detecção de bandeira. Evite carregar toda lógica como dependency: função tem <50 linhas, dependência é overhead.
Testar validação Luhn no formulário é suficiente ou devo testar com sandbox do gateway?+
Luhn testa apenas formato. Sandbox do Cielo, PagSeguro ou Mercado Pago testa fluxo real: seus dígitos + CVV + data + autorização mock. Sempre execute ambos: 1) testes unitários (Luhn, edge cases), 2) testes de integração (sandbox), 3) testes em produção (valor baixo). Descobrir que Luhn está correto mas gateway rejeita tudo é sinal de que faltaram testes de integração.
Meu número gerado passou no Luhn mas o gateway retorna 'cartão inválido'. O que verificar?+
Verifique nesta ordem: 1) CVV tem 3-4 dígitos (Amex usa 4)? 2) Validade está futura (MM/AA)? 3) BIN está na lista de cartões de teste do gateway? 4) Você mockó as chamadas HTTP ao gateway em testes unitários? Se sim, a rejeição é do seu mock, não do Luhn. Rodar contra sandbox real filtra esses problemas. Nunca assuma que Luhn é garantia de aceitação.