> ## 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.

# Documentos de teste (recebedores)

> CPFs e CNPJs de teste do sandbox: quais aprovam, quais recusam o recebedor, e um gerador de documento válido.

export const DocumentGenerator = ({lang = 'pt'}) => {
  const KINDS = ['cpf', 'cnpj'];
  const MAGIC_SUFFIXES = ['0001', '0002', '0003', '0004', '9999'];
  const TONE_COLORS = {
    success: '#16a34a',
    error: '#dc2626',
    warning: '#d97706',
    info: '#2563eb'
  };
  const SCEN_TONE = {
    ok: 'success',
    '0001': 'error',
    '0002': 'error',
    '0003': 'error',
    '0004': 'warning',
    9999: 'error'
  };
  const GROUPS = [{
    key: 'approved',
    items: ['ok']
  }, {
    key: 'refused',
    items: ['0001', '0002', '0003']
  }, {
    key: 'pending',
    items: ['0004']
  }, {
    key: 'special',
    items: ['9999']
  }];
  const I18N = {
    pt: {
      title: 'Gerador de documento de teste',
      desc: 'Escolha o tipo e o cenário. O CPF/CNPJ gerado tem dígitos verificadores válidos (passa em qualquer validação) e termina no sufixo que dispara o cenário no sandbox.',
      kind: 'Tipo de documento',
      scenario: 'Cenário',
      button: 'Gerar documento',
      copy: 'Copiar',
      copied: 'Copiado!',
      note: 'Copiado sem máscara (só dígitos) — pronto para o campo `document` da API.',
      groups: {
        approved: 'Aprovado',
        refused: 'Recusado',
        pending: 'Pendente',
        special: 'Especial'
      },
      scen: {
        ok: 'Aprovado (qualquer documento sem sufixo mágico)',
        '0001': 'Documento recusado — document_number_invalid',
        '0002': 'Dados bancários recusados — bank_account_invalid',
        '0003': 'Verificação (KYC) reprovada — rejected_by_acquirer',
        '0004': 'Verificação (KYC) fica pendente — under_review',
        9999: 'Erro inesperado do provider (exception)'
      }
    },
    en: {
      title: 'Test document generator',
      desc: 'Pick the type and scenario. The generated CPF/CNPJ has valid check digits (passes any validation) and ends in the suffix that triggers the scenario in the sandbox.',
      kind: 'Document type',
      scenario: 'Scenario',
      button: 'Generate document',
      copy: 'Copy',
      copied: 'Copied!',
      note: 'Copied without mask (digits only) — ready for the API `document` field.',
      groups: {
        approved: 'Approved',
        refused: 'Refused',
        pending: 'Pending',
        special: 'Special'
      },
      scen: {
        ok: 'Approved (any document without a magic suffix)',
        '0001': 'Document refused — document_number_invalid',
        '0002': 'Bank account refused — bank_account_invalid',
        '0003': 'Verification (KYC) rejected — rejected_by_acquirer',
        '0004': 'Verification (KYC) stays pending — under_review',
        9999: 'Unexpected provider error (exception)'
      }
    },
    es: {
      title: 'Generador de documento de prueba',
      desc: 'Elige el tipo y el escenario. El CPF/CNPJ generado tiene dígitos verificadores válidos (pasa cualquier validación) y termina en el sufijo que activa el escenario en el sandbox.',
      kind: 'Tipo de documento',
      scenario: 'Escenario',
      button: 'Generar documento',
      copy: 'Copiar',
      copied: '¡Copiado!',
      note: 'Copiado sin máscara (solo dígitos) — listo para el campo `document` de la API.',
      groups: {
        approved: 'Aprobado',
        refused: 'Rechazado',
        pending: 'Pendiente',
        special: 'Especial'
      },
      scen: {
        ok: 'Aprobado (cualquier documento sin sufijo mágico)',
        '0001': 'Documento rechazado — document_number_invalid',
        '0002': 'Datos bancarios rechazados — bank_account_invalid',
        '0003': 'Verificación (KYC) rechazada — rejected_by_acquirer',
        '0004': 'Verificación (KYC) queda pendiente — under_review',
        9999: 'Error inesperado del proveedor (exception)'
      }
    }
  };
  const t = I18N[lang] || I18N.pt;
  const CPF_WEIGHTS_1 = [10, 9, 8, 7, 6, 5, 4, 3, 2];
  const CPF_WEIGHTS_2 = [11, 10, 9, 8, 7, 6, 5, 4, 3, 2];
  const CNPJ_WEIGHTS_1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
  const CNPJ_WEIGHTS_2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
  const checkDigit = (digits, weights) => {
    const sum = digits.reduce((acc, d, i) => acc + d * (weights[i] ?? 0), 0);
    const remainder = sum % 11;
    return remainder < 2 ? 0 : 11 - remainder;
  };
  const randomDigits = count => Array.from({
    length: count
  }, () => Math.floor(Math.random() * 10));
  const generateWithSuffix = (kind, suffix) => {
    const fixed = [Number(suffix[0]), Number(suffix[1])];
    const wantedDvs = suffix.slice(2);
    const randomCount = kind === 'cpf' ? 7 : 10;
    const [w1, w2] = kind === 'cpf' ? [CPF_WEIGHTS_1, CPF_WEIGHTS_2] : [CNPJ_WEIGHTS_1, CNPJ_WEIGHTS_2];
    for (let attempt = 0; attempt < 5000; attempt++) {
      const base = [...randomDigits(randomCount), ...fixed];
      const dv1 = checkDigit(base, w1);
      const dv2 = checkDigit([...base, dv1], w2);
      if (`${dv1}${dv2}` === wantedDvs) return [...base, dv1, dv2].join('');
    }
    return null;
  };
  const generateApproved = kind => {
    const randomCount = kind === 'cpf' ? 9 : 12;
    const [w1, w2] = kind === 'cpf' ? [CPF_WEIGHTS_1, CPF_WEIGHTS_2] : [CNPJ_WEIGHTS_1, CNPJ_WEIGHTS_2];
    for (let attempt = 0; attempt < 100; attempt++) {
      const base = randomDigits(randomCount);
      const dv1 = checkDigit(base, w1);
      const dv2 = checkDigit([...base, dv1], w2);
      const digits = [...base, dv1, dv2].join('');
      if (!MAGIC_SUFFIXES.includes(digits.slice(-4))) return digits;
    }
    return null;
  };
  const formatDocument = digits => {
    if (!digits) return '';
    if (digits.length === 11) {
      return `${digits.slice(0, 3)}.${digits.slice(3, 6)}.${digits.slice(6, 9)}-${digits.slice(9)}`;
    }
    return `${digits.slice(0, 2)}.${digits.slice(2, 5)}.${digits.slice(5, 8)}/${digits.slice(8, 12)}-${digits.slice(12)}`;
  };
  const [kind, setKind] = React.useState('cpf');
  const [scenario, setScenario] = React.useState('ok');
  const [doc, setDoc] = React.useState(null);
  const [copied, setCopied] = React.useState(false);
  const generate = () => {
    const digits = scenario === 'ok' ? generateApproved(kind) : generateWithSuffix(kind, scenario);
    setDoc(digits);
    setCopied(false);
  };
  const copy = () => {
    if (!doc) return;
    navigator.clipboard?.writeText(doc);
    setCopied(true);
    setTimeout(() => setCopied(false), 1500);
  };
  const tone = SCEN_TONE[scenario] || 'success';
  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.kind}
          </label>
          <select style={fieldStyle} value={kind} onChange={e => setKind(e.target.value)}>
            {KINDS.map(k => <option key={k} value={k}>
                {k.toUpperCase()}
              </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={scenario} onChange={e => setScenario(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>

      {doc && <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'
  }}>
              {formatDocument(doc)}
            </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
  }}>{kind.toUpperCase()}</span>
            <span style={{
    opacity: 0.6
  }}> · </span>
            <span style={{
    color: TONE_COLORS[tone],
    fontWeight: 600
  }}>
              {t.scen[scenario]}
            </span>
          </div>
          <div style={{
    marginTop: '6px',
    fontSize: '12px',
    opacity: 0.7
  }}>
            {t.note}
          </div>
        </div>}
    </div>;
};

No sandbox, o desfecho do onboarding de um **recebedor** é decidido pelos **últimos 4 dígitos**
do documento (CPF/CNPJ) enviado no [`POST /recipients`](/pt-BR/recipients/create).
Qualquer documento **com dígitos verificadores válidos** fora dos cenários abaixo é **aprovado**.

É o mesmo princípio dos [cartões de teste](/pt-BR/sandbox/cartoes): você escolhe o desfecho —
inclusive a **recusa**, que no ambiente real depende de uma análise KYC de verdade.

<Warning>
  Os 2 últimos dígitos de um CPF/CNPJ são **dígitos verificadores calculados** — não dá para
  simplesmente inventar um número terminado em `0003`, porque a validação de documento rejeita
  antes de chegar ao sandbox. Use o gerador abaixo: ele só produz documentos válidos.
</Warning>

## Gerador de documento de teste

Escolha o tipo (CPF/CNPJ) e o cenário — o documento gerado sempre tem dígitos verificadores
válidos e termina no sufixo do cenário.

<DocumentGenerator />

<Info>
  A máscara não importa: `690.636.000-03` e `69063600003` disparam o mesmo cenário — o sandbox
  olha só os dígitos.
</Info>

## Cenários por sufixo

### Aprovado

| Sufixo                       | Resultado                                                  |
| ---------------------------- | ---------------------------------------------------------- |
| Qualquer outro fora da lista | **Aprovado** — o vínculo segue o fluxo normal até `active` |

### Recusado

| Sufixo | Resultado                                                                            | Pendência reportada       |
| ------ | ------------------------------------------------------------------------------------ | ------------------------- |
| `0001` | Documento recusado na entrada — a conta nem chega a ser criada no provedor           | `document_number_invalid` |
| `0002` | Dados bancários recusados na entrada — a conta nem chega a ser criada                | `bank_account_invalid`    |
| `0003` | Conta criada, mas a verificação (KYC) é **reprovada** — o vínculo vai para `refused` | `rejected_by_acquirer`    |

### Pendente

| Sufixo | Resultado                                                                                             | Pendência reportada      |
| ------ | ----------------------------------------------------------------------------------------------------- | ------------------------ |
| `0004` | Conta criada com verificação (KYC) **em análise** — não aprova nem reprova; o vínculo fica aguardando | `under_review` (warning) |

### Especial

| Sufixo | Comportamento                                                         |
| ------ | --------------------------------------------------------------------- |
| `9999` | Erro inesperado do provedor (exception) — igual ao `9999` dos cartões |

## O que observar depois

Depois de criar o recebedor com um documento de recusa, valide os dois lados da sua integração:

<Steps>
  <Step title="Consulte o recebedor">
    [`GET /recipients/:id`](/pt-BR/recipients/get) — confira o
    `status` e as `pendencies`, com o `code` da tabela acima e os textos `message`/`action`
    prontos para exibir.
  </Step>

  <Step title="Receba o webhook">
    Assine `recipient.refused` e `recipient.pendency_updated` — o payload traz o recebedor
    completo, incluindo as pendências. Veja o
    [ciclo de aprovação](/pt-BR/recipients#ciclo-de-aprova%C3%A7%C3%A3o-kyc).
  </Step>
</Steps>

## Documentos prontos (copiar e colar)

Todos com dígitos verificadores válidos:

| Cenário                            | CPF              | CNPJ                 |
| ---------------------------------- | ---------------- | -------------------- |
| Aprovado                           | `526.018.159-06` | `35.623.012/3836-50` |
| Documento recusado (`0001`)        | `261.090.200-01` | `83.944.726/6400-01` |
| Dados bancários recusados (`0002`) | `058.310.200-02` | `88.843.853/0700-02` |
| KYC reprovado (`0003`)             | `690.636.000-03` | `44.846.027/7200-03` |
| KYC pendente (`0004`)              | `006.717.700-04` | `97.580.166/5400-04` |
| Erro inesperado (`9999`)           | `125.522.299-99` | `42.968.717/8999-99` |

Para variar os números (ex.: testar vários recebedores recusados), use o **gerador** acima.

<Note>
  Esses cenários valem para o sandbox. Em produção, aprovação e recusa dependem da análise
  KYC real — e o motivo chega do mesmo jeito, pelas
  [`pendencies`](/pt-BR/recipients#por-que-meu-recebedor-foi-recusado).
</Note>
