DEV Community

CESAR EDUARDO STURMER
CESAR EDUARDO STURMER

Posted on

Construindo o surfaai do zero: stack, decisões e o que eu faria diferente

Depois de alguns anos como engenheiro de software em empresas — frontend architecture, sistemas distribuídos, produtos que outras pessoas definiam — decidi construir algo do zero, sozinho: do primeiro commit até estar rodando em produção, com usuários reais e dinheiro de verdade passando pelo sistema.

Esse algo é o surfaai.

Esta é a primeira de uma série de artigos técnicos sobre as decisões que tomei construindo o surfaai. Não é um tutorial de "como fazer X" — é um relato real de arquitetura: os trade-offs que pesei, os erros que cometi, e o que eu faria diferente se começasse hoje. Se você já pensou em sair de só integrar sistemas dos outros pra construir o seu, ou só curte ver decisão técnica real (com o porquê, não só o resultado final), essa série é pra você.

Por que o surfaai

O surfaai nasceu de um problema simples: conectar alunos a providers (instrutores/prestadores de serviço) numa plataforma que cuidasse de agendamento, pagamento e repasse automático — sem que cada provider precisasse resolver seu próprio checkout, emissão, taxas e split.

Toda plataforma que conecta duas pontas com dinheiro no meio esbarra cedo ou tarde na mesma pergunta difícil: quem fica responsável pela parte financeira? Deixar cada provider resolver isso por conta própria significa fricção de onboarding, inconsistência de experiência pro aluno e nenhum controle real sobre a operação. Foi esse o problema que me fez decidir construir a própria camada de pagamento da plataforma, em vez de terceirizar isso pra fora do produto.

Sou o desenvolvedor principal do projeto — arquitetura, backend, frontend e pagamentos —, e este é o primeiro de uma série de artigos técnicos sobre as decisões que tomamos construindo isso em produção.

A stack

  • Next.js + React + TypeScript no front e nas rotas de API
  • Tailwind pra UI
  • Supabase (Postgres + Auth + RLS) como backend
  • Stripe Connect (Express) para pagamentos e repasse a providers

Cada peça dessa stack foi escolhida pensando em um time pequeno mantendo um produto com dinheiro real passando por ele — ou seja, produtividade de desenvolvimento sem abrir mão de segurança e corretude financeira.

O desafio central: marketplace de verdade precisa de split de pagamento

A parte mais delicada de um marketplace não é o cadastro ou o agendamento — é garantir que o dinheiro do aluno chegue certo ao provider, com a taxa da plataforma descontada corretamente, de forma auditável e sem race conditions.

Isso significou usar Stripe Connect com destination charges: o PaymentIntent é criado na plataforma, com uma taxa de aplicação (application_fee_amount) e o destino do repasse (transfer_data.destination) apontando pra conta Connect do provider. O Stripe cuida da transferência líquida automaticamente assim que o pagamento é confirmado.

Vou detalhar essa arquitetura no próximo artigo da série.

Uma decisão que valeu a pena: créditos só nascem no webhook

Desde o início, decidimos que créditos de compra nunca são criados no client-side — só no evento payment_intent.succeeded do webhook do Stripe. Isso evita duplicação de crédito por race condition (por exemplo, usuário atualizando a página de confirmação duas vezes) e torna o sistema auditável: toda criação de crédito tem uma origem única e rastreável.

Migração de gateway em produção

O surfaai não nasceu no Stripe — começou com Asaas e migramos para Stripe já com usuários ativos. Foi uma das decisões técnicas mais arriscadas do projeto, e vou contar como fizemos isso sem quebrar nada em outro artigo desta série.

O que vem a seguir

Nos próximos posts vou destrinchar:

  • Como funciona destination charges na prática (com código)
  • Por que idempotência em webhook de pagamento não é opcional
  • Como migramos de gateway de pagamento sem downtime

Se você constrói produtos com pagamento embutido, ou só curte ver decisões reais de arquitetura (com os trade-offs e os erros no caminho), me segue aqui e no GitHub.

Top comments (0)