Arklay Spirits — e-commerce de bebidas

E-commerce de bebidas com delivery por bairro: app cliente em Next.js 15, backoffice em Laravel 12 com Filament, pagamento PagBank em cartão e PIX com webhook idempotente e carrinho travado durante a transação.

Next.js 15TypeScriptTailwind CSSLaravel 12FilamentMySQLSanctumDockerPagBankCloudflare

O contexto

Uma distribuidora de bebidas que vende por delivery na própria região. Loja física com horário de funcionamento, entrega limitada a alguns bairros, taxa diferente por bairro, cupom de desconto em campanha. O pedido chega, alguém separa, alguém entrega.

Antes do sistema, o pedido chegava por WhatsApp. Isso funciona até um ponto: o atendente digita o endereço errado, cobra a taxa de outro bairro, aceita pedido depois que a loja fechou, ou perde o pedido no meio de vinte conversas abertas.

O problema

O ponto difícil não era o catálogo. Era pagamento e concorrência.

Em PIX, existe uma janela entre o cliente gerar o QR Code e o pagamento cair. Nessa janela o pedido não pode ficar em limbo: se o sistema confirma cedo demais, a loja separa mercadoria de um pedido que nunca foi pago; se confirma tarde demais, o cliente já pagou e ninguém sabe. E o carrinho precisa estar congelado — se o preço ou o estoque mudarem enquanto o cliente paga, o valor cobrado deixa de bater com o valor do pedido.

O segundo problema era estrutural: regra de negócio de e-commerce se multiplica. "A loja está aberta?" é uma pergunta feita na home, no carrinho, no checkout e no webhook. Se a resposta for reimplementada em cada um desses lugares, uma hora elas divergem.

Decisões técnicas

Monorepo em vez de dois repositórios. App cliente e backoffice moram no mesmo repo. A alternativa — repositórios separados — obriga a coordenar dois PRs e dois deploys toda vez que o contrato da API muda. Aqui um campo novo no pedido entra em um commit só, que toca o endpoint Laravel e o tipo TypeScript que o consome. Em troca, aceitei um deploy mais acoplado, o que é aceitável para uma equipe deste tamanho.

Sanctum em vez de Passport/OAuth2. O app Next.js é primeiro-parte. Não existe integração de terceiro consumindo essa API, então o fluxo completo de OAuth2 (client credentials, refresh, escopos) seria cerimônia sem consumidor. Sanctum resolve com token simples e menos superfície para configurar errado. Se um dia entrar um parceiro externo, essa decisão volta à mesa.

Filament no backoffice em vez de CRUD à mão. A operação da loja precisa de tela de produto, pedido, cupom e zona de entrega. Escrever isso à mão são semanas de formulário e tabela que ninguém vai lembrar de manter. Filament gera esse painel sobre os mesmos models Eloquent que a API já usa — uma fonte de verdade, não duas. O custo é ficar dentro das convenções do Filament quando a tela foge do padrão; nesse caso, a tela vira componente próprio.

Regra de negócio em Actions, não em controller. Aplicar cupom, calcular taxa por bairro, abrir e fechar a loja por horário, confirmar pagamento: cada uma é uma classe com uma responsabilidade. O controller só traduz HTTP. Isso vale o incômodo de ter mais arquivos, porque a mesma Action é chamada pelo checkout e pelo webhook, e o comportamento é garantidamente idêntico nos dois caminhos.

Todo cálculo financeiro no backend. O frontend exibe preço, nunca decide preço. Confiar no valor que vem do cliente é o erro clássico de e-commerce; o backend recalcula o carrinho inteiro antes de gerar a cobrança.

Webhook idempotente e carrinho travado. A confirmação do PagBank pode chegar duas vezes, ou fora de ordem. O handler é idempotente por identificador da transação: reprocessar o mesmo evento não muda o pedido de novo. Durante a janela de pagamento o carrinho fica congelado e reservado, e libera na confirmação ou na expiração.

O que ficou pronto

E-commerce em produção com catálogo, carrinho, cupons, zonas de entrega por bairro, abertura e fechamento automáticos por horário, pagamento em cartão e PIX, e painel administrativo para a operação. Deploy em Docker com Nginx, SSL e Cloudflare na frente — sem depender de plataforma fechada de e-commerce.

O que eu faria diferente hoje

Colocaria a reserva de estoque em fila desde o começo, em vez de resolver no ciclo da requisição. Funciona no volume atual, mas em pico de campanha uma fila dedicada dá controle melhor sobre retentativa e expiração.

Interface do Sistema

Storefront do E-commerce Arklay Spirits

Painel Administrativo de Pedidos

Voltar para projetos