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

# Cartões de teste

> Todos os cartões de teste do sandbox: quais passam, quais falham, e um gerador de número válido.

export const CardGenerator = ({lang = 'pt'}) => {
  const BRANDS = [{
    digit: '4',
    name: 'Visa'
  }, {
    digit: '5',
    name: 'Mastercard'
  }, {
    digit: '6',
    name: 'Elo'
  }, {
    digit: '3',
    name: 'Hipercard'
  }];
  const TONE_COLORS = {
    success: '#16a34a',
    error: '#dc2626',
    warning: '#d97706',
    info: '#2563eb'
  };
  const SCEN_TONE = {
    '0000': 'success',
    '0010': 'info',
    1001: 'error',
    1002: 'error',
    1003: 'error',
    1004: 'error',
    1005: 'error',
    1006: 'error',
    1007: 'error',
    1008: 'error',
    1009: 'error',
    1010: 'error',
    1011: 'error',
    1012: 'error',
    1013: 'error',
    2001: 'warning',
    2002: 'warning',
    2003: 'warning',
    2004: 'warning',
    2005: 'warning',
    3001: 'warning',
    9999: 'error'
  };
  const GROUPS = [{
    key: 'approved',
    items: ['0000', '0010']
  }, {
    key: 'declined',
    items: ['1001', '1002', '1003', '1004', '1005', '1006', '1007', '1008', '1009', '1010', '1011', '1012', '1013']
  }, {
    key: 'temp',
    items: ['2001', '2002', '2003', '2004', '2005']
  }, {
    key: 'special',
    items: ['3001', '9999']
  }];
  const I18N = {
    pt: {
      title: 'Gerador de cartão de teste',
      desc: 'Escolha a bandeira e o cenário. O número gerado é sempre Luhn-válido (passa no checkout) e termina no last-4 que dispara o cenário no sandbox.',
      brand: 'Bandeira',
      scenario: 'Cenário',
      button: 'Gerar cartão',
      copy: 'Copiar',
      copied: 'Copiado!',
      validity: 'Validade: qualquer data futura (ex.: 12/30) · CVV: qualquer (ex.: 123, ignorado pelo sandbox)',
      groups: {
        approved: 'Aprovado',
        declined: 'Recusado (permanente)',
        temp: 'Falha temporária (retryável)',
        special: 'Especial'
      },
      scen: {
        '0000': 'Aprovado (paid)',
        '0010': 'Aguardando pagamento (waiting_payment)',
        1001: 'Saldo insuficiente — INSUFFICIENT_FUNDS',
        1002: 'Cartão recusado — CARD_DECLINED',
        1003: 'Cartão expirado — EXPIRED_CARD',
        1004: 'Número inválido — INVALID_CARD_NUMBER',
        1005: 'CVV inválido — INVALID_CVV',
        1006: 'Suspeita de fraude — FRAUD_SUSPECTED',
        1007: 'Não autorizado — DO_NOT_HONOR',
        1008: 'Limite excedido — EXCEEDED_LIMIT',
        1009: 'Cartão restrito — RESTRICTED_CARD',
        1010: 'Cartão bloqueado — LOST_STOLEN_CARD',
        1011: 'Não permitido — NOT_PERMITTED',
        1012: 'Valor inválido — INVALID_AMOUNT',
        1013: 'Transação duplicada — DUPLICATE_TRANSACTION',
        2001: 'Erro de processamento — PROCESSING_ERROR',
        2002: 'Timeout do gateway — GATEWAY_TIMEOUT',
        2003: 'Erro de rede — NETWORK_ERROR',
        2004: 'Emissor indisponível — ISSUER_UNAVAILABLE',
        2005: 'Sem rota — NO_ROUTE',
        3001: 'Timeout lento (5s) — GATEWAY_TIMEOUT',
        9999: 'Erro inesperado (exception)'
      }
    },
    en: {
      title: 'Test card generator',
      desc: 'Pick the brand and scenario. The generated number is always Luhn-valid (passes checkout) and ends in the last-4 that triggers the scenario in the sandbox.',
      brand: 'Brand',
      scenario: 'Scenario',
      button: 'Generate card',
      copy: 'Copy',
      copied: 'Copied!',
      validity: 'Expiry: any future date (e.g. 12/30) · CVV: any (e.g. 123, ignored by the sandbox)',
      groups: {
        approved: 'Approved',
        declined: 'Declined (permanent)',
        temp: 'Temporary failure (retryable)',
        special: 'Special'
      },
      scen: {
        '0000': 'Approved (paid)',
        '0010': 'Awaiting payment (waiting_payment)',
        1001: 'Insufficient funds — INSUFFICIENT_FUNDS',
        1002: 'Card declined — CARD_DECLINED',
        1003: 'Expired card — EXPIRED_CARD',
        1004: 'Invalid number — INVALID_CARD_NUMBER',
        1005: 'Invalid CVV — INVALID_CVV',
        1006: 'Suspected fraud — FRAUD_SUSPECTED',
        1007: 'Do not honor — DO_NOT_HONOR',
        1008: 'Limit exceeded — EXCEEDED_LIMIT',
        1009: 'Restricted card — RESTRICTED_CARD',
        1010: 'Lost/stolen card — LOST_STOLEN_CARD',
        1011: 'Not permitted — NOT_PERMITTED',
        1012: 'Invalid amount — INVALID_AMOUNT',
        1013: 'Duplicate transaction — DUPLICATE_TRANSACTION',
        2001: 'Processing error — PROCESSING_ERROR',
        2002: 'Gateway timeout — GATEWAY_TIMEOUT',
        2003: 'Network error — NETWORK_ERROR',
        2004: 'Issuer unavailable — ISSUER_UNAVAILABLE',
        2005: 'No route — NO_ROUTE',
        3001: 'Slow timeout (5s) — GATEWAY_TIMEOUT',
        9999: 'Unexpected error (exception)'
      }
    },
    es: {
      title: 'Generador de tarjeta de prueba',
      desc: 'Elige la marca y el escenario. El número generado siempre es Luhn-válido (pasa el checkout) y termina en los últimos 4 que activan el escenario en el sandbox.',
      brand: 'Marca',
      scenario: 'Escenario',
      button: 'Generar tarjeta',
      copy: 'Copiar',
      copied: '¡Copiado!',
      validity: 'Vencimiento: cualquier fecha futura (ej.: 12/30) · CVV: cualquiera (ej.: 123, ignorado por el sandbox)',
      groups: {
        approved: 'Aprobado',
        declined: 'Rechazado (permanente)',
        temp: 'Fallo temporal (reintentable)',
        special: 'Especial'
      },
      scen: {
        '0000': 'Aprobado (paid)',
        '0010': 'Esperando pago (waiting_payment)',
        1001: 'Saldo insuficiente — INSUFFICIENT_FUNDS',
        1002: 'Tarjeta rechazada — CARD_DECLINED',
        1003: 'Tarjeta vencida — EXPIRED_CARD',
        1004: 'Número inválido — INVALID_CARD_NUMBER',
        1005: 'CVV inválido — INVALID_CVV',
        1006: 'Sospecha de fraude — FRAUD_SUSPECTED',
        1007: 'No autorizado — DO_NOT_HONOR',
        1008: 'Límite excedido — EXCEEDED_LIMIT',
        1009: 'Tarjeta restringida — RESTRICTED_CARD',
        1010: 'Tarjeta bloqueada — LOST_STOLEN_CARD',
        1011: 'No permitido — NOT_PERMITTED',
        1012: 'Monto inválido — INVALID_AMOUNT',
        1013: 'Transacción duplicada — DUPLICATE_TRANSACTION',
        2001: 'Error de procesamiento — PROCESSING_ERROR',
        2002: 'Timeout del gateway — GATEWAY_TIMEOUT',
        2003: 'Error de red — NETWORK_ERROR',
        2004: 'Emisor no disponible — ISSUER_UNAVAILABLE',
        2005: 'Sin ruta — NO_ROUTE',
        3001: 'Timeout lento (5s) — GATEWAY_TIMEOUT',
        9999: 'Error inesperado (exception)'
      }
    }
  };
  const t = I18N[lang] || I18N.pt;
  const luhnChecksum = digits => {
    let sum = 0;
    let isDouble = false;
    for (let i = digits.length - 1; i >= 0; i--) {
      let d = digits[i];
      if (isDouble) {
        d *= 2;
        if (d > 9) d -= 9;
      }
      sum += d;
      isDouble = !isDouble;
    }
    return sum % 10;
  };
  const buildLuhnValidCard = (brandDigit, last4) => {
    const digits = [Number(brandDigit), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Number(last4[0]), Number(last4[1]), Number(last4[2]), Number(last4[3])];
    digits[11] = 0;
    if (luhnChecksum(digits) !== 0) {
      for (let d = 0; d <= 9; d++) {
        digits[11] = d;
        if (luhnChecksum(digits) === 0) break;
      }
    }
    return digits.join('').replace(/(.{4})/g, '$1 ').trim();
  };
  const [brand, setBrand] = React.useState('4');
  const [last4, setLast4] = React.useState('0000');
  const [card, setCard] = React.useState(null);
  const [copied, setCopied] = React.useState(false);
  const generate = () => {
    setCard(buildLuhnValidCard(brand, last4));
    setCopied(false);
  };
  const copy = () => {
    if (!card) return;
    navigator.clipboard?.writeText(card.replace(/\s/g, ''));
    setCopied(true);
    setTimeout(() => setCopied(false), 1500);
  };
  const tone = SCEN_TONE[last4] || 'success';
  const brandName = BRANDS.find(b => b.digit === brand)?.name ?? 'Visa';
  const fieldStyle = {
    width: '100%',
    padding: '8px 10px',
    borderRadius: '8px',
    border: '1px solid rgba(128,128,128,0.35)',
    background: 'rgba(128,128,128,0.06)',
    color: 'inherit',
    fontSize: '14px'
  };
  return <div style={{
    border: '1px solid rgba(128,128,128,0.25)',
    borderRadius: '14px',
    padding: '20px',
    background: 'rgba(128,128,128,0.04)',
    margin: '16px 0'
  }}>
      <div style={{
    fontWeight: 700,
    fontSize: '16px',
    marginBottom: '4px'
  }}>
        {t.title}
      </div>
      <div style={{
    fontSize: '13px',
    opacity: 0.75,
    marginBottom: '16px'
  }}>
        {t.desc}
      </div>

      <div style={{
    display: 'flex',
    gap: '12px',
    flexWrap: 'wrap',
    marginBottom: '14px'
  }}>
        <div style={{
    flex: '1 1 140px'
  }}>
          <label style={{
    fontSize: '12px',
    fontWeight: 600,
    opacity: 0.8
  }}>
            {t.brand}
          </label>
          <select style={fieldStyle} value={brand} onChange={e => setBrand(e.target.value)}>
            {BRANDS.map(b => <option key={b.digit} value={b.digit}>
                {b.name}
              </option>)}
          </select>
        </div>
        <div style={{
    flex: '2 1 260px'
  }}>
          <label style={{
    fontSize: '12px',
    fontWeight: 600,
    opacity: 0.8
  }}>
            {t.scenario}
          </label>
          <select style={fieldStyle} value={last4} onChange={e => setLast4(e.target.value)}>
            {GROUPS.map(g => <optgroup key={g.key} label={t.groups[g.key]}>
                {g.items.map(code => <option key={code} value={code}>
                    {t.scen[code]}
                  </option>)}
              </optgroup>)}
          </select>
        </div>
      </div>

      <button onClick={generate} style={{
    background: '#00286D',
    color: '#fff',
    border: 'none',
    borderRadius: '8px',
    padding: '9px 18px',
    fontSize: '14px',
    fontWeight: 600,
    cursor: 'pointer'
  }}>
        {t.button}
      </button>

      {card && <div style={{
    marginTop: '16px',
    padding: '14px 16px',
    borderRadius: '10px',
    border: `1px solid ${TONE_COLORS[tone]}`,
    background: 'rgba(128,128,128,0.06)'
  }}>
          <div style={{
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'space-between',
    gap: '10px',
    flexWrap: 'wrap'
  }}>
            <div style={{
    fontFamily: 'monospace',
    fontSize: '20px',
    letterSpacing: '1px'
  }}>
              {card}
            </div>
            <button onClick={copy} style={{
    background: 'transparent',
    border: '1px solid rgba(128,128,128,0.4)',
    borderRadius: '6px',
    padding: '5px 12px',
    fontSize: '13px',
    cursor: 'pointer',
    color: 'inherit'
  }}>
              {copied ? t.copied : t.copy}
            </button>
          </div>
          <div style={{
    marginTop: '8px',
    fontSize: '13px'
  }}>
            <span style={{
    fontWeight: 600
  }}>{brandName}</span>
            <span style={{
    opacity: 0.6
  }}> · </span>
            <span style={{
    color: TONE_COLORS[tone],
    fontWeight: 600
  }}>
              {t.scen[last4]}
            </span>
          </div>
          <div style={{
    marginTop: '6px',
    fontSize: '12px',
    opacity: 0.7
  }}>
            {t.validity}
          </div>
        </div>}
    </div>;
};

No sandbox, o desfecho de um pagamento com cartão é decidido pelos **últimos 4 dígitos** do número.
Qualquer cartão **Luhn-válido** fora dos cenários abaixo é **aprovado**.

<Warning>
  O número precisa ser **Luhn-válido**, senão o formulário de pagamento o
  rejeita **antes** de chegar ao gateway. Ex.: `4000 0000 0000 0000` é recusado
  pelo formulário; `4000 0000 0002 0000` é aceito. Use o gerador abaixo para
  nunca errar isso.
</Warning>

## Gerador de cartão de teste

Escolha a bandeira e o cenário — o número gerado é sempre Luhn-válido e termina no last-4 do cenário.

<CardGenerator />

<Info>
  **Validade:** qualquer data futura (ex.: `12/30`). **CVV:** qualquer (ex.:
  `123`) — é ignorado pelo sandbox. **Bandeira:** definida pelo 1º dígito (`4`
  Visa, `5` Mastercard, `6` Elo, `3` Hipercard).
</Info>

## Cenários por last-4

### Aprovado

| Last-4                                 | Resultado                                         |
| -------------------------------------- | ------------------------------------------------- |
| `0000` ou qualquer outro fora da lista | **Aprovado** (`paid`)                             |
| `0010`                                 | Fica **aguardando pagamento** (`waiting_payment`) |

### Recusado (permanente, não adianta repetir)

| Last-4 | Decline code            | Significado            |
| ------ | ----------------------- | ---------------------- |
| `1001` | `INSUFFICIENT_FUNDS`    | Saldo insuficiente     |
| `1002` | `CARD_DECLINED`         | Cartão recusado        |
| `1003` | `EXPIRED_CARD`          | Cartão expirado        |
| `1004` | `INVALID_CARD_NUMBER`   | Número inválido        |
| `1005` | `INVALID_CVV`           | CVV inválido           |
| `1006` | `FRAUD_SUSPECTED`       | Suspeita de fraude     |
| `1007` | `DO_NOT_HONOR`          | Não autorizado         |
| `1008` | `EXCEEDED_LIMIT`        | Limite excedido        |
| `1009` | `RESTRICTED_CARD`       | Cartão restrito        |
| `1010` | `LOST_STOLEN_CARD`      | Cartão perdido/roubado |
| `1011` | `NOT_PERMITTED`         | Operação não permitida |
| `1012` | `INVALID_AMOUNT`        | Valor inválido         |
| `1013` | `DUPLICATE_TRANSACTION` | Transação duplicada    |

### Falha temporária (retryável)

| Last-4 | Decline code         | Significado           |
| ------ | -------------------- | --------------------- |
| `2001` | `PROCESSING_ERROR`   | Erro de processamento |
| `2002` | `GATEWAY_TIMEOUT`    | Timeout do gateway    |
| `2003` | `NETWORK_ERROR`      | Erro de rede          |
| `2004` | `ISSUER_UNAVAILABLE` | Emissor indisponível  |
| `2005` | `NO_ROUTE`           | Sem rota de pagamento |

### Especiais

| Last-4 | Comportamento                                           |
| ------ | ------------------------------------------------------- |
| `3001` | Timeout **lento** — falha (retryável) após \~5 segundos |
| `9999` | Erro inesperado (exception)                             |

## Cartões prontos (copiar e colar)

Todos Luhn-válidos:

| Cenário                           | Número                | CVV   | Validade |
| --------------------------------- | --------------------- | ----- | -------- |
| Aprovado                          | `4000 0000 0002 0000` | `123` | `12/30`  |
| Saldo insuficiente                | `4111 0009 1111 1001` | `123` | `12/30`  |
| Cartão recusado                   | `4111 0008 1111 1002` | `123` | `12/30`  |
| Suspeita de fraude                | `4111 0004 1111 1006` | `123` | `12/30`  |
| Erro de processamento (retryável) | `4111 0007 1111 2001` | `123` | `12/30`  |
| Timeout 5s                        | `4111 0005 1111 3001` | `123` | `12/30`  |

Para os demais cenários, use o **gerador** acima.

<Note>
  Esses cenários valem para o sandbox. Em produção, a
  aprovação/recusa depende do emissor real do cartão.
</Note>
