Puestos extra
Un plan con puestos incluidos y un complemento que vende puestos adicionales.
Para un producto cuyo plan incluye algunos puestos y cobra aparte los que pasen de ahí. El ejemplo es Equipo, con 5 puestos incluidos por 30 USD al mes, y «Puestos extra» a 5 USD por persona.
El modelo
| En tu aplicación | En Billete |
|---|---|
La organización org_7 | Un cliente con externalId: "org_7" |
| Equipo | Un plan con seats: { mode: "included", limit: 5 } y las cuotas |
| Puestos extra | Un plan por puesto, sin cuotas, en otra suscripción del mismo cliente |
| Las personas del equipo | Miembros del cliente |
- Los puestos de un cliente se suman. Billete reparte entre los miembros los puestos de todas las suscripciones del cliente con acceso: 5 de Equipo más los que compre de Puestos extra. Por eso el complemento va en el mismo cliente, como una segunda suscripción.
- El complemento solo da puestos. Sin
features, no cambia lo que el equipo puede usar: eso sigue saliendo de Equipo. - Los dos necesitan límite. Un plan sin
seatstiene puestos ilimitados, y si alguna suscripción da ilimitados, el cliente los tiene todos y el complemento no limita nada.
¿Te conviene un solo plan? Si cada puesto cuesta lo mismo y no hay un precio base, un único plan por puesto, como en planes de equipo, es más simple: una suscripción, un Cobro de renovación y una fecha. Con el complemento, cada suscripción renueva por su lado.
1. Crea los dos planes
const team = await billete.plans.create({
name: "Equipo",
lookupKey: "equipo",
currency: "USD",
prices: [{ months: 1, amount: 3000 }],
seats: { mode: "included", limit: 5 },
gracePeriodDays: 3,
suspensionDays: 30,
features: { mensajes: 10000 },
});
const extraSeats = await billete.plans.create({
name: "Puestos extra",
lookupKey: "puestos-extra",
currency: "USD",
prices: [{ months: 1, amount: 500 }],
seats: { mode: "per_seat", min: 1, max: 100 },
gracePeriodDays: 3,
});Dales los mismos ciclos y la misma gracia para que el equipo no pierda puestos antes que el resto del plan.
2. Vende puestos extra
Ofrécelos solo a quien ya tiene Equipo, desde la pantalla del equipo. El cliente elige cuántos en el checkout:
const checkout = await billete.checkouts.create({
plan: PLANS.extraSeats,
months: 1,
customer: org.id,
successUrl: "https://tuapp.com/equipos/org_7?checkout={CHECKOUT_ID}",
});
return redirect(checkout.url);customer es el externalId del cliente que ya existe. Sin quantity, el cliente elige entre el mínimo y el máximo del plan. Al pagar, sus puestos se suman en el acto y los miembros que esperaban puesto lo reciben.
Úsalo solo la primera vez. Si el equipo ya tiene Puestos extra en curso, otro checkout de ese plan responde subscription_exists al crearlo, sin cobrar nada: cambia esa suscripción (paso 3).
3. Agrega o quita puestos
Desde aquí todo es un cambio sobre la suscripción de Puestos extra:
// 3 puestos más: paga solo lo que queda del periodo, en un checkout.
const result = await billete.subscriptions.changes.create(org.extraSeatsSubscriptionId, {
quantity: 6,
when: "now",
});
if (result.type === "checkout") return redirect(result.checkout.url);
// Menos puestos: desde la próxima renovación.
await billete.subscriptions.changes.create(org.extraSeatsSubscriptionId, {
quantity: 2,
when: "renewal",
});Si la reducción deja menos puestos que miembros activos, falla con seats_in_use: quita antes a quien sobre, o envía disableExcessMembers: true para que se queden sin puesto. Para dejar solo los incluidos, cancela la suscripción de Puestos extra al final del periodo. El portal también deja cambiar los puestos de un plan por puesto.
4. Miembros y prioridad
await billete.members.create(org.id, { externalId: admin.id, name: admin.name, priority: true });
await billete.members.create(org.id, { externalId: user.id, name: user.name });Cuando faltan puestos, primero los conservan los miembros con priority y luego los más antiguos; los demás quedan con enabled: false, registrados, hasta que se liberen puestos. members.list(org.id) devuelve capacity (los puestos del cliente, null si son ilimitados) y enabledCount, para mostrar «7 de 8 puestos en uso».
5. Sigue las dos suscripciones
| Evento | Qué pasó | Qué hace tu aplicación |
|---|---|---|
subscription.created | Pagó Equipo o compró Puestos extra (planId dice cuál). | Guarda el ID de cada suscripción en el equipo. |
subscription.updated | Cambió la cantidad de puestos o se programó una reducción. | Muestra los puestos actuales. |
customer.member_added, customer.member_removed | Entró o salió alguien del equipo. | Actualiza la lista y el contador. |
subscription.suspended, subscription.expired | Equipo dejó de pagarse. | Desactiva el equipo como en planes de equipo. Si Equipo expira, cancela Puestos extra: sin Equipo, esos puestos no dan acceso a nada. |