Adicionar Pagamentos Stripe Com o Claude Code
Liga o Stripe Checkout, os webhooks e o portal do cliente numa app Next.js usando o Claude Code. Do primeiro prompt ao pagamento em produção numa só sessão.
Pare de configurar. Comece a construir.
Templates SaaS com orquestração de IA.
Problema: a integração do Stripe tem 7 peças móveis: o checkout, os webhooks, o portal do cliente, as variáveis de ambiente, as chaves de teste, as chaves de produção e a CLI. A maioria dos tutoriais cobre três delas e deixa-te a fazer debug ao resto à meia-noite.
Ganho Rápido: adiciona o servidor MCP do Stripe ao Claude Code num único comando, para que o Claude possa consultar clientes, preços e subscrições em tempo real enquanto constróis:
claude mcp add --transport http stripe https://mcp.stripe.com/Essa única linha dá ao Claude acesso direto à tua conta Stripe. Ele consegue ler dados ao vivo, verificar que os teus produtos existem, e apanhar erros de configuração antes de chegarem ao teu handler de webhook.
O Que Precisas Antes de Começar
Precisas de ter três coisas no sítio.
Uma conta Stripe com pelo menos um produto e um preço criados. Podes pedir ao Claude para os criar via servidor MCP, sem tocar no Dashboard. Assim que o MCP estiver ligado, um prompt como "create a product called Pro Plan with a $29/month recurring price" basta.
Claude Code com o MCP do Stripe ligado (comando acima). A alternativa é a skill de boas práticas do Stripe, que injeta documentação do Stripe na tua sessão:
npx skills add https://docs.stripe.com --yesO servidor MCP é a melhor escolha para desenvolvimento ativo. O Claude pode chamá-lo a meio da sessão para listar os teus preços, procurar um cliente por email, obter o estado atual de uma subscrição, ou verificar que o teu webhook secret corresponde ao que o Dashboard mostra. A skill dá-te apenas contexto de documentação. Não consulta a tua conta.
As duas coisas não se excluem mutuamente. Algumas equipas correm ambas: o MCP para consultas ao vivo, a skill para orientação de boas práticas. O MCP com uma chave de API restrita (rk_test_...) é a configuração mais segura em modo de teste, porque as chaves restritas limitam que objetos Stripe o Claude pode tocar.
Quatro variáveis de ambiente no teu ficheiro .env.local:
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_URL=http://localhost:3000A STRIPE_SECRET_KEY nunca toca no cliente. A NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY é segura para expor. A STRIPE_WEBHOOK_SECRET vem da CLI do Stripe quando inicias o reencaminhamento local de eventos (coberto na secção de testes). A NEXT_PUBLIC_URL alimenta os teus parâmetros success_url e return_url.
Checkout Sessions vs. Payment Intents
Há duas APIs do Stripe que podem receber um pagamento. Escolher a errada acrescenta semanas de trabalho.
Checkout Sessions (stripe.checkout.sessions.create()) reencaminham o utilizador para uma página alojada pelo Stripe ou para um formulário embutido no teu domínio. O Stripe trata da introdução do cartão, do 3D Secure, do Apple Pay, da apresentação da moeda e dos impostos. Para a maioria dos produtos SaaS, é a escolha certa. Tanto as compras únicas como as subscrições passam por aqui.
Payment Intents (stripe.paymentIntents.create()) são de nível mais baixo. Constróis tu próprio o formulário com o Stripe Elements, recolhes os dados do cartão e chamas a API. Mais controlo, mais código, mais manutenção. Usa isto apenas quando precisas de uma UI de checkout totalmente personalizada que uma página alojada não consiga entregar.
O padrão preferido em 2026 para as Checkout Sessions é o Embedded Checkout: ui_mode: 'embedded' mantém o utilizador no teu domínio, usa os componentes EmbeddedCheckoutProvider e EmbeddedCheckout do @stripe/react-stripe-js, e devolve a um return_url que defines. O Hosted Checkout (redirecionamento para stripe.com) continua a funcionar, mas o Embedded é para onde o Stripe encaminha as novas integrações.
Construir o Fluxo de Checkout
Pede ao Claude para fazer o scaffold da rota, e depois verifica o param mode antes de lançar.
Um prompt útil para dar ao Claude aqui é: "Build me a Stripe Checkout Sessions route in app/api/checkout/route.ts. Use mode: payment for one-time purchases, return the session URL as JSON, and read all URLs from environment variables."
Para uma compra única, a rota fica assim:
// app/api/checkout/route.ts
import { NextResponse } from 'next/server'
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-11-20.acacia',
})
export async function POST(request: Request) {
const { priceId } = await request.json()
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing`,
})
return NextResponse.json({ url: session.url })
}Para uma subscrição, muda exatamente uma coisa. Define mode: 'subscription' em vez de mode: 'payment'. O teu priceId tem de apontar para um preço recorrente (não um preço único) no Stripe. Tudo o resto na rota fica igual.
Para mudar para o Embedded Checkout, adiciona ui_mode: 'embedded' e substitui success_url por return_url. A resposta devolve session.client_secret em vez de session.url, que o EmbeddedCheckoutProvider usa no frontend.
Webhooks: A Parte Que Toda a Gente Erra
Os webhooks são onde o Stripe responde à tua app. Toda a decisão de provisionamento (conceder acesso, revogar acesso, enviar um recibo) devia viver no handler de webhook, não no redirecionamento de sucesso do checkout. Os utilizadores fecham separadores e carregam no botão de voltar atrás. O redirecionamento não é fiável. O webhook dispara sempre.
O problema crítico no App Router do Next.js é o parsing do corpo em bruto. A verificação de assinatura do Stripe (stripe.webhooks.constructEvent) exige o corpo do pedido em bruto, como string. O App Router faz o parsing do corpo antes de o teu handler o ver. Se chamares request.json(), a stream é consumida e a verificação falha sempre.
Dá ao Claude a restrição exata: "Build a Stripe webhook handler at app/api/stripe-webhook/route.ts. It must use request.text() for the raw body, not request.json(), and verify the signature before processing any events."
Usa antes request.text():
// app/api/stripe-webhook/route.ts
import { NextResponse } from 'next/server'
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-11-20.acacia',
})
export async function POST(request: Request) {
const rawBody = await request.text()
const sig = request.headers.get('stripe-signature')!
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(
rawBody,
sig,
process.env.STRIPE_WEBHOOK_SECRET!
)
} catch (err) {
return NextResponse.json(
{ error: 'Webhook signature verification failed' },
{ status: 400 }
)
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session
// Provision access, send welcome email
await grantAccess(session.customer as string)
break
}
case 'customer.subscription.updated': {
const sub = event.data.object as Stripe.Subscription
// Update tier in DB
await updateSubscription(sub.customer as string, sub.status)
break
}
case 'customer.subscription.deleted': {
const sub = event.data.object as Stripe.Subscription
// Revoke access
await revokeAccess(sub.customer as string)
break
}
case 'invoice.payment_failed': {
const invoice = event.data.object as Stripe.Invoice
// Send dunning email, flag in DB
await handleFailedPayment(invoice.customer as string)
break
}
}
return NextResponse.json({ received: true })
}Os quatro eventos a tratar, no mínimo, estão na tabela abaixo. O resto pode ser acrescentado à medida que a tua lógica de faturação cresce.
| Evento | Quando dispara | Ação |
|---|---|---|
checkout.session.completed | Pagamento ou subscrição iniciados | Provisionar acesso, enviar email de boas-vindas |
customer.subscription.updated | Mudança de plano, renovação, fim de trial | Atualizar o escalão na BD |
customer.subscription.deleted | Cancelamento | Revogar acesso |
invoice.payment_failed | Cobrança recorrente falhou | Enviar email de cobrança, sinalizar na BD |
Mais uma coisa: esta rota tem de ser excluída de qualquer middleware do Next.js que leia o corpo, e não pode ficar envolvida nas verificações de autenticação da tua app. O Stripe envia-lhe um pedido não autenticado. Protege-a apenas com a verificação de assinatura.
Configurar o Portal do Cliente
O portal do cliente deixa os utilizadores gerirem a sua própria subscrição: cancelar, atualizar a informação de pagamento, ver faturas. Há um passo que trama a maioria dos programadores.
Tens de configurar o portal no Dashboard do Stripe antes de criar quaisquer sessões de portal. Sem uma configuração guardada, cada chamada billingPortal.sessions.create() devolve um erro. O URL de configuração para o modo de teste é https://dashboard.stripe.com/test/settings/billing/portal. O modo de produção tem o seu próprio URL separado em https://dashboard.stripe.com/settings/billing/portal. Ambos exigem configuração antes de fazeres deploy.
O passo de configuração é onde decides que ações os utilizadores podem realizar: cancelar a subscrição, atualizar o método de pagamento, mudar de plano, descarregar faturas. Define essas opções no Dashboard, guarda a configuração, e depois as tuas chamadas à API vão funcionar.
Uma vez configurada, a rota para criar uma sessão é curta:
// app/api/portal/route.ts
import { NextResponse } from 'next/server'
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-11-20.acacia',
})
export async function POST(request: Request) {
const { customerId } = await request.json()
const session = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: `${process.env.NEXT_PUBLIC_URL}/account`,
})
return NextResponse.json({ url: session.url })
}O teu frontend redireciona para session.url. O utilizador vê a sua página de faturação do Stripe, faz alterações e regressa a /account quando termina. As alterações disparam como eventos customer.subscription.updated ou customer.subscription.deleted, que o teu handler de webhook já cobre.
Nota: as configurações de portal do modo de teste e do modo de produção são completamente separadas. Configurar o portal em modo de teste não o configura para o modo de produção. Passa pelos mesmos passos de configuração em ambos os ambientes antes de ires para produção.
Testar o Fluxo Completo
A CLI do Stripe reencaminha eventos ao vivo do Stripe para o teu servidor local. Corre isto num terminal separado antes de testar qualquer pagamento:
stripe listen --forward-to localhost:3000/api/stripe-webhookA CLI imprime um webhook signing secret quando arranca. Esse valor vai para a STRIPE_WEBHOOK_SECRET no teu .env.local. Muda sempre que reinicias o listener, por isso não o metas hardcoded.
Para disparar um evento específico sem clicar por um formulário de pagamento, usa o stripe trigger:
stripe trigger checkout.session.completed
stripe trigger customer.subscription.deleted
stripe trigger invoice.payment_failedCartões de teste para diferentes cenários:
| Cenário | Número do cartão |
|---|---|
| Pagamento bem-sucedido | 4242 4242 4242 4242 |
| Recusado | 4000 0000 0000 0002 |
| 3D Secure obrigatório | 4000 0025 0000 3155 |
| Fundos insuficientes | 4000 0000 0000 9995 |
Usa qualquer data de validade futura, qualquer CVC de 3 dígitos e qualquer código postal. O número do cartão é o único valor que altera o resultado.
A sequência de teste recomendada é: arranca o listener da CLI, abre a tua página de checkout, completa um pagamento com o cartão 4242 4242 4242 4242, observa o evento checkout.session.completed a chegar ao teu terminal, e confirma que o teu handler correu (verifica a BD ou os logs). Depois dispara stripe trigger invoice.payment_failed para confirmar que o teu caminho de cobrança funciona, sem esperar por uma cobrança falhada real. Deixa o cartão de 3D Secure (4000 0025 0000 3155) para o fim. O Stripe redireciona por um passo de autenticação que o teu frontend tem de tratar. Se o redirecionamento falhar em silêncio, a sessão nunca se completa e nenhum webhook dispara.
Checklist Para Ir Para Produção
Mudar do modo de teste para o de produção são quatro passos, não uma simples troca de chave.
Primeiro, substitui as tuas chaves de teste (sk_test_, pk_test_) por chaves de produção (sk_live_, pk_live_) nas variáveis de ambiente de produção.
Segundo, configura o portal do cliente em modo de produção em https://dashboard.stripe.com/settings/billing/portal. O modo de teste e o modo de produção têm configurações de portal completamente separadas. O que defines em modo de teste não passa para o outro.
Terceiro, define o teu endpoint de webhook no Dashboard do Stripe para o teu URL de produção. Vai a Developers > Webhooks > Add endpoint e aponta-o para https://yourdomain.com/api/stripe-webhook. Copia o webhook signing secret de produção e define-o como STRIPE_WEBHOOK_SECRET em produção.
Quarto, confirma os teus price IDs. Os preços de modo de teste (price_test_...) não existem em modo de produção. Quaisquer price IDs em hardcode precisam de ser atualizados para price IDs de produção, ou lidos das variáveis de ambiente.
Corre um pagamento real com um cartão real (e depois faz o reembolso) antes de dares a integração por concluída. O trigger da CLI é bom para testar handlers, mas nada confirma o caminho completo como uma cobrança a sério.
O Que o Build This Now Entrega Já Pronto
Escrever e fazer debug a este fluxo leva um dia inteiro à maioria dos programadores. A edge function de checkout do Stripe, o handler de webhook, a rota do portal do cliente e as quatro variáveis de ambiente vêm já ligadas no Build This Now desde o primeiro dia. A integração faz deploy nas Supabase Edge Functions, testada e a funcionar, antes de escreveres a tua primeira funcionalidade personalizada.
O Stripe não é a parte difícil. Difícil é ligar todas as peças pela ordem certa. Depois de o fazeres uma vez, sabes onde estão as falhas.
Pare de configurar. Comece a construir.
Templates SaaS com orquestração de IA.
Modo Auto do Claude Code
Um segundo modelo Sonnet revê cada chamada de ferramenta do Claude Code antes de ser executada. O que o modo auto bloqueia, o que permite e as regras de permissão que cria nas tuas definições.
Feedback Loops
Passe para o Claude Code um prompt que escreve código, roda o seu teste ou comando de dev, lê a saída, corrige o que quebra e faz loop até a suite ficar verde.