Fluxo de Pagamento de Boleto
Pagamento de boletos de terceiros (contas de consumo, tributos, boletos bancários) com débito na conta ativa. Ciclo: consultar a linha digitável → pagar agora ou agendar → comprovante / cancelar.
- Backend:
fastgivr-api—App\Http\Controllers\Payments\BoletoPaymentController,App\Services\Payments\BoletoPaymentService,App\Services\PinValidator - Rotas:
routes/payments.php(prefixo/payments, namepayments.), carregado porbootstrap/app.php - Middleware:
auth:api,sanctum·SetActiveAccountMiddleware·InjectAccountIntoRequest - Persistência:
boleto_withdraws+transactions(ledger) - Adaptador bancário:
Account::getBank()→App\Models\Banks\Teste(sandbox, mock determinístico).getBoletoDetails/payBoleto/pagarBoleto/consultarComprovante/cancelarAgendamento/consultarBoletosDDA— todos mockados.
Substitui os antigos endpoints /quickpay/boleto/{payments,payment-barcode,info-barcode}.
Envelope padrão { code, success, data|<chave>, message? }. Erros de regra de negócio: HTTP 422 com message. Não encontrado: 404.
1. Mapa de endpoints
| Método | Rota | Ação | Aprovação |
|---|---|---|---|
POST | /payments/boletos/inspect | consulta a linha digitável → registra PENDENTE | — |
GET | /payments/boletos | histórico (?status= ?search= ?per_page=) | — |
GET | /payments/boletos/dda | boletos DDA disponíveis para a conta | — |
GET | /payments/boletos/{id} | detalhe | — |
POST | /payments/boletos/{id}/pay | paga agora | PIN |
POST | /payments/boletos/{id}/schedule | agenda para uma data futura | PIN |
POST | /payments/boletos/{id}/cancel | cancela um pagamento pendente/agendado | — |
GET | /payments/boletos/{id}/receipt | comprovante junto ao banco | — |
POST | /payments/boletos/{id}/request-approval | "autorizar" — envia para aprovação de outro usuário | PIN |
GET | /payments/boletos/pending-approval | fila de pagamentos aguardando aprovação | — |
PUT | /payments/boletos/{id}/approve | "efetivar" — aprova e paga agora | PIN |
DELETE | /payments/boletos/{id}/reject | rejeita um pagamento pendente de aprovação | PIN |
PUT | /payments/boletos/approve-batch | aprova vários pendentes de uma vez (list_id) | PIN |
DELETE | /payments/boletos/reject-batch | rejeita vários pendentes de uma vez (list_id) | PIN |
GET | /payments/boletos/recipients | favorecidos frequentes (cedente/beneficiário) | — |
GET | /payments/boletos/recipients/{id} | detalhe de um favorecido | — |
DELETE | /payments/boletos/recipients/{id} | remove um favorecido | — |
2. Visão geral
Máquina de estados (App\Enums\StatusBoletoPayment)
| valor | significado |
|---|---|
pending | consultado, aguardando ação |
processing | agendado |
completed | pago / liquidado |
canceled | cancelado |
3. POST /payments/boletos/inspect
InspectBoletoRequest — prepareForValidation remove tudo que não for dígito; regra barcode: required · string · digits_between:44,48.
BoletoPaymentService::inspect:
- Bloqueia se já houver
BoletoWithdrawCOMPLETEDpara o mesmo(account, barcode)→ 422. Account::getBank()->getBoletoDetails($barcode)→App\DTO\BoletoReturnDTO(guardado pormethod_exists; banco sem o método →LogicException→ 422).updateOrCreatedoBoletoWithdrawpor(account_id, barcode):status = pending,value = dto.valorAtual,assignor = dto.nomeBeneficiario,due_date(a partir do timestampdto.dataVencimento),json = dto.toArray().
Resposta 200 — BoletoWithdrawResource:
{
"id": 42,
"status": "pending",
"value": 249.90,
"barcode": "3419...",
"assignor": "Telefônica Brasil S.A.",
"due_date": "2026-09-10T00:00:00.000000Z",
"boleto": { /* BoletoReturnDTO.toArray(): valor, dataVencimento, nomeBeneficiario, nomeBanco, ... */ },
"metadata": null
}4. POST /payments/boletos/{id}/pay
PayBoletoRequest: pin required·size:6, description nullable·max:255. {id} restrito a [0-9]+.
- O PIN é validado antes de abrir a transação de banco de dados (
PinValidator->validate— ver Fluxo de Pix §4 para os códigos403/409/422/429e o bloqueio por tentativas). BoletoPaymentService::payroda dentro deDB::transaction:lockForUpdate()noBoletoWithdraw;assertPayable— bloqueia seprocessing/completed(422 "já foi pago ou está em processamento") oucanceled;Account::getBank()->payBoleto($barcode, $dados + description)(guardado);status = completed,paid_at = now(),txid(primeiro detxid/idPagamento/idPagamentoTributosBarra/idTransacao/autenticacao),metadata = dados + resposta do banco;- lançamento no ledger —
recordTransaction:
| campo | valor |
|---|---|
type_id | slug boleto-payment (fallback pagamento-boleto) |
value | -BoletoWithdraw.value |
balance | account.balance() - value |
description | BoletoWithdraw.description ou "Pagamento boleto {barcode}" |
Após o lançamento, pay() dispara BoletoPaidNotification in-app aos usuários da conta (via AccountNotificationService, preferência bill_payment; uma falha de notificação não desfaz o pagamento).
Resposta 201 — BoletoWithdrawResource (agora status: "completed", com paid_at e txid).
O comprovante fica em GET /payments/boletos/{id}/receipt (dados do banco) e, para o extrato, em GET /consolidation/transactions/{id}/pdf.
5. POST /payments/boletos/{id}/schedule
ScheduleBoletoRequest: pin required·size:6, scheduled_at required·date·after:today, description nullable·max:255.
BoletoPaymentService::schedule (dentro de DB::transaction):
- exige
status = pending(senão 422 "Só é possível agendar um boleto pendente."); Account::getBank()->pagarBoleto($barcode, $dados + ['dataPagamento' => 'Y-m-d']);status = processing,scheduled_at,txiddo retorno.- Não debita o ledger — o débito ocorre na liquidação (webhook/job do PSP).
6. POST /payments/boletos/{id}/cancel
BoletoPaymentService::cancel — sem PIN:
- só permite
pendingouprocessing(senão 422 "Este pagamento não pode ser cancelado."); - se
processinge houver id de agendamento (txidoumetadata.idPagamento*), chamaAccount::getBank()->cancelarAgendamento($id); status = canceled.
7. Aprovação em duas etapas (maker-checker)
A collection do parceiro bancário real ("API de Contas — Integração", pasta "Pagamentos") modela um fluxo de dupla checagem para pagamento de boleto: "autorizar" (prepara, sem executar) → fila de pendentes → "efetivar"/aprovar (ou rejeitar), inclusive em lote. BoletoPaymentService (métodos requestApproval/pendingApproval/approvePending/rejectPending/ approvePendingBatch/rejectPendingBatch) e o novo status StatusBoletoPayment::AWAITING_APPROVAL.
POST /payments/boletos/{id}/request-approval—PinOnlyBoletoRequest(pinrequired·size:6).status = pending → awaiting_approval; notifica in-app (bill_payment) viaBoletoApprovalRequestedNotification.GET /payments/boletos/pending-approval— lista paginada (status = awaiting_approval, mais recentes primeiro).PUT /payments/boletos/{id}/approve— mesmoPinOnlyBoletoRequest(PIN de quem aprova, pode ser outro usuário da conta). Reaproveitapay()por baixo: efetiva o pagamento no banco de verdade, mesmo ledger e notificação de "boleto pago" do fluxo de pagamento direto.DELETE /payments/boletos/{id}/reject—awaiting_approval → canceled, sem debitar.PUT /payments/boletos/approve-batch/DELETE /payments/boletos/reject-batch—BatchPendingBoletoRequest(list_idrequired·array,pin). Processa item a item, best-effort: um id que já não está mais pendente não derruba o lote — devolve{ approved: [...], failed: { id: motivo } }(ourejected/failed).
⚠️ Sem checagem de alçada por papel. Qualquer usuário com acesso à conta e o PIN correto pode tanto "autorizar" quanto "aprovar" — não há hoje nenhuma rota da API que imponha permissão/papel (spatie/laravel-permission está seedado, mas nenhum middleware/Gate o consulta). Ainda não wired na UI (fastgivr-internet-bank) — só backend.
8. Favorecidos de boletos
GET /payments/boletos/recipients[/{id}] · DELETE /payments/boletos/recipients/{id} — cedentes/beneficiários frequentes, tabela boleto_recipients (account_id, document nullable, name, favorite, count, last_used_at). Sem cadastro manual (POST): a identidade só existe depois de um boleto real ser pago — BoletoPaymentService::pay() grava/atualiza automaticamente a partir de documentoBeneficiario/nomeBeneficiario do BoletoReturnDTO; sem documento do cedente, casa por nome para não colidir favorecidos diferentes num mesmo registro.
9. GET /payments/boletos/{id}/receipt
BoletoPaymentService::receipt — resolve o id do pagamento (txid ou metadata.idPagamento*); se não houver → 422 "ainda não possui comprovante". Caso contrário Account::getBank()->consultarComprovante($id) e devolve o array cru do banco.
10. GET /payments/boletos/dda
BoletoPaymentService::dda → Account::getBank()->consultarBoletosDDA($filtros) (query string, exceto page/per_page). Retorna o array do banco (vazio no Teste).
11. GET /payments/boletos — histórico
?status= (um dos valores de StatusBoletoPayment), ?search= (casa assignor, description e barcode só-dígitos), ?per_page= (default 15). Ordenado por created_at desc; devolve BoletoWithdrawResource::collection paginado com o envelope { data, meta }.
12. Modelo boleto_withdraws (App\Models\BoletoWithdraw)
Migrações: 0001_01_01_000007_* (base) + 2026_09_01_000006_align_boleto_withdraws_for_payments_table.php.
| Coluna | Tipo | Nota |
|---|---|---|
unique_hash | string? | sha256(account_id | barcode | value) (trait HasUniqueHash) |
status | string | cast StatusBoletoPayment (pending/processing/awaiting_approval/completed/canceled) |
value | decimal(12,2) | valor a debitar |
barcode | string? | linha digitável / código de barras (só dígitos) |
assignor | string? | beneficiário (exibição em lista) |
description | string? | descrição informada no pagamento |
txid | string? | id do pagamento no PSP (ou autenticação) |
json | json | snapshot do BoletoReturnDTO da consulta |
metadata | json | dados de trabalho + resposta do banco |
due_date | timestamp? | vencimento |
scheduled_at | timestamp? | data do agendamento |
paid_at | timestamp? | liquidação |
deleted_at | timestamp? | soft delete |
Relação account (belongsTo).
13. Adaptador bancário (Account::getBank() → App\Models\Banks\Teste)
Sandbox: um único adaptador, com mocks determinísticos. guardBank() lança um erro claro caso um provedor real futuro não implemente algum método.
| Método | Uso | BankInterface? |
|---|---|---|
getBoletoDetails(barcode): BoletoReturnDTO | inspect | não |
payBoleto(barcode, data): array | pay | não |
pagarBoleto(barcode, data, idempotencyKey?) | schedule | sim |
consultarComprovante(idPagamento) | receipt | sim |
cancelarAgendamento(idPagamento) | cancel (agendado) | sim |
consultarBoletosDDA(filters) | dda | sim |
BoletoReturnDTO (App\DTO\BoletoReturnDTO) normaliza a consulta: valor/valorAtual, dataVencimento (timestamp), nomeBeneficiario, nomeBanco, linhaDigitavel, nossoNumero, pagavel, vencido, json.
14. Frontend
fastgivr-internet-bank/src/services/payments.ts — payments.boletos.{ list, get, inspect, pay, schedule, cancel, receipt, dda }. Tela src/pages/payments/BoletoPaymentPage.tsx (rota /pagamentos/boleto sob AppLayout):
- Consultar —
boletoPaymentsApi.inspect({ barcode }); guarda oidretornado e exibe beneficiário / vencimento / valor. - Confirmar — diálogo com resumo + input do PIN (6 dígitos); botão só habilita com
pin.length === 6. - Pagar —
boletoPaymentsApi.pay(id, { pin, description? }); em sucesso, abre o comprovante local e limpa o formulário.
Erros do backend (422 regra de negócio, 403/409/429 PIN) sobem como rejeição do Axios e são exibidos via toast.