Byro
E-commerce de eBooks — API Laravel + React, com checkout Mercado Pago.
Back-end (API)
- PHP 8.3
- Laravel 11
- PostgreSQL 16
- Sanctum
- HMAC-SHA256
- Mercado Pago SDK
- Scramble (OpenAPI 3.1)
- Pest 3
Front-end
- React 18
- Vite 6
- React Router v7
- Tailwind v3
- Zustand
- Axios
- date-fns
- Mercado Pago SDK React
Infra & Deploy
- Docker
- Docker Compose
- Nginx
- Caddy
- Hetzner VPS
- Vercel
Full-screen para maior qualidade.
Contexto
Byro é um e-commerce de eBooks. A plataforma cobre cadastro, autenticação por token, catálogo, carrinho, checkout via Mercado Pago e entrega do PDF por URL assinada.
O back-end trata cenários comuns em integrações com gateways de pagamento: recebimento de webhooks fora de ordem, coexistência entre IPN legado e Webhooks v2, validação de assinatura HMAC sujeita ao parser nativo do PHP, e liberação idempotente do produto frente a múltiplas notificações para o mesmo pagamento.
O front-end implementa integração com gateway (webhook, retorno e os três estados do Mercado Pago).
Decisões técnicas
API · Validação HMAC com defesa em profundidade
O Mercado Pago assina cada webhook com HMAC-SHA256 sobre um manifest no formato id:{paymentId};request-id:{xRequestId};ts:{ts};. O id precisa vir da query string crua: o parse_str() do PHP renomeia data.id para data_id automaticamente, o que quebra a assinatura. Parse manual do QUERY_STRING preserva o nome do parâmetro.
O formato do manifest varia entre topics e versões da API. A validação compara as variações possíveis com a assinatura recebida usando hash_equals (comparação timing-safe). Se nenhuma bate, um modo controlado por config registra o caso e confirma o pagamento direto na API do Mercado Pago via access_token — defesa em profundidade: o produto não é liberado sem confirmação, e uma variação de formato não prevista não derruba a venda.
API · Signed URLs em vez de download autenticado direto
Servir o PDF direto de uma rota autenticada acopla o download ao token do Sanctum e quebra quando o usuário troca de aba, abre em outro dispositivo ou usa um gerenciador de download. A solução é uma URL assinada de 30 minutos (URL::signedRoute), que embute o ebookId e o e-mail do usuário e é validada pelo middleware signed do Laravel.
Resultado: nenhum estado para gerenciar, expiração nativa e nome de arquivo personalizado gerado a partir do e-mail embutido na URL.
API · Scramble no lugar de l5-swagger
Documentar a API em OpenAPI com darkaonline/l5-swagger exige anotações #[OA\Post(...)] e #[OA\Property(...)] em cada endpoint. Essa informação já vive no routes/api.php, no FormRequest e no return type — repetir tudo em atributos duplicaria a mesma definição em vários lugares e deixaria a documentação fácil de desatualizar.
dedoc/scramble lê rotas, FormRequests e return types automaticamente e gera o OpenAPI 3.1 sem anotação. PHPDoc curto por endpoint cobre o resumo e os status menos óbvios (404/409). A documentação fica sempre sincronizada com o código, na mesma UI Swagger em /docs/api.
Front · Zustand + persist para o carrinho
Para um catálogo dinâmico, o carrinho precisou de estado global. Zustand resolve com store pequena (~25 linhas), seleção granular por hook (useCartStore(s => s.items.length) só re-renderiza quando contagem muda), e middleware persist para sincronização com localStorage.
Carrinho sobrevive a reload e logout — comportamento esperado para visitante não autenticado que encheu carrinho antes de criar conta.