02Critério5 min

A primeira coisa que tiro de qualquer template é o Stripe

Começo quase todos os projetos apagando o checkout, o portal e o webhook. Não é descuido: é separar a infraestrutura de que sempre vou precisar daquela que ainda não é hora.

Por Salva Castells

Imagem de capa: MarkusHagenlocher, CC BY-SA 3.0.
Neste post

Olhei o histórico de um app de controle de horas que escrevi há um ano. O primeiro commit foi às 15:48. Às 16:55, sessenta e sete minutos depois, o segundo commit relevante se chamava "remove Stripe API routes for checkout, portal and webhook". Três arquivos fora: 68 linhas de checkout, 48 de portal, 222 de webhook. Eu ainda não tinha escrito nem a tela principal.

Não foi um arroubo. Esse é o padrão: clono o boilerplate, e a primeira coisa que faço é desligar os pagamentos.

O reflexo: clonar e apagar

O boilerplate que uso já vem com o Stripe montado de fábrica. E é bom que venha: quando é preciso, ter isso resolvido economiza um dia. O problema é que vem ligado por padrão, e ligado significa que arrasta coisas.

Num template interno que comecei nesta primavera, o commit de "remove Stripe integration and related billing features" foi o segundo do repositório, no mesmo dia do init project. O diff não tocava só em rotas. Levou junto:

  • A tabela user_billing inteira. customer_id, price_id, sub_credits, otp_credits, initial_sub_date, last_otp_date, has_sub, cancel_next_month. Nove colunas e um índice sobre user_id para gerenciar uma cobrança que não existia.
  • Os priceId hardcoded na config. Dois planos de exemplo com identificadores reais do Stripe (price_1R3Rhw...) copiados de outro projeto. Isso são bombas-relógio: ninguém apaga, alguém faz o deploy.
  • Cinco variáveis de ambiente. USE_STRIPE, NEXT_PUBLIC_USE_STRIPE, STRIPE_PUBLIC_KEY, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET. Mais cinco entradas no .env.example sobre as quais alguém vai ter que se perguntar se são necessárias.
  • A primeira migração. Foi apagada e regerada, porque a tabela de billing estava dentro.

E o ConfigFile, que declarava [key: string]: string | string[] | PricingConfig | PricingConfig[], passou a ser string | string[]. Um tipo que deixou de ter que contemplar planos de preço é um tipo com o qual se trabalha melhor nos três meses seguintes.

Por que os pagamentos são o que menos me dizem

Um webhook do Stripe não é só mais uma rota. São 222 linhas que verificam assinatura, distinguem checkout.session.completed de invoice.paid de customer.subscription.deleted, e escrevem no banco de dados a partir de fora da sua sessão. Isso tem que ser testado com o CLI reenviando eventos, tem que ser idempotente, e é preciso decidir o que acontece quando o mesmo evento chega duas vezes.

Todo esse trabalho é real. Só que ele responde a uma pergunta que eu ainda não mereci: quanto eu cobro? E essa pergunta vem depois de outra muito mais barata de responder: isso serve para alguém?

Enquanto eu não souber a segunda, o webhook é código que eu mantenho e não exercito. No dia em que fizer falta, eu coloco de volta numa tarde, e dessa vez com o modelo de preços decidido em vez de com dois planos de exemplo herdados.

O que mais cai nessa primeira poda

O Stripe não viaja sozinho. Num MVP de três dias para gerenciar incidentes, o commit número dois do repositório se chama, literalmente, "remove useless things". Duas horas e onze minutos depois do primeiro. Levou o painel de admin completo (client-page.tsx com 250 linhas, a tabela de usuários com 380, o gráfico com 114), as rotas de feedback, a tabela de feedback e seu schema. As rotas do Stripe ele não apagou: renomeou para .txt. route.ts para route.txt. Deixam de compilar, deixam de existir para o Next, e continuam ali caso um dia sejam necessárias.

No dia seguinte, nesse mesmo projeto, caiu o magic link. A página de auth tinha dois caminhos: Google e e-mail com código de verificação, com seu nonce, seu magicLinkSent, seu magicLinkCode, seu handleMagicLinkCode. O commit se chama "eliminar magic link (solo Google)" e leva 116 linhas da página. Num app interno para uma empresa onde todo mundo tem conta Google, o segundo caminho só acrescentava estados para manter.

Em outro template, alguns meses atrás, o mesmo corte pegou o admin: fora /admin, fora /account, fora hasAdminRole(), fora ADMIN_EMAILS do .env.example. E fora quatro colunas do schema de usuários que só serviam ao painel: role, banned, ban_reason, ban_expires. Mais impersonated_by na tabela de sessões.

Essa última é a que eu mais gosto como exemplo. Um campo impersonated_by em sessions não é uma comodidade: é uma decisão de segurança que alguém tem que entender antes de mexer na autenticação. Arrastá-lo "por via das dúvidas" num projeto sem painel de admin é deixar uma porta com fechadura e sem parede em volta.

Onde eu deixei o Stripe

Não é uma regra contra os pagamentos. É uma regra sobre quando.

Num SaaS de geração de imagens, o Stripe entrou cedo e ficou: checkout habilitado, e seis dias depois o webhook ativo e os créditos diários funcionando. Ali a cobrança não era uma hipótese de negócio, era contenção de custos: cada geração custa dinheiro real no momento em que alguém aperta o botão. Sem créditos não há produto, há uma fatura.

O mesmo numa API de transcrição: o billing e os limites do plano gratuito foram escritos quando a conta de usuário ainda estava sendo montada, porque o minuto de áudio processado tem preço desde a primeira requisição.

O critério fica claro quando você põe lado a lado: se cada uso te custa dinheiro, a cobrança é infraestrutura e vai desde o dia um. Se o custo marginal é zero, a cobrança é produto e vai quando você souber o que vende. Uma ferramenta de controle de horas, um gestor de incidentes interno ou um template não gastam nada por usuário. Um modelo de imagens gasta.

O que sobra depois da poda

Depois dessa primeira hora, o que sobrevive em todos os projetos é sempre o mesmo: auth com um único provedor, o banco de dados com as tabelas do domínio e nada mais, e as rotas de que o app precisa para fazer o que faz. Isso é o que eu chamo de infraestrutura. Todo o resto é uma aposta sobre o futuro do produto, e as apostas se pagam em manutenção mesmo quando você não ganha.

Um template completo te economiza a primeira tarde. Apagar o que não é hora te economiza os três meses seguintes.