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-api—App\Http\Controllers\Access\Auth\AuthRegisterController - Prefixo:
/access/register· Auth: pública (pré-login; credencial de retomada =register_id+document) - Persistência: tabela
registers(Register+ enumRegisterStatus), 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" — traitIssuesVerificationCodes, código com hash empassword_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.
| Auth | pública |
| Headers | Content-Type: application/json |
| Status | resultante: EMAIL_PENDING |
Request
{
"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
| Campo | Regras |
|---|---|
name | required·string·max:255 |
email | required·email·max:255 · único em users e registers |
cpf | required·string · válido (cpf) · único em accounts e registers |
password | required·string·min:8·confirmed (regra de força configurável) |
password_confirmation | required · igual a password |
Respostas
| Status | Corpo (data) | Quando |
|---|---|---|
201 | { register_id, document, status: "EMAIL_PENDING", email_hint: "jo****@email.com", code_expires_in: 600 } | criado |
422 | erros 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
{
"register_id": 42,
"document": "12345678909",
"code": "482913", // 6 dígitos
}Validações — register_id: required; document: required·string·min:11·max:14; code: required·string·size:6.
Respostas
| Status | Corpo (data) | Quando |
|---|---|---|
200 | { status: "EMAIL_VERIFIED", next_step: "account-type" } | código correto e dentro dos 10 min |
422 | null + message: "Código de validação incorreto." | código errado |
422 | null + message: "O código de validação expirou. Solicite um novo código." | fora do TTL |
404 | null | register_id/document não conferem |
Efeitos colaterais — data.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
| Status | Corpo | Quando |
|---|---|---|
200 | { status: "EMAIL_PENDING", code_expires_in: 600 } | novo código enviado |
400 | message: "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" }.
Respostas — 200 · { 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
{
"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ções — is_pep: required·boolean; pep_details: required_if:is_pep,true.
Respostas — 200 · { 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)
{
"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",
},
}Respostas — 200 · { 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
{
"register_id": 42,
"document": "12345678000199", // CNPJ, 14 dígitos
"cnpj": "12345678000199",
// campos manuais opcionais que sobrescrevem o autofill
"trade_name": "…",
}Validações — cnpj: 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.
Respostas — 200 · { status: "PROFILE_IN_PROGRESS", next_step: "legal-representative", company: { …autofill… } }.
2.8 PATCH /access/register/legal-representative — Pessoa Jurídica
Dados do responsável legal da empresa.
Request — { register_id, document, name, cpf, rg, birth_date, email, phone, role }.
Respostas — 200 · { 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.
| Auth | pública (register_id + document) |
| Headers | Content-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
| Status | Corpo (data) | Quando |
|---|---|---|
200 | { status: "DOCUMENTS_SUBMITTED", uploaded: [ "identity_front", … ] } | arquivos aceitos |
422 | erros 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 / body — document (obrigatório) e, se disponível, register_id.
Resposta 200 (data)
{
"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
| Status | Corpo (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.
| Coluna | Tipo | Nota |
|---|---|---|
id | bigint | PK |
document | string | único — CPF (11) ou CNPJ (14), só dígitos |
email | string | único |
name | string | titular PF ou razão social PJ |
user_id | bigint? | User já criado no start (credencial, verificação de e-mail e "esqueci a senha" reaproveitam /auth/*) |
account_type | integer | 1 = PF · 2 = PJ (default 1) |
account_status | integer | cast para RegisterStatus |
current_step | string? | passo atual (RegisterStep), indexado |
completed_steps | json? | lista de passos concluídos |
is_pep, pep_details | boolean?, json? | declaração de PEP |
rg, mother_name, father_name, birth_date, address | — | dados PF (address em json) |
cnpj, company_data, legal_representative | — | dados PJ (company_data = autofill BrasilAPI) |
documents, submitted_at, review | json?, timestamp?, json? | documentos enviados e resultado da análise |
reviewed_by | bigint? | users.id do agente do backoffice que aprovou/rejeitou |
partner_client_id, partner_analysis_status, partner_analysis_payload | — | integração com o parceiro de KYC |
registration_ip, registration_device_id, registration_device, registration_user_agent | — | origem do dispositivo que iniciou o start |
deleted_at | timestamp? | 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(guardapi,AuthController@register) é outro fluxo — cadastro simples deusersque já emite JWT, sem etapas nem aprovação. O onboarding descrito aqui vive em/access/registere promoveregisters→accountssó apósAPPROVED.
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:
- o backend responde com um ponteiro para o onboarding (
{ registration_pending: true, register_id, document, status }); - o frontend chama
GET /access/register/progresse navega paracurrent_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.