@dariomvg/create
stripebillingfacturacioncustomer-portalintegracion

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 en lib/stripe-billing/service.ts
  • @stripe/stripe-js — SDK del lado del cliente, usado en lib/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á:

VariableDónde conseguirla
STRIPE_BILLING_WEBHOOK_SECRETDashboard → 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.paid
  • invoice.payment_failed
  • invoice.upcoming
  • customer.subscription.updated
  • customer.subscription.deleted
  • payment_method.attached
  • payment_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.ts es el único archivo que importa el paquete stripe o lee STRIPE_SECRET_KEY / STRIPE_BILLING_WEBHOOK_SECRET.
  • Este módulo no crea ni gestiona suscripciones: getBillingCycle lee las fechas/el estado de una suscripción existente, no crea una.
  • Si necesitás buscar el subscriptionId de 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.