Créditos por consumo
Mensajes, tokens o proyectos limitados por plan, con el saldo a la vista y qué hacer al llegar al límite.
Para un producto que limita cuánto se usa en cada plan, como una aplicación de IA con mensajes al mes. El ejemplo tiene mensajes que se reponen cada mes, tokens para trabajos largos y proyectos que el usuario crea y borra.
El modelo
| En tu aplicación | En Billete |
|---|---|
| Mensajes del mes | Una cuota metered con reset |
| Tokens de un trabajo largo | La misma cuota metered, con una reserva que se confirma al terminar |
| Proyectos activos | Una cuota count: sube al crear, baja al borrar y nunca se reinicia |
| El límite de cada plan | features del plan: un número, o null para ilimitado |
- Billete lleva la cuenta. Tu aplicación no guarda contadores: pide consumir antes de trabajar y Billete responde si cabe, de forma atómica. Dos peticiones a la vez nunca pasan juntas el límite.
- Elige bien cuándo se repone.
reset: { every: "billing" }sigue el periodo de cada suscripción: con un plan anual, los mensajes se reponen una vez al año. Si quieres una asignación mensual también en el anual, usa un calendario:{ every: "month", interval: 1, day: 1, hour: 0 }repone a todos el día 1, a medianoche en la zona horaria del negocio. - Los límites no se suman. Si un cliente tiene varias suscripciones, recibe el mayor límite que le da alguna. Para dar más, se cambia a un plan con más cuota.
1. Crea las cuotas y ponlas en los planes
await billete.features.create({
key: "mensajes", name: "Mensajes", type: "metered", unit: "mensajes",
reset: { every: "month", interval: 1, day: 1, hour: 0 },
});
await billete.features.create({
key: "tokens", name: "Tokens", type: "metered", unit: "tokens", reset: { every: "billing" },
});
await billete.features.create({ key: "proyectos", name: "Proyectos", type: "count" });
await billete.plans.create({
name: "Pro",
currency: "USD",
prices: [{ months: 1, amount: 2000 }, { months: 12, amount: 20000 }],
features: { mensajes: 3000, tokens: 2_000_000, proyectos: null },
});La clave y el tipo no cambian después de crear la cuota. Cambiar los features de un plan cambia en el acto las cuotas de todos sus suscriptores. En un plan de equipo, crea la cuota con per: "member" para que cada persona tenga su propio límite y pasa { member: user.id } al consumir y consultar.
2. Consume antes de responder
const { allowed, feature } = await billete.features.consume(user.id, "mensajes", 1, {
idempotencyKey: `mensaje-${message.id}`,
});
if (!allowed) return showLimitReached(feature);
await answer(message);Si no cabe, no se descuenta nada y feature trae lo que le queda. La clave de idempotencia hace seguro reintentar si se pierde la respuesta.
3. Reserva los trabajos largos
Cuando no sabes cuánto gastará un trabajo, reserva un máximo, ejecútalo y confirma lo que usó: el resto vuelve a la cuota.
const { allowed, operation } = await billete.features.reserve(user.id, "tokens", 50_000, {
operation: job.id, expiresInSeconds: 600,
});
if (!allowed || !operation) return showLimitReached();
if (operation.status !== "reserved") return; // Otro intento ya lo cerró.
const result = await runJob(job).catch(async (error: unknown) => {
await billete.features.operations.release(operation.id);
throw error;
});
await billete.features.operations.commit(operation.id, { amount: result.tokensUsed });operation es tu ID del trabajo, así que repetir la reserva no consume dos veces. Si una confirmación falla por la red, reinténtala; no liberes por una duda. Detalles en reservas de consumo.
4. Cuenta lo que el usuario tiene
// Al crear un proyecto.
const { allowed } = await billete.features.consume(user.id, "proyectos", 1);
// Al borrarlo.
await billete.features.release(user.id, "proyectos", 1);
// Si dudas del contador, fíjalo con lo que hay en tu base.
await billete.features.set(user.id, "proyectos", await db.projects.count(user.id));Si el usuario baja a un plan con menos proyectos de los que tiene, conserva los suyos: consume responde allowed: false hasta que borre los que sobran. Qué hacer con los existentes lo decide tu aplicación.
5. Muestra lo que le queda
const mensajes = await billete.features.check(user.id, "mensajes");
// { limit: 3000, used: 2840, remaining: 160, resetsAt: "2026-11-01T04:00:00.000Z", … }remaining y limit son null si es ilimitado. Desde el navegador, con una sesión de cliente, client.customer.features() devuelve lo mismo sin pasar por tu servidor. Billete no emite eventos de consumo: consulta al mostrar la pantalla.
6. Al llegar al límite
Muéstrale cuándo se repone (resetsAt) y ofrécele un plan con más cuota con un cambio: el límite nuevo rige al confirmarse el pago. Con reset: { every: "billing" }, un cambio de plan empieza un periodo nuevo y con él un consumo nuevo; con un calendario, el consumo del mes sigue contando contra el límite nuevo.
Billete no vende recargas sueltas: una cuota solo crece con el plan. Si quieres vender paquetes de mensajes, véndelos como un producto con un checkout, suma lo comprado a un saldo en tu base al recibir checkout.completed y gástalo cuando consume responda allowed: false. Ese saldo lo llevas tú.