Suscripciones

Gestiona períodos, renovaciones y cancelaciones.

Iniciar la suscripción

Para cobrar el primer período, crea un checkout de plan. POST /v1/subscriptions crea una suscripción sin cobro: con un plan gratuito queda active (enviar trialDays es trial_not_applicable); con un plan pagado empieza en prueba (sin prueba, trial_required). Acepta quantity en planes por puesto. Un cliente no puede tener dos suscripciones en curso (trialing, active, past_due o suspended) al mismo plan (subscription_exists). A otro plan sí: se crea una segunda suscripción, sin error. Por lo mismo, un cambio hacia un plan en el que el cliente ya tiene otra suscripción en curso también responde subscription_exists, y un checkout de ese plan también, al crearlo con el cliente y al pagarlo, antes de cobrar.

Para una plataforma con organizaciones, cada organización es un cliente con su externalId y una sola suscripción: pasar a pago, cambiar de plan o volver al plan gratuito modifica esa misma suscripción con un cambio, no con una suscripción nueva.

Renovación

Se emite un cobro

Billete prepara el cobro de renovación antes de terminar el período.

El cliente paga

Con Pago Móvil, el cliente inicia cada pago desde la página del cobro. No hay domiciliación automática.

Tu aplicación actualiza el acceso

Escucha subscription.renewed, subscription.past_due y subscription.expired (y, si el plan suspende, subscription.suspended y subscription.reactivated). Durante la gracia puede conservar el acceso; al suspenderse o expirar, se revoca. Un plan gratuito se renueva solo al final de cada período, sin cobro que pagar.

Operaciones

MétodoRutaAcción
POST / GET/v1/subscriptionsCrear / listar suscripciones
GET / PATCH/v1/subscriptions/:subscriptionConsultar / actualizar metadata
POST/v1/subscriptions/:subscription/changes/previewCotizar sin efectos
POST/v1/subscriptions/:subscription/changesAplicar o programar condiciones
POST/v1/subscriptions/:subscription/cancelCancelar
POST/v1/subscriptions/:subscription/resumeReanudar una cancelación programada

La lista filtra por customer (ID de Billete o externalId; si el cliente no existe, customer_not_found), plan, status y search.

La cancelación espera al final del período por defecto. {"atPeriodEnd": false} es inmediata.

Vuelta al plan gratuito

Si el plan tiene un fallbackPlanId válido, la suscripción no termina al cancelarse ni al expirar: sigue active con el mismo ID en el plan gratuito (su ciclo gratuito más corto, un puesto, sin cupón y sin cobro automático), guarda el plan anterior en previousPlanId y emite subscription.updated, no subscription.canceled ni subscription.expired; el cliente recibe un correo con el plan gratuito que conserva. Trata ese subscription.updated con un planId nuevo como la señal para ajustar el acceso. Para terminarla del todo en una sola llamada, por ejemplo al cerrar la cuenta del cliente en tu aplicación, cancélala con {"atPeriodEnd": false, "fallback": false}: termina en el plan que tenía y el correo del cliente lo nombra. fallback: false solo va con atPeriodEnd: false. Si el respaldo dejó de ser válido (eliminado o ya no gratuito), la suscripción termina como siempre; uno desactivado sigue recibiéndola. También termina como siempre si el cliente ya tiene otra suscripción en curso a ese plan gratuito: la conserva, sin una segunda.

Suspensión

Un plan con suspensionDays (7, 14, 30, 60 o 90) no termina la suscripción cuando la gracia acaba sin pago: la pasa a suspended durante esos días y emite subscription.suspended. Sirve, por ejemplo, para un plan de equipo, donde pasar a un plan gratuito personal no tiene sentido y lo que corresponde es desactivar el equipo hasta que pague.

  • Sin acceso. Las cuotas del plan dejan de estar disponibles y sus puestos no cuentan, así que los miembros quedan sin puesto (enabled: false) y no se agregan nuevos (seat_limit_reached).
  • Conserva todo lo demás: el mismo ID, plan, ciclo, puestos, cupón, cambio programado y miembros.
  • El Cobro de renovación sigue abierto. renewal.status es suspended, renewal.suspensionEndsAt dice hasta cuándo se puede pagar y renewal.daysRemaining cuenta los días que faltan. Billete se lo recuerda al cliente al suspenderse, cada semana y a diario los últimos 3 días.
  • Pagarlo la reactiva (subscription.reactivated) con un periodo nuevo que empieza al pagar: no se cobran los días sin acceso. Los miembros recuperan su puesto en su orden de siempre.
  • Si la ventana termina sin pago, pasa a expired, emite subscription.expired y el Cobro queda incobrable.
  • Mientras está suspendida no acepta cambios (subscription_suspended): paga la renovación o cancélala. Cancelarla la termina en el acto, con o sin atPeriodEnd, porque su periodo ya terminó.

Un plan suspende o tiene respaldo, nunca ambos (suspension_with_fallback). Antes de pagar, renewal.suspensionEndsAt también dice hasta cuándo duraría la suspensión.

Cambios y cotizaciones

Envía { plan?, months?, quantity?, when: "now" | "renewal", successUrl?, cancelUrl?, acceptUnusedCredit? } y, si hace falta, endTrial y disableExcessMembers. Tu aplicación decide cuándo; Billete calcula cuánto. renewal programa el cambio para la próxima renovación (type: "scheduled"). now cotiza: si cuesta 0 se aplica en el acto (type: "applied"); si no, devuelve un checkout alojado para el cliente (type: "checkout") y el cambio se aplica solo al confirmarse su pago. PATCH no acepta plan, ciclo ni puestos.

La vista previa recibe { plan?, months?, quantity? } (y endTrial) y devuelve credit, charge, tax, total, unusedCredit, version, currentPeriodEnd y excessMembers (los miembros activos que el cambio dejaría sin puesto); si reinicia el ciclo incluye periodEndIfPaidNow como referencia. Todos los montos son enteros en unidad menor.

Un cambio de plan o ciclo inicia un periodo nuevo al confirmarse; agregar puestos al mismo plan y ciclo conserva el vencimiento y cobra el tramo restante. Bajar puestos con now es quantity_decrease_requires_renewal (salvo en una prueba). Un cambio programado parte del que ya estaba programado: programar menos puestos conserva un ciclo programado antes, y al revés. Mientras esté programada una reducción, las altas respetan la cantidad menor. Un plan que no se cobra por puesto rechaza quantity distinta de 1 (quantity_not_allowed); uno por puesto, una cantidad fuera de su rango (quantity_out_of_range).

Durante una prueba, un cambio now (de plan, de ciclo o de puestos, también a menos puestos) se aplica en el acto, sin costo (type: "applied"), y la prueba sigue: el primer cobro, al terminarla, ya usa las condiciones nuevas. Para terminar la prueba y cobrar las condiciones nuevas desde hoy, envía endTrial: true (solo con now; con renewal es un error de validación). Pasar a un plan gratuito siempre termina la prueba.

Puestos en uso. Ningún cambio, inmediato o programado, deja miembros activos sin puesto: si el plan, el ciclo o los puestos nuevos dan menos puestos que miembros activos, falla con seats_in_use y el mensaje dice cuántos miembros quitar. Para aceptarlo igual envía disableExcessMembers: true: los que se queden sin puesto pasan a enabled: false (primero conservan el suyo los prioritarios y luego los más antiguos) hasta que se liberen puestos. Los miembros que ya estaban sin puesto no frenan un cambio que agrega puestos.

El crédito representa valor pagado, sin IVA y después de cupones y reembolsos, prorrateado hacia abajo. Incluye crédito trasladado de cambios anteriores. Se aplica el cupón permanente al nuevo cargo; un cupón fijo no se descuenta otra vez al comprar puestos. No hay saldo a favor: si unusedCredit > 0, exige acceptUnusedCredit: true (unused_credit_not_accepted).

Un cambio que se paga ahora y empieza un periodo nuevo (de plan o de ciclo, por ejemplo de un plan gratis a uno pago) acepta un cupón del plan nuevo: envía coupon, también en la vista previa para ver el precio, o deja que el cliente lo escriba en el checkout del cambio si tu negocio acepta cupones. El pago del cambio es el primero que descuenta, y el cupón queda como el de la suscripción para sus demás payments, reemplazando el que tuviera. Un cambio programado para la renovación, uno sin costo durante una prueba o uno que solo agrega puestos no lo admiten (coupon_not_applicable).

Ejemplo: el plan nuevo vale 100 y quedan 40 de crédito; el checkout cobra 60. El periodo nuevo vale 100 (40 trasladados más 60 pagados), así que un cambio a mitad de ese periodo da 50 de crédito.

Las cotizaciones vencen a los 60 minutos o al abrir la ventana de renovación (3 días antes del fin del período), lo que ocurra primero. No se crean cambios inmediatos dentro de esa ventana ni en gracia (subscription_renewal_open): paga la renovación o programa el cambio. Un plan gratuito no abre esa ventana y puede cambiar en cualquier momento de su período. Un pago iniciado puede confirmarse durante un máximo de 24 horas con la misma versión comercial. Durante la verificación, otro cambio o una cancelación devuelve subscription_change_in_progress.

Las renovaciones, los vencimientos, las cancelaciones y los reembolsos liquidados invalidan las cotizaciones anteriores. Un pago obsoleto conserva la suscripción, marca checkout.subscriptionChange.outcome: "rejected", emite subscription.change_failed y solicita un reembolso completo. Si el método es manual, el negocio debe efectuarlo y registrarlo; pendiente no significa devuelto.

Configurar los precios y las cuotas del plan.