Next.js SaaS
Como implementar assinaturas do Stripe no Next.js: Checkout, webhooks e estado da cobrança
Implemente cobrança de assinaturas do Stripe no Next.js com Checkout Sessions criadas no servidor, webhooks verificados, direitos de acesso locais e processamento idempotente.
O redirecionamento de volta do Checkout faz parte da experiência do usuário. São os webhooks verificados que permitem à aplicação conciliar o estado assíncrono da cobrança de forma confiável.
Mantenha um modelo local de cobrança
Armazene os IDs de cliente e assinatura do provedor na conta responsável e normalize o plano, o status, o período atual, o estado de cancelamento e os direitos de acesso de que a aplicação precisa. O objeto do provedor não substitui um modelo de acesso específico do produto.
Defina como os estados trialing, active, past_due, canceled e incomplete afetam a aplicação. Mantenha o histórico de cobrança e as decisões de acesso compreensíveis. Um rótulo na página de preços deve corresponder a um ID de preço configurado no servidor, sem aceitar um preço arbitrário enviado pelo navegador.
Crie Checkout Sessions no servidor
Autentique a conta, valide o plano solicitado contra uma lista de opções permitidas, crie ou reutilize seu Stripe Customer e crie uma Checkout Session no modo de assinatura. Inclua o ID estável da conta nos metadados para conciliar os eventos posteriores sem depender de um endereço de e-mail.
Retorne a URL do provedor ou redirecione para ela. Nunca exponha a chave secreta ao navegador. Use uma chave de idempotência quando uma requisição repetida da aplicação não puder criar operações duplicadas no provedor.
"use server";
export async function startCheckout(plan: PlanId) {
const account = await requireBillingAdmin();
const price = PRICE_IDS[plan];
if (!price) return { error: "Unknown plan" };
const session = await stripe.checkout.sessions.create({
mode: "subscription",
customer: account.stripeCustomerId,
line_items: [{ price, quantity: 1 }],
success_url: SITE_URL + "/settings/billing?checkout=complete",
cancel_url: SITE_URL + "/pricing",
metadata: { accountId: account.id },
});
redirect(session.url);
}Trate a página de retorno como uma confirmação pendente
O cliente pode chegar à URL de sucesso antes que a aplicação processe todos os eventos assíncronos, e uma URL copiada não comprova pagamento. Mostre um estado de confirmação pendente, leia o registro local da assinatura e atualize-o após o processamento do webhook.
Não conceda acesso permanente com base em um parâmetro de consulta ou em uma resposta do provedor recebida no cliente. Os direitos de acesso devem seguir o estado verificado no servidor. Quando a cobrança permanecer incompleta, deixe claro como o cliente pode tentar novamente ou buscar suporte.
Verifique o corpo original da requisição do webhook
Um Route Handler do Next.js pode ler o corpo sem alterações com request.text e a assinatura com request.headers. Passe o corpo original, o valor de Stripe-Signature e o segredo do endpoint para a biblioteca oficial. Interpretar o JSON antes altera o corpo usado na verificação e pode fazer a validação da assinatura falhar.
Rejeite assinaturas inválidas antes de realizar qualquer trabalho. Mantenha separados os segredos dos endpoints de teste e produção. Retorne rapidamente uma resposta de sucesso depois de registrar o trabalho aceito; o envio demorado de e-mails ou a sincronização devem ficar em uma tarefa em segundo plano.
export async function POST(request: Request) {
const payload = await request.text();
const signature = request.headers.get("stripe-signature");
const event = stripe.webhooks.constructEvent(
payload,
signature,
process.env.STRIPE_WEBHOOK_SECRET,
);
await recordBillingEvent(event);
return new Response(null, { status: 200 });
}Processe eventos de forma idempotente
A entrega de webhooks pode se repetir, e a ordem não é garantida. Insira o ID do evento do provedor com uma restrição de unicidade antes de aplicar seus efeitos. Se o evento já existir, confirme o recebimento sem enviar outro e-mail nem aplicar a alteração duas vezes.
Nas atualizações e exclusões de assinaturas, consulte ou determine o estado atual e oficial do provedor quando a ordem dos eventos puder deixar o registro local desatualizado. Sempre que possível, registre o evento e atualize o estado da cobrança na mesma transação.
- —Registre o ID e o tipo do evento.
- —Associe-o à conta local.
- —Aplique o estado de assinatura normalizado uma vez.
- —Coloque as tarefas complementares mais lentas na fila depois de persistir as mudanças de estado.
Trate todo o ciclo de vida da assinatura
Dê suporte à criação de clientes e assinaturas, mudanças de plano, renovações, falhas de pagamento, cancelamento agendado, cancelamento imediato e exclusão. Use o portal do cliente quando ele atender ao produto, em vez de recriar o gerenciamento de formas de pagamento e faturas sem necessidade.
A autorização continua sendo necessária: somente quem tiver a função de acesso adequada na conta pode iniciar o checkout ou abrir o gerenciamento de cobrança. Registre quem solicitou a mudança de plano e mostre a data em que ela entra em vigor para que o suporte consiga explicar o estado da conta.
Teste novas tentativas e falhas
Use a CLI do Stripe ou um destino de eventos de teste para encaminhar eventos assinados. Repita o mesmo evento, entregue uma atualização antiga depois de uma mais recente, use o segredo errado, interrompa o banco de dados e provoque uma falha de pagamento. Confirme que o acesso e o estado local continuam compreensíveis.
Teste a compilação de produção com credenciais do modo de teste antes de habilitar o endpoint real. Mantenha separados os IDs de produção e teste e impeça que uma implantação de prévia se registre por acidente como destino de produção.
Execute a cobrança em uma versão estável com HTTPS
O Stripe exige um endpoint de webhook HTTPS acessível publicamente em produção. O Adios fornece o ambiente de execução persistente do Next.js, a rota HTTPS gerada, domínios personalizados, TLS gerenciado, referências a segredos, verificações de saúde e logs de execução necessários para operar esse endpoint junto à interface do SaaS.
Implante e verifique o Checkout no modo de teste, investigue falhas de assinatura sem registrar segredos do payload nos logs e depois promova a versão saudável. Registre a URL estável do webhook de produção, em vez de uma prévia temporária. Se uma futura versão candidata falhar na compilação ou na verificação de saúde, ela não precisa substituir o endpoint de cobrança que está funcionando.
env:
STRIPE_SECRET_KEY: secret://STRIPE_SECRET_KEY
STRIPE_WEBHOOK_SECRET: secret://STRIPE_WEBHOOK_SECRET
DATABASE_URL: secret://DATABASE_URL
runtime:
name: node@24
port: 3000
health_path: /api/health