Next.js SaaS
Cómo crear suscripciones de Stripe en Next.js: Checkout, webhooks y estado de facturación
Crea la facturación de suscripciones de Stripe en Next.js con Checkout Sessions creadas en el servidor, webhooks verificados, derechos de acceso locales y procesamiento idempotente.
La redirección de regreso desde Checkout es un evento de la experiencia de usuario. Los webhooks verificados permiten que la aplicación concilie de forma fiable el estado asíncrono de facturación.
Mantén un modelo local de facturación
Guarda los ID de cliente y suscripción del proveedor en la cuenta a la que pertenecen y normaliza el plan, el estado, el periodo actual, el estado de cancelación y los derechos de acceso que necesita tu aplicación. El objeto del proveedor no sustituye a un modelo de acceso específico del producto.
Decide cómo afectan a la aplicación los estados trialing, active, past_due, canceled e incomplete. Mantén explicables el historial de facturación y las decisiones de acceso. Una etiqueta de la página de precios debe corresponder a un único ID de precio configurado en el servidor, en lugar de un precio arbitrario enviado por el navegador.
Crea Checkout Sessions en el servidor
Autentica la cuenta, valida el plan solicitado con una lista de valores permitidos, crea o reutiliza su Stripe Customer y crea una Checkout Session en modo de suscripción. Adjunta tu ID estable de cuenta como metadato para poder conciliar los eventos posteriores sin confiar en una dirección de correo electrónico.
Devuelve la URL del proveedor o redirige a ella. Nunca expongas la clave secreta al navegador. Usa una clave de idempotencia cuando una solicitud repetida de la aplicación no deba crear operaciones duplicadas en el proveedor.
"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);
}Trata la página de retorno como un estado pendiente
Un cliente puede llegar a la URL de éxito antes de que tu aplicación procese todos los eventos asíncronos, y una URL copiada no demuestra que se haya pagado. Muestra un estado de confirmación, lee el registro local de la suscripción y actualízalo después de procesar el webhook.
No concedas acceso permanente a partir de un parámetro de consulta ni de una respuesta del proveedor del lado del cliente. Los derechos de acceso deben depender del estado verificado del servidor. Ofrece al cliente una vía clara para reintentar o contactar con soporte cuando la facturación siga incompleta.
Verifica la solicitud de webhook sin procesar
Un Route Handler de Next.js puede leer el cuerpo sin modificar con request.text y la firma desde request.headers. Pasa el cuerpo sin procesar, el valor de Stripe-Signature y el secreto del endpoint a la biblioteca oficial. Analizar primero el JSON cambia el cuerpo usado para la verificación y puede hacer que fallen las firmas.
Rechaza las firmas inválidas antes de realizar trabajo. Mantén separados los secretos de los endpoints de prueba y producción. Devuelve rápidamente una respuesta de éxito después de registrar el trabajo aceptado; el envío de correos o las sincronizaciones que tarden deben pasar a un trabajo en 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 });
}Procesa los eventos de forma idempotente
Las entregas de webhooks pueden repetirse y su orden no está garantizado. Inserta el ID de evento del proveedor con una restricción de unicidad antes de aplicar efectos secundarios. Si el evento ya existe, confirma su recepción sin enviar otro correo ni aplicar dos veces el cambio.
Para las actualizaciones y eliminaciones de suscripciones, recupera o deriva el estado actual del proveedor que sirve como fuente de verdad cuando el orden de los eventos pueda dejar desactualizado el registro local. Siempre que sea posible, guarda el registro del evento y actualiza el estado de facturación en una transacción.
- —Registra el ID y el tipo de evento.
- —Asócialo a la cuenta local.
- —Aplica el estado normalizado de la suscripción una sola vez.
- —Pon en cola las tareas lentas de seguimiento después de persistir los cambios de estado.
Gestiona todo el ciclo de vida de la suscripción
Admite la creación de clientes y suscripciones, los cambios de plan, las renovaciones, los pagos fallidos, la cancelación programada, la cancelación inmediata y la eliminación. Usa el portal de clientes cuando encaje con el producto, en lugar de reconstruir sin motivo la gestión de métodos de pago y facturas.
La autorización sigue siendo necesaria: solo el rol adecuado de la cuenta puede iniciar el proceso de pago o abrir la gestión de facturación. Registra quién solicitó un cambio de plan y muestra la fecha de entrada en vigor para que soporte pueda explicar el estado de la cuenta.
Prueba los reintentos y los fallos
Usa la CLI de Stripe o un destino de eventos de prueba para reenviar eventos firmados. Repite el mismo evento, entrega una actualización antigua después de una más reciente, usa un secreto incorrecto, interrumpe la base de datos y provoca un pago fallido. Confirma que el acceso y el estado local sigan siendo explicables.
Prueba la compilación de producción con credenciales del modo de prueba antes de habilitar el endpoint real. Mantén separados los ID reales y de prueba e impide que un despliegue de vista previa se registre accidentalmente como destino de producción.
Aloja la facturación en una versión HTTPS estable
Stripe requiere un endpoint de webhook HTTPS accesible públicamente en producción. Adios proporciona el entorno de ejecución persistente de Next.js, la ruta HTTPS generada, los dominios personalizados, el TLS gestionado, las referencias a secretos, las comprobaciones de estado y los registros del entorno de ejecución necesarios para operar ese endpoint junto a la interfaz del SaaS.
Despliega y verifica Checkout en modo de prueba, inspecciona los fallos de firma sin registrar los secretos de los datos recibidos y promueve la versión que funciona correctamente. Registra la URL estable del webhook de producción, en lugar de una vista previa temporal. Si una futura versión candidata falla en la compilación o en la comprobación de estado, no tiene por qué sustituir al endpoint de facturación que funciona.
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