Stripe Billing — Configuración
Módulo autocontenido para facturación estilo SaaS: Customer Portal, facturas, historial de pagos, métodos de pago, próxima factura y ciclo de facturación.
Está separado del módulo de pagos únicos (lib/stripe/): no comparten
código ni endpoint de webhook. Ambos envuelven la misma cuenta de Stripe,
pero mantienen sus responsabilidades independientes.
Supuesto: este módulo opera sobre un customerId / subscriptionId de
Stripe existente que ya tenés a mano. No crea clientes ni suscripciones:
eso queda fuera de su alcance.
Dependencias
stripe— SDK del lado del servidor, usado enlib/stripe-billing/service.ts@stripe/stripe-js— SDK del lado del cliente, usado enlib/stripe-billing/client.ts
No se instalan automáticamente: agregalos con tu gestor de paquetes antes de usar el módulo. Si ya los instalaste para el módulo de pagos, estás cubierto: son los mismos paquetes.
Mapa de archivos
lib/stripe-billing/
├── service.ts # Internal logic + Stripe SDK instance. Don't touch/import directly elsewhere.
├── actions.ts # Server Actions — import into Server Components.
├── types.ts # Shared types — safe to import anywhere.
└── client.ts # Client-side helpers — import into Client Components.
app/api/webhooks/stripe-billing/
└── route.ts # Billing webhook endpoint. Register this URL in your Stripe Dashboard.
.env.local # Adds STRIPE_BILLING_WEBHOOK_SECRET (reuses STRIPE_SECRET_KEY
# and NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY if you already set
# those up for the payments module).
1. Variables de entorno
STRIPE_SECRET_KEY y NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY se comparten con
el módulo de pagos: definilas una sola vez. Además, agregá:
| Variable | Dónde conseguirla |
|---|---|
STRIPE_BILLING_WEBHOOK_SECRET | Dashboard → Developers → Webhooks (creá un segundo endpoint, separado) |
2. Customer Portal
Primero habilitá y configurá el portal en el Dashboard:
Settings → Billing → Customer portal. Elegí qué pueden hacer los clientes
ahí (cancelar planes, actualizar métodos de pago, ver facturas, etc.):
createCustomerPortalSession simplemente abre una sesión con lo que
configures ahí.
3. Webhooks
Agregá un segundo endpoint, separado en el Stripe Dashboard que apunte a:
https://yourdomain.com/api/webhooks/stripe-billing
Suscribilo como mínimo a:
invoice.paidinvoice.payment_failedinvoice.upcomingcustomer.subscription.updatedcustomer.subscription.deletedpayment_method.attachedpayment_method.detached
Para probar en local:
stripe listen --forward-to localhost:3000/api/webhooks/stripe-billing
La lógica de negocio de lo que pasa con cada evento va dentro de
stripeBillingService.handleWebhookEvent en service.ts: la única parte
de ese archivo pensada para extenderse a medida que crecen tus necesidades de facturación.
4. Uso
Customer Portal (historial de facturación, facturas, cancelar: todo alojado por Stripe)
import { createCustomerPortalSession } from "@/lib/stripe-billing/actions";
const result = await createCustomerPortalSession({
customerId,
returnUrl: "https://yourdomain.com/account/billing",
});
if (result.success) {
// redirect the user to result.data.url
}
O desde un Client Component, una vez que tenés la URL:
"use client";
import { redirectToCustomerPortal } from "@/lib/stripe-billing/client";
redirectToCustomerPortal(url);
Facturas
import { listInvoices, getInvoice, getInvoiceStatus } from "@/lib/stripe-billing/actions";
const invoices = await listInvoices({ customerId });
const invoice = await getInvoice(invoiceId);
const status = await getInvoiceStatus(invoiceId);
Historial de pagos
import { listPaymentHistory } from "@/lib/stripe-billing/actions";
const history = await listPaymentHistory(customerId);
Métodos de pago
import { listPaymentMethods } from "@/lib/stripe-billing/actions";
const methods = await listPaymentMethods(customerId);
// each entry includes isDefault
Actualizar el método de pago (SetupIntent + Stripe Elements)
Del lado del servidor:
import { createSetupIntent } from "@/lib/stripe-billing/actions";
const result = await createSetupIntent(customerId);
// result.data.clientSecret — pass to your Elements form
Del lado del cliente, después de recolectar los datos de la tarjeta con Stripe Elements:
"use client";
import { confirmCardSetup } from "@/lib/stripe-billing/client";
const result = await confirmCardSetup(clientSecret, paymentMethodId);
Después, terminá definiéndolo como el método por defecto:
import { setDefaultPaymentMethod } from "@/lib/stripe-billing/actions";
await setDefaultPaymentMethod(customerId, result.data.paymentMethodId);
Próxima factura y ciclo de facturación
import { getUpcomingInvoice, getBillingCycle } from "@/lib/stripe-billing/actions";
const upcoming = await getUpcomingInvoice(customerId);
const cycle = await getBillingCycle(subscriptionId);
Manejo de errores
Cada función de actions.ts y client.ts devuelve:
{ success: true, data: T } | { success: false, error: string }
Verificá siempre result.success antes de leer result.data: nada
lanza errores a través del límite del módulo.
Notas
service.tses el único archivo que importa el paquetestripeo leeSTRIPE_SECRET_KEY/STRIPE_BILLING_WEBHOOK_SECRET.- Este módulo no crea ni gestiona suscripciones:
getBillingCyclelee las fechas/el estado de una suscripción existente, no crea una. - Si necesitás buscar el
subscriptionIdde un cliente en tu propia base de datos, esa búsqueda ocurre fuera de este módulo: es específica de tu app, no es una responsabilidad de Stripe Billing.