Autoservicio del cliente

Deja que tus clientes paguen sus renovaciones, cancelen o cambien sus puestos con el portal de Billete o con tus propias pantallas.

Para un producto con suscripciones donde el cliente debe poder administrar lo que paga sin escribirte. Hay dos formas: enviarlo al portal alojado de Billete, que no requiere construir nada, o mostrar lo mismo dentro de tu aplicación con el cliente público.

El modelo

NecesitasEn Billete
Un botón «Administrar suscripción»Una sesión de portal (portalSessions.create) y una redirección a su url
Las mismas acciones en tus pantallasUna sesión de cliente (customerSessions.create) y el cliente público (client.customer)
Saber qué hizo el clienteLos webhooks de la suscripción en tu servidor
  • Lo que el cliente puede hacer lo decide el negocio, en el dashboard, en Configuración → Portal del cliente: ver el historial de cobros, cancelar y reanudar (y preguntar por qué cancela), cambiar el ciclo, cambiar los puestos y desactivar el cobro automático. Tus propias pantallas siguen las mismas reglas: una acción desactivada responde portal_action_disabled.
  • Pagar siempre se puede. Los cobros abiertos aparecen siempre, aunque el historial esté desactivado, y una renovación pendiente muestra «Pagar renovación» (o «Pagar y reactivar» si está suspendida).
  • Cambiar de plan no está en el portal. Subir o bajar de plan lo hace tu aplicación con un cambio, como en planes personales.
  • Tu servidor elige el cliente. Las dos sesiones se crean a partir del usuario que inició sesión en tu aplicación, nunca con un ID que envíe el navegador. En un plan de equipo, el cliente es la organización: abre el portal solo a quien la administra.

1. Abre el portal alojado

// Ruta de tu servidor, detrás de tu inicio de sesión.
const portal = await billete.portalSessions.create({
  customer: user.id, // tu externalId o el ID cus_…
  returnUrl: "https://tuapp.com/ajustes",
});

return redirect(portal.url);

El enlace se abre una sola vez y vence a los 60 minutos: créalo en cada clic y no lo guardes para reutilizarlo. El botón «Volver» del portal lleva a returnUrl.

Esto es lo que el cliente puede hacer ahí y lo que llega a tu servidor:

En el portalQué pasaEvento
CancelarLa suscripción termina al final del periodo pagado (cancelAtPeriodEnd). Una suspendida termina en el acto.subscription.updated; al terminar, subscription.canceled, o subscription.updated si el plan tiene respaldo. Una suspendida: subscription.canceled.
ReanudarQuita la cancelación programada.subscription.updated
Cambiar cicloEl ciclo nuevo rige desde la próxima renovación (pendingChange).subscription.updated
Cambiar puestosMás puestos se pagan ahora, solo por lo que queda del periodo, en un checkout que vuelve a returnUrl; menos puestos rigen desde la renovación. Durante una prueba, ambos en el acto.subscription.updated
Desactivar cobro automáticoLas renovaciones dejan de cobrarse solas y se pagan desde su cobro (autoCharge: null).subscription.updated
Pagar la renovaciónAbre la página del cobro.invoice.paid y subscription.renewed, o subscription.reactivated si estaba suspendida.
Abrir un reclamoSobre un cobro pagado.claim.created

Quitar puestos que están en uso responde seats_in_use: primero tu aplicación saca a esos miembros del equipo. Como con cualquier webhook, ante cualquiera de estos eventos consulta la suscripción y guarda su estado actual (webhooks sin perder ni duplicar).

2. O construye tus propias pantallas

Tu servidor crea una sesión de cliente para el usuario que inició sesión y le entrega solo el token:

const session = await billete.customerSessions.create({ customer: user.id });
return { token: session.token };

En el navegador o en tu app móvil, el cliente público lee lo mismo que muestra el portal, incluido lo que el negocio permite:

import { createClient } from "@boelabs/billete-sdk/client";

const client = createClient({ apiVersion: "2026-10-24", customerSession: () => currentToken });

const { features, subscriptions, invoices } = await client.customer.portal();
const [subscription] = subscriptions;

Con eso armas la pantalla: el plan, renewal (cuánto, cuándo vence y invoiceUrl para pagar), cancelAtPeriodEnd y pendingChange. Cada acción está en client.customer:

if (features.cancel) {
  await client.customer.cancelSubscription(subscription.id, { feedback: "too_expensive" });
}

const result = await client.customer.changeSeats(subscription.id, { quantity: 8 });
if (result.type === "checkout") window.location.assign(result.checkout.url);

También están resumeSubscription, updateSubscription (el ciclo, con months), stopAutoCharge, openClaim y features para mostrar las cuotas y su uso. Para que el cliente pague una renovación sin salir de tu aplicación, el cobro (renewal.invoiceId) se paga con client.invoices igual que un checkout propio.

La sesión dura 60 minutos por defecto (expiresInMinutes, hasta 24 horas). Cuando vence, las llamadas responden customer_session_invalid: pide a tu servidor un token nuevo. Por eso customerSession acepta una función que devuelve el token vigente.

Lo que muestres en estas pantallas es para el cliente. Para dar o quitar acceso, tu servidor consulta la suscripción y las cuotas con su clave, no lo que dice el navegador.

Portal del cliente · Sesiones de cliente · Cliente de navegador · Suscripciones