Skip to content

Fluxo de Cadastro — Onboarding Multi-etapas

Abertura de conta como um processo persistente e retomável, não um único formulário. Cada etapa tem endpoint próprio e grava o progresso; se o usuário sair no meio, ao voltar (login ou retomada pelo documento) ele cai direto na última etapa pendente, sem perder dados nem documentos já enviados.

  • Backend: fastgivr-apiApp\Http\Controllers\Access\Auth\AuthRegisterController
  • Prefixo: /access/register · Auth: pública (pré-login; credencial de retomada = register_id + document)
  • Persistência: tabela registers (Register + enum RegisterStatus), em colunas dedicadas por etapa (current_step, completed_steps, is_pep/pep_details, rg/mother_name/birth_date/address, company_data/legal_representative, documents, reviewed_by, partner_client_id) — ver §3
  • E-mail: código de verificação de 6 dígitos (App\Mail\EmailVerificationMail, mesmo mecanismo de "esqueci a senha" — trait IssuesVerificationCodes, código com hash em password_reset_tokens, expira em 1 h)
  • KYC do parceiro: ao submit, o cadastro é enviado ao parceiro de verificação (sandbox local por padrão) — detalhe em Banco de Dados — Histórico de Correções §1

1. Visão geral do fluxo

Ciclo de vida do cadastro (RegisterStatus)

Retomada. A identidade do cadastro em andamento é (register_id, document). O cliente guarda os dois após o POST /start (ex.: localStorage) e os reenvia em toda etapa. No login, se existir um Register não concluído para o documento, a aplicação chama GET /access/register/progress e redireciona para next_step.


2. Endpoints (Etapa 1)

Envelope padrão: { code, success, data, message? }. Erros de validação: HTTP 422 no formato padrão do Laravel — { message, errors: { campo: [erros] } } (as etapas usam FormRequest dedicados em App\Http\Requests\Access\Register\*, não validação manual no controller — por isso o formato difere do envelope { success, data } do resto da API; normalizeApiError() no front-end já lê os dois formatos). Retomada de etapa: todo endpoint (exceto /start) exige register_id + document e responde 404 se não houver Register correspondente.

2.1 POST /access/register/start

Cria o cadastro com os dados básicos e dispara o código de verificação.

Authpública
HeadersContent-Type: application/json
Statusresultante: EMAIL_PENDING

Request

jsonc
{
  "name": "João da Silva",
  "email": "joao@email.com",
  "cpf": "12345678909",              // 11 dígitos; aceita com ou sem máscara
  "password": "••••••••",
  "password_confirmation": "••••••••",
}

Validações

CampoRegras
namerequired·string·max:255
emailrequired·email·max:255 · único em users e registers
cpfrequired·string · válido (cpf) · único em accounts e registers
passwordrequired·string·min:8·confirmed (regra de força configurável)
password_confirmationrequired · igual a password

Respostas

StatusCorpo (data)Quando
201{ register_id, document, status: "EMAIL_PENDING", email_hint: "jo****@email.com", code_expires_in: 600 }criado
422erros de validação (e-mail/CPF em uso, senha fraca, confirmação diferente)payload inválido

Efeitos colaterais — cria o User (credencial de login) e o Register vinculado (account_type default PF, current_step = verify-email); registra o dispositivo de origem como trusted (ver §3); envia o código de 6 dígitos por App\Mail\EmailVerificationMail.

2.2 POST /access/register/verify-email

Confirma o e-mail com o código de 6 dígitos.

Request

jsonc
{
  "register_id": 42,
  "document": "12345678909",
  "code": "482913",                 // 6 dígitos
}

Validaçõesregister_id: required; document: required·string·min:11·max:14; code: required·string·size:6.

Respostas

StatusCorpo (data)Quando
200{ status: "EMAIL_VERIFIED", next_step: "account-type" }código correto e dentro dos 10 min
422null + message: "Código de validação incorreto."código errado
422null + message: "O código de validação expirou. Solicite um novo código."fora do TTL
404nullregister_id/document não conferem

Efeitos colateraisdata.email_validation_valid = true, limpa email_validation_code e email_sent_at; account_status → EMAIL_VERIFIED.

2.3 POST /access/register/resend-email-code

Reenvia o código. Throttle de 2 min entre envios.

Request{ register_id, document }.

Respostas

StatusCorpoQuando
200{ status: "EMAIL_PENDING", code_expires_in: 600 }novo código enviado
400message: "Você só pode enviar um novo e-mail de validação após 2 minutos."dentro da janela

2.4 PATCH /access/register/account-type

Define o tipo de pessoa e adapta o restante do fluxo.

Request{ register_id, document, account_type: "PF" | "PJ" }.

Respostas200 · { status: "PROFILE_IN_PROGRESS", next_step: "pep" }. Efeito: atualiza coluna account_type (1/2) e data.progress.account_type.

2.5 PATCH /access/register/pep

Declaração de Pessoa Exposta Politicamente.

Request

jsonc
{
  "register_id": 42,
  "document": "12345678909",
  "is_pep": true,
  "pep_details": {                   // obrigatório quando is_pep = true
    "role": "Cargo/função",
    "entity": "Órgão/entidade",
    "since": "2021-01",
  },
}

Validaçõesis_pep: required·boolean; pep_details: required_if:is_pep,true.

Respostas200 · { status: "PROFILE_IN_PROGRESS", next_step: "personal-data" | "company-data" }.

2.6 PATCH /access/register/personal-data — Pessoa Física

Dados cadastrais restantes da PF.

Request (exemplos)

jsonc
{
  "register_id": 42,
  "document": "12345678909",
  "rg": "12.345.678-9",
  "mother_name": "Maria da Silva",
  "father_name": "José da Silva",
  "birth_date": "1990-05-12",
  "address": {
    "zip": "01001000", "street": "Praça da Sé", "number": "100",
    "complement": "", "district": "Sé", "city": "São Paulo", "state": "SP",
  },
}

Respostas200 · { status: "DOCUMENTS_PENDING", next_step: "documents" } quando o perfil PF fica completo; senão { status: "PROFILE_IN_PROGRESS", missing: [...] }.

2.7 PATCH /access/register/company-data — Pessoa Jurídica

Recebe o CNPJ e, quando possível, faz autofill pela BrasilAPI.

Request

jsonc
{
  "register_id": 42,
  "document": "12345678000199",     // CNPJ, 14 dígitos
  "cnpj": "12345678000199",
  // campos manuais opcionais que sobrescrevem o autofill
  "trade_name": "…",
}

Validaçõescnpj: required·string · válido (cnpj).

Autofill (BrasilAPI GET /api/cnpj/v1/{cnpj}) — grava em data.company: razao_social, nome_fantasia, situacao_cadastral, endereco, cnae_fiscal + cnaes_secundarios, natureza_juridica, entre outros. Falha ou indisponibilidade da BrasilAPI não bloqueia o fluxo — o usuário preenche manualmente.

Respostas200 · { status: "PROFILE_IN_PROGRESS", next_step: "legal-representative", company: { …autofill… } }.

Dados do responsável legal da empresa.

Request{ register_id, document, name, cpf, rg, birth_date, email, phone, role }.

Respostas200 · { status: "DOCUMENTS_PENDING", next_step: "documents" } quando o perfil PJ fica completo.

2.9 POST /access/register/documents

Upload dos documentos de validação (multipart). Os documentos exigidos variam por account_type.

Authpública (register_id + document)
HeadersContent-Type: multipart/form-data

Campos (exemplos) — PF: identity_front, identity_back, selfie. PJ: social_contract, rep_identity_front, rep_identity_back, rep_selfie. Cada arquivo: image|pdf · mimes:jpeg,png,jpg,pdf · max:2048 (KB).

Respostas

StatusCorpo (data)Quando
200{ status: "DOCUMENTS_SUBMITTED", uploaded: [ "identity_front", … ] }arquivos aceitos
422erros por campo (tipo/tamanho)arquivo inválido

Efeitos colaterais — arquivos em storage/app/public/registers/{id}/…; caminhos em data.documents; account_status → DOCUMENTS_SUBMITTED.

2.10 GET /access/register/progress

Estado consolidado do cadastro — usado na retomada.

Query / bodydocument (obrigatório) e, se disponível, register_id.

Resposta 200 (data)

jsonc
{
  "register_id": 42,
  "document": "12345678909",
  "account_type": "PF",
  "status": "DOCUMENTS_PENDING",
  "current_step": "documents",
  "completed_steps": ["start", "verify-email", "account-type", "pep", "personal-data"],
  "pending": ["documents", "submit"],
  "documents": { "identity_front": true, "identity_back": false, "selfie": false },
  "review": null,                   // { result, reason, reviewed_at } após análise
}

404 quando não há cadastro para o documento.

2.11 POST /access/register/submit

Finaliza o cadastro.

Request{ register_id, document }.

Regras — valida que todas as etapas obrigatórias do account_type estão concluídas e que os documentos obrigatórios foram recebidos.

Respostas

StatusCorpo (data)Quando
200{ status: "ACTIVE", auto_approved: true, account: { id, agency, account, bank_gateway } }sandbox — aprovado automaticamente
200{ status: "UNDER_REVIEW" }produção — enviado para análise manual
422{ missing: ["documents.selfie", …] }falta etapa ou documento obrigatório

Sandbox (config('banks.sandbox') === true, default): App\Services\Access\AccountProvisioningService cria na hora a Account (gateway config('banks.default_gateway') = Teste, status = ACTIVE, key_pix = documento, taxas zeradas, endereço em metadata.address), o vínculo UserAccount e o papel "Administrador da Conta"; marca registers.account_status → ACTIVE. O usuário já pode fazer login e usar o banco. Idempotente — chamar de novo devolve a mesma conta.

Produção (BANK_SANDBOX=false): account_status → UNDER_REVIEW + e-mail ao titular. A aprovação é feita depois por php artisan register:approve {document} (ou --id=) ou pela ação de backoffice — que chamam o mesmo serviço.


3. Modelo registers

Migração base: database/migrations/0001_01_01_000008_create_logging_tables.php (+ 2026_09_05_000003_update_registers_table_review_and_partner_fields.php para os campos de aprovação/parceiro). Progresso e dados de cada etapa vivem em colunas dedicadas — não há mais um campo JSON genérico guardando o estado do fluxo; data (json) segue no schema só por compatibilidade com linhas antigas, e não é mais escrito pelo fluxo multi-etapas atual.

ColunaTipoNota
idbigintPK
documentstringúnico — CPF (11) ou CNPJ (14), só dígitos
emailstringúnico
namestringtitular PF ou razão social PJ
user_idbigint?User já criado no start (credencial, verificação de e-mail e "esqueci a senha" reaproveitam /auth/*)
account_typeinteger1 = PF · 2 = PJ (default 1)
account_statusintegercast para RegisterStatus
current_stepstring?passo atual (RegisterStep), indexado
completed_stepsjson?lista de passos concluídos
is_pep, pep_detailsboolean?, json?declaração de PEP
rg, mother_name, father_name, birth_date, addressdados PF (address em json)
cnpj, company_data, legal_representativedados PJ (company_data = autofill BrasilAPI)
documents, submitted_at, reviewjson?, timestamp?, json?documentos enviados e resultado da análise
reviewed_bybigint?users.id do agente do backoffice que aprovou/rejeitou
partner_client_id, partner_analysis_status, partner_analysis_payloadintegração com o parceiro de KYC
registration_ip, registration_device_id, registration_device, registration_user_agentorigem do dispositivo que iniciou o start
deleted_attimestamp?soft delete

Dispositivo confiável automático. O start() também registra o dispositivo do cadastro como trusted em trusted_devices — quem termina o onboarding já faz o 1º POST /auth/login sem passar pela verificação de dispositivo descrita em Fluxo de Login.

POST /auth/register (guard api, AuthController@register) é outro fluxo — cadastro simples de users que já emite JWT, sem etapas nem aprovação. O onboarding descrito aqui vive em /access/register e promove registersaccounts só após APPROVED.


4. Integração com o login

No POST /auth/login (ver Fluxo de Login), se as credenciais baterem mas ainda não existir conta ativa e houver um Register do documento com status anterior a APPROVED:

  1. o backend responde com um ponteiro para o onboarding ({ registration_pending: true, register_id, document, status });
  2. o frontend chama GET /access/register/progress e navega para current_step.

5. Frontend

Implementado em fastgivr-internet-bank: RegisterPage (/cadastro — etapa start) e OnboardingPage (/cadastro/continuar — as etapas seguintes, dirigida por current_step), consumindo src/services/access.ts. register_id + document ficam em localStorage após o start; ao reabrir, GET /access/register/progress decide em qual etapa renderizar a tela — passos em completed_steps ficam disponíveis para edição até o submit.

FastGivr API Documentation