> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cakto.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Validação do número do cartão

> O dígito verificador (algoritmo de Luhn): o que é, o que a Cakto faz com ele e como conferir antes de chamar a API.

O último dígito de um cartão não é informação — é **conferência**. Ele é escolhido para fazer uma conta fechar, e essa conta é o **algoritmo de Luhn**.

Um número que não fecha essa conta **não existe**. Nenhuma adquirente do mundo autoriza, porque não há cartão emitido com aquele número. Conferir isso é uma operação local, de microssegundos, que não depende de rede.

***

## O que a Cakto faz

> **Tokenização recusa com 422**
>
> `POST /public_api/card-tokens/` (e `createToken`, no SDK) valida o dígito verificador **antes** de tokenizar. Número que não fecha volta `422`:
>
> ```json theme={null}
> { "number": ["Número de cartão inválido: o dígito verificador não confere. Confira se algum dígito foi trocado — este número não é cobrável em nenhuma adquirente."] }
> ```

Como o pagamento por cartão só aceita `cardToken`, um número que falha na tokenização **não chega a ser cobrado** — não existe outro caminho de número de cartão na API.

O checkout hospedado da Cakto também confere, direto no campo, enquanto o comprador digita.

> [!NOTE]
>
> A validação **não** diz que o cartão existe, que está ativo, que tem limite ou que não está bloqueado. Ela só descarta o que é impossível. Recusa por saldo, limite, antifraude ou emissor continua vindo da adquirente, como sempre.

***

## Como o algoritmo funciona

1. Percorra os dígitos **da direita para a esquerda**.
2. **Dobre um sim, um não**, começando pelo segundo (o penúltimo dígito do cartão).
3. Se o dobro passar de 9, **subtraia 9** (`8 × 2 = 16 → 7`).
4. Some tudo. Se o total for **múltiplo de 10**, o número passa.

Com `4111 1111 1111 1111`, os dobrados são sete `1 → 2` e o `4 → 8`; os outros oito dígitos entram como estão:

```
8 (não dobrados)  +  14 (sete 1→2)  +  8 (o 4→8)  =  30      30 % 10 = 0   ✅
```

Troque só o último dígito para `2` e a soma vira **31** — resto 1, recusado.

### O que ele pega

| erro | exemplo | pega? |
| - | - | - |
| um dígito trocado | `4111 1111 1111 111**2**` | ✅ |
| dois dígitos vizinhos invertidos | `**14**11 1111 1111 1111` | ✅ |
| número de comprimento impossível | `0`, vinte dígitos | ✅ |
| número inventado que por acaso fecha a conta | — | ❌ passa aqui, a adquirente recusa |

Os dois primeiros são os erros de digitação mais comuns de quem preenche um formulário, e é para eles que o dígito verificador existe.

***

## Como conferir antes de chamar a API

Vale conferir no seu lado mesmo com a API validando: o comprador vê o erro **no campo, no ato**, sem esperar o round-trip, e você não gasta chamada nem rate limit com um número que já nasce recusado.

```ts TypeScript theme={null}
export function passaNoLuhn(cardNumber: string): boolean {
  const digitos = cardNumber.replace(/\D/g, "");

  // Faixa do ISO/IEC 7812. O piso importa tanto quanto o teto: sem ele, um
  // único "0" fecharia o módulo 10 por coincidência.
  if (digitos.length < 12 || digitos.length > 19) return false;

  let total = 0;
  for (let i = 0; i < digitos.length; i++) {
    // Dobra um sim, um não, a partir do penúltimo (da direita para a esquerda).
    let valor = Number(digitos[digitos.length - 1 - i]);
    if (i % 2 === 1) {
      valor *= 2;
      if (valor > 9) valor -= 9;
    }
    total += valor;
  }

  return total % 10 === 0;
}
```

```python Python theme={null}
def passa_no_luhn(numero: str) -> bool:
    digitos = [c for c in numero if c.isdigit()]

    # Faixa do ISO/IEC 7812. O piso importa tanto quanto o teto: sem ele, um
    # único "0" fecharia o módulo 10 por coincidência.
    if not 12 <= len(digitos) <= 19:
        return False

    total = 0
    for i, caractere in enumerate(reversed(digitos)):
        valor = int(caractere)
        if i % 2:                      # dobra um sim, um não, a partir do penúltimo
            valor *= 2
            if valor > 9:
                valor -= 9
        total += valor

    return total % 10 == 0
```

As duas implementações **normalizam por dentro**: espaço e hífen que o comprador digita não invalidam o número. `4111 1111-1111.1111` passa.

***

## Dois enganos que custam caro

> **Detectar a bandeira não é validar o número**
>
> Uma função que resolve **bandeira e comprimento a partir do BIN** aceita `4111 1111 1111 1112` sem reclamar: o prefixo e o comprimento estão certos, só o dígito verificador não fecha.
>
> São checagens diferentes e independentes — faça as duas. Foi exatamente assim que um número inválido atravessou o formulário do nosso próprio checkout e só foi recusado pela adquirente, na autorização.

> **Validar só no navegador não protege o servidor**
>
> Validação no formulário melhora a experiência de quem digita, mas é **contornável**: qualquer um pode chamar a sua API diretamente.
>
> Se você tem backend próprio no meio do caminho, confira lá também. É por isso que a Cakto valida na tokenização, e não só no checkout.

***

## Números para teste

Estes fecham o dígito verificador e servem para exercitar a sua integração sem tomar `422`:

| número | bandeira |
| - | - |
| `4111 1111 1111 1111` | Visa |
| `5555 5555 5555 4444` | Mastercard |
| `3782 822463 10005` | Amex |

> [!WARNING]
>
> Fechar o Luhn **não** os torna cobráveis: são números de teste e a adquirente recusa a autorização. Eles servem para validar o seu fluxo até a tokenização, não para simular venda aprovada.

> [!NOTE]
>
> Se a sua suíte de testes usa número inventado, troque por um destes. Desde a mudança anunciada em [Changelog](/mudancas), número que não fecha o dígito verificador volta `422` na tokenização.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.