API REST para gerenciamento seguro de carteiras em BRL, com depósitos, transferências, reversões administrativas, ledger imutável, idempotência e proteção contra concorrência.
| Tecnologia | Uso no projeto |
|---|---|
| PHP 8.4 | Runtime usado nas imagens da aplicação e do worker |
| Laravel 13 | Framework HTTP, validação, Eloquent, eventos e comandos de console |
| Laravel Sanctum | Autenticação por Bearer Token e abilities por operação |
| PostgreSQL 17 | Persistência transacional, constraints, locks e triggers do ledger |
| Redis 7.4 | Cache, sessões e fila |
| Nginx | Servidor HTTP e encaminhamento para PHP-FPM |
| Docker Compose | Orquestração de aplicação, Nginx, PostgreSQL, Redis e worker |
| Scribe | Documentação HTML, OpenAPI e collection Postman |
| PHPUnit | Testes unitários, Feature, integração e concorrência real |
| Laravel Pint | Padronização de código PHP |
- Cadastro com criação atômica de usuário e carteira.
- Login e logout com Laravel Sanctum.
- Tokens com abilities diferentes para usuários e administradores.
- Consulta de saldo e histórico paginado da própria carteira.
- Depósitos em BRL com registro contábil.
- Transferências atômicas entre carteiras.
- Bloqueio de saldo insuficiente e de transferência para si mesmo.
- Reversões operacionais exclusivas para administradores.
- Reversões capazes de produzir saldo negativo quando o valor original já foi gasto.
- Idempotência para depósito, transferência e reversão.
- Ledger imutável protegido também por triggers no PostgreSQL.
- Locks pessimistas e ordem determinística de aquisição para concorrência.
- Promoção e remoção operacional do papel de administrador.
- Reconciliação de saldos em modo somente leitura ou correção explícita.
- Eventos de domínio, logs estruturados e correlação por
X-Request-ID. - Health check de aplicação, PostgreSQL e Redis.
- OpenAPI e collection Postman versionados e reproduzíveis.
- Factories financeiras e cenário local de demonstração idempotente.
- Valores financeiros representados como inteiros, evitando erros de ponto flutuante.
- Regras críticas protegidas em mais de uma camada: aplicação, transação e banco.
- Ledger append-only, sem atualização ou exclusão de lançamentos.
- Respostas idempotentes preservadas para replay fiel da primeira operação.
- Eventos financeiros publicados somente após o commit da transação.
- Testes de concorrência com processos e conexões PostgreSQL independentes.
- Policies combinadas com abilities do token para operações administrativas.
- Artefatos de contrato gerados a partir de rotas, Requests, Resources e anotações.
- Seed demonstrativo construído pelas Actions reais, sem um caminho financeiro paralelo.
O projeto separa transporte HTTP, validação, dados de entrada e regras de aplicação:
Request HTTP
│
├── Middleware: autenticação, abilities e X-Request-ID
│
└── Controller invocável
│
├── Form Request → DTO
│
└── Action de aplicação
│
├── transação + locks no PostgreSQL
├── Models + ledger + idempotência
└── evento pós-commit → listener → log estruturado
Principais diretórios:
app/
├── Actions/ # Casos de uso de autenticação, saúde e carteira
├── Console/Commands/ # Gestão de papéis e reconciliação
├── DTOs/ # Dados validados que entram nas Actions
├── Enums/ # Tipos, estados, papéis e lançamentos
├── Events/ # Eventos financeiros pós-commit
├── Http/ # Controllers, Requests, Resources e middleware
├── Listeners/ # Auditoria estruturada dos eventos
├── Models/ # Entidades Eloquent e relacionamentos
├── Policies/ # Autorização administrativa
└── Support/ # Idempotência e correlação HTTP
database/
├── factories/ # Factories válidas para os quatro models centrais
├── migrations/ # Schema, constraints e triggers
└── seeders/ # Cenário financeiro local reproduzível
docs/api/ # OpenAPI e collection Postman versionados
tests/ # Testes Unit, Feature, integração e concorrência
O arquivo-fonte do diagrama está em schema.dbml.
| Método | Rota | Acesso | Descrição |
|---|---|---|---|
GET |
/api/health |
Público | Saúde da aplicação e dependências |
POST |
/api/v1/auth/register |
Público | Cadastra usuário e carteira |
POST |
/api/v1/auth/login |
Público | Emite token Sanctum |
POST |
/api/v1/auth/logout |
Autenticado | Revoga o token atual |
GET |
/api/v1/auth/me |
wallet:read |
Retorna perfil e carteira |
GET |
/api/v1/wallet |
wallet:read |
Consulta saldo da própria carteira |
GET |
/api/v1/wallet/transactions |
wallet:read |
Lista o histórico paginado |
POST |
/api/v1/deposits |
wallet:transact |
Realiza depósito idempotente |
POST |
/api/v1/transfers |
wallet:transact |
Realiza transferência idempotente |
POST |
/api/v1/transactions/{transaction}/reversals |
Admin + wallet:reverse |
Reverte depósito ou transferência |
A autenticação usa Authorization: Bearer {TOKEN}. Depósito, transferência e reversão também exigem um header Idempotency-Key não vazio, com no máximo 255 caracteres.
- Docker Engine com Docker Compose v2.
- GNU Make, opcional, para os atalhos operacionais.
- PHP 8.3 ou superior.
- Composer 2.
- PostgreSQL.
- Redis.
- Extensões PHP:
bcmath,intl,pcntl,pdo_pgsql,redisezip.
Na raiz do repositório, execute:
make setupO comando:
- cria
.enva partir de.env.example, quando necessário; - constrói e inicia os cinco serviços;
- instala dependências PHP;
- gera
APP_KEYsomente quando ainda estiver vazia; - executa migrations;
- carrega os dados de demonstração.
Sem Make:
cp .env.example .env
docker compose up -d --build
docker compose exec app composer install --no-interaction
docker compose exec app php artisan key:generate
docker compose exec app php artisan migrate --seedA API estará disponível em:
http://localhost:18080
Verifique o ambiente:
make status
curl http://localhost:18080/api/healthPara interromper os containers sem remover os volumes:
make down- Instale as dependências:
composer install- Crie e ajuste o ambiente:
cp .env.example .env
php artisan key:generatePara serviços executados no host, ajuste pelo menos:
DB_HOST=127.0.0.1
REDIS_HOST=127.0.0.1- Crie os bancos
walletewallet_testno PostgreSQL e execute:
php artisan migrate --seed- Inicie a API e o worker em terminais separados:
php artisan serve --host=127.0.0.1 --port=18080
php artisan queue:work --sleep=3 --tries=3 --timeout=90O phpunit.xml usa o hostname postgres, apropriado ao Docker. Para executar os testes inteiramente no host, forneça uma configuração de teste equivalente apontando para 127.0.0.1.
O arquivo .env.example contém a configuração local completa. As variáveis mais relevantes são:
| Variável | Padrão local | Descrição |
|---|---|---|
APP_NAME |
Carteira API |
Nome da aplicação e da documentação |
APP_URL |
http://localhost:18080 |
URL base usada pela aplicação e pelo Scribe |
APP_LOCALE |
pt_BR |
Idioma padrão das validações |
APP_DEBUG |
true |
Debug local; deve ser false fora do desenvolvimento |
AUTH_TOKEN_NAME |
api-token |
Nome dos tokens Sanctum |
AUTH_TOKEN_EXPIRATION_MINUTES |
60 |
Validade dos tokens em minutos |
DB_HOST |
postgres |
Host PostgreSQL dentro do Compose |
DB_DATABASE |
wallet |
Banco principal local |
DB_USERNAME |
wallet |
Usuário local do PostgreSQL |
DB_PASSWORD |
wallet_local_password |
Senha somente para ambiente local |
REDIS_HOST |
redis |
Host Redis dentro do Compose |
CACHE_STORE |
redis |
Backend de cache |
QUEUE_CONNECTION |
redis |
Backend da fila |
APP_PORT |
18080 |
Porta HTTP publicada pelo Nginx |
APP_UID / APP_GID |
1000 |
IDs usados pelo usuário não-root da imagem |
Nunca versione um .env real ou reutilize as credenciais locais em produção.
Carregue o cenário separadamente com:
make seedTodas as contas abaixo usam a senha local password:
| Papel | Saldo final | |
|---|---|---|
| Administrador | admin@carteira.local |
R$ 0,00 |
| Usuário | ana@carteira.local |
R$ 750,00 |
| Usuário | bruno@carteira.local |
R$ 750,00 |
O cenário cria um depósito de R$ 1.000,00 para Ana, um depósito de R$ 500,00 para Bruno e uma transferência de R$ 250,00 de Ana para Bruno. O seeder pode ser executado novamente sem duplicar recursos ou saldos.
Defina a URL base:
BASE_URL=http://localhost:18080curl --request GET "$BASE_URL/api/health" \
--header 'Accept: application/json'curl --request POST "$BASE_URL/api/v1/auth/register" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
"name": "Maria Silva",
"email": "maria@example.com",
"password": "StrongPassword123!",
"password_confirmation": "StrongPassword123!"
}'curl --request POST "$BASE_URL/api/v1/auth/login" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
"email": "ana@carteira.local",
"password": "password"
}'Copie data.access_token da resposta:
TOKEN='cole-o-token-aqui'curl --request GET "$BASE_URL/api/v1/wallet" \
--header 'Accept: application/json' \
--header "Authorization: Bearer $TOKEN"curl --request POST "$BASE_URL/api/v1/deposits" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $TOKEN" \
--header 'Idempotency-Key: deposit-example-001' \
--data '{
"amount": 15000,
"currency": "BRL"
}'curl --request POST "$BASE_URL/api/v1/transfers" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $TOKEN" \
--header 'Idempotency-Key: transfer-example-001' \
--data '{
"destination_user_id": 3,
"amount": 5000,
"currency": "BRL"
}'curl --request GET "$BASE_URL/api/v1/wallet/transactions?page=1&per_page=15" \
--header 'Accept: application/json' \
--header "Authorization: Bearer $TOKEN"Faça login como admin@carteira.local, copie o token para ADMIN_TOKEN e informe o ULID da transação:
ADMIN_TOKEN='cole-o-token-administrativo-aqui'
TRANSACTION_ID='01K0M8W4Z8H7Y2K5Q9R3C6D1EF'
curl --request POST \
"$BASE_URL/api/v1/transactions/$TRANSACTION_ID/reversals" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $ADMIN_TOKEN" \
--header 'Idempotency-Key: reversal-example-001' \
--data '{
"reason": "Solicitação confirmada pelo atendimento."
}'curl --request POST "$BASE_URL/api/v1/auth/logout" \
--header 'Accept: application/json' \
--header "Authorization: Bearer $TOKEN"Todos os valores monetários em centavos são inteiros. 15000 representa R$ 150,00. A API aceita apenas BRL e não recebe números decimais ou strings numéricas para amount.
Cada alteração de saldo produz lançamento de crédito ou débito com balance_before e balance_after. Constraints verificam a transição matemática, e triggers impedem UPDATE, DELETE e TRUNCATE no ledger.
Depósitos, transferências e reversões exigem Idempotency-Key. Repetir a mesma chave e o mesmo payload devolve a resposta original sem duplicar dinheiro. Reutilizar a chave com outro payload retorna conflito HTTP 409.
As Actions usam transações PostgreSQL e lockForUpdate. Transferências bloqueiam as carteiras em ordem determinística, reduzindo risco de deadlock e impedindo gasto duplo do mesmo saldo.
Uma reversão cria uma nova transação e lançamentos opostos; o histórico original não é apagado. Se o destinatário já gastou o valor recebido, a reversão ainda preserva a verdade contábil e pode deixar sua carteira negativa.
Usuários comuns recebem abilities de leitura e transação. Apenas administradores recebem wallet:reverse, e a Policy também confirma o papel persistido no banco.
Não existe endpoint público para elevar papéis. A gestão é operacional:
docker compose exec app php artisan user:promote-admin usuario@example.com
docker compose exec app php artisan user:demote-admin usuario@example.comO rebaixamento revoga todos os tokens do usuário. Após uma promoção, faça novo login para emitir um token com as abilities administrativas.
| Comando | Descrição |
|---|---|
make help |
Lista os atalhos disponíveis |
make setup |
Prepara ambiente, containers, dependências, migrations e seed |
make up |
Inicia os serviços |
make down |
Interrompe os serviços sem apagar volumes |
make migrate |
Executa migrations pendentes |
make seed |
Carrega ou atualiza o cenário demonstrativo |
make test |
Executa a suíte completa |
make lint |
Verifica o padrão Laravel Pint |
make format |
Formata o código com Pint |
make docs |
Regenera OpenAPI e Postman versionados |
make reconcile |
Compara os saldos com o ledger |
make logs |
Acompanha logs do Compose |
make shell |
Abre um shell no container da aplicação |
make status |
Exibe o estado dos serviços |
O modo padrão é somente leitura e retorna falha quando encontra divergências:
make reconcileA correção precisa ser explícita:
docker compose exec app php artisan wallet:reconcile --fixO modo --fix bloqueia a carteira, recalcula o saldo pelo ledger, incrementa a versão e registra a correção em log.
Com os serviços ativos:
- HTML interativo:
http://localhost:18080/docs; - OpenAPI servido:
http://localhost:18080/docs.openapi; - Postman servido:
http://localhost:18080/docs.postman.
Artefatos versionados:
Regere os dois arquivos depois de qualquer alteração no contrato HTTP:
composer docs:generateNo fluxo Docker, use make docs. O exporter normaliza variáveis, autenticação e identificadores da collection para que a geração seja determinística.
make testmake lintmake formatdocker compose exec app php artisan test \
tests/Feature/Api/V1/Transfer/CreateTransferTest.phpA suíte cobre autenticação, autorização, saldo, idempotência, rollback, constraints, eventos, reconciliação e concorrência real. Os testes usam wallet_test, criado pelo container PostgreSQL.
- Apenas BRL é suportado.
- Não existe integração com banco, PIX, cartão ou outro provedor externo.
- A API não oferece refresh token; tokens expiram conforme configuração.
- Não há interface web para operação financeira.
- A gestão de administradores é feita somente por console.
- O ledger é interno; não há exportação de extrato contábil.
- O ambiente local possui credenciais deliberadamente simples e não serve como configuração de produção.
- PHPStan/Larastan permanece como evolução futura opcional de qualidade.
- Adicionar PHPStan/Larastan e integrar ao fluxo de qualidade.
- Criar pipeline de integração contínua.
- Publicar métricas operacionais e tracing distribuído.
- Adicionar filtros por tipo e intervalo de datas ao histórico.
- Implementar notificações de operações financeiras.
- Integrar um provedor externo de pagamentos em ambiente isolado.
- Planejar suporte a múltiplas moedas com regras explícitas de câmbio.
O projeto está configurado sob a licença MIT no composer.json.
A autoria e a evolução técnica são preservadas no histórico Git do repositório.
