Cargando…

Esto está tardando más de lo esperado.

Volver al centro de ayuda

Vende consumo con medidores y paquetes

Crea un medidor en el panel de administración, gasta una unidad con una línea de código y deja que la gente compre paquetes por adelantado o pague lo que usó cuando cierra el ciclo.

Un medidor es una unidad que tu proyecto cuenta y cobra: un timbre, un gigabyte, un correo enviado. No es una capacidad que se enciende, así que no es un complemento; es algo que se agota o se acumula. Un medidor tiene tres modalidades que nunca se mezclan. Un medidor prepago vende paquetes de unidades que se compran por adelantado y se gastan hasta que se acaban. Un medidor pospago cuenta lo que se usó durante el ciclo de cobro y factura el excedente en la siguiente factura. Un medidor de capacidad mide un nivel que está ocupado ahora mismo — dispositivos conectados, asientos usados, gigabytes guardados —, que sube cuando se toma algo y baja cuando se libera, y nunca se cobra. Esta guía cubre el camino completo, desde el formulario del panel de administración hasta la línea en la factura.

Enciéndelo

Los medidores viven en el paquete Billing Core junto a los complementos, así que necesitan la misma bandera. Los planes son opcionales: un medidor prepago funciona sin plan y sin suscripción. Stripe sólo hace falta para vender paquetes; los saldos y el consumo funcionan sin él.

Opción Variable de entorno Valor por omisión Qué cambia
Módulo de complementos FEATURE_ADDONS_ENABLED false Apagado: /account/add-ons responde 404, el recurso del panel rechaza toda acción y los dos comandos diarios no se programan.
Stripe STRIPE_KEY, STRIPE_SECRET, STRIPE_WEBHOOK_SECRET vacío Necesarias para vender paquetes y para cobrar el consumo pospago. Cada guardado de un medidor o de un precio habla con Stripe.

La entrada Complementos del menú de la cuenta aparece en cuanto un complemento o un medidor está a la venta, así que un proyecto que sólo vende medidores también tiene la página. La bandera también programa billing:close-meter-periods a diario a las 02:00 y billing:expire-trials a las 08:00. La instalación, las llaves y el webhook están en Enciende los planes y la facturación.

Un medidor se crea en el panel de administración, no en código

No hay nada que declarar en PlanFeatures ni en PlanLimits. Un medidor no enciende nada: cuenta. Lo único que tu código escribe para un medidor es la llamada que gasta una unidad, y esa llamada nombra la llave del medidor.

Ve a Planes → Meter en el panel de administración, en /admin/billing-meters. El índice lista nombre, llave, modalidad y si está a la venta. Crear abre /admin/billing-meters/create; editar es /admin/billing-meters/{id}/edit. El formulario muestra una advertencia cuando STRIPE_SECRET no está definida: los saldos y el consumo siguen funcionando, pero no se puede vender ningún paquete hasta que la llave esté configurada.

La lista de medidores en Plans, Meter

Campo Obligatorio Qué hace
Nombre Sí El título de la tarjeta de saldo. También el nombre del producto en Stripe.
Clave Sí Lo que tu código nombra cuando gasta una unidad. Sigue al nombre mientras creas el medidor (Timbres se vuelve timbres), puedes editarla antes de guardar y nunca cambia después: el campo queda deshabilitado al editar y un cambio de llave se rechaza. Minúsculas, dígitos y guiones bajos, única.
Unidad (singular), Unidad (plural) Sí timbre / timbres. Toda frase sobre el saldo las usa. Escríbelas en inglés (stamp / stamps), como indica la nota bajo cada campo: la versión en español sale de los archivos de idioma, así que agrega ahí una entrada para cada nombre. Un nombre sin entrada se acepta igual y se muestra tal como lo escribiste.
Descripción No Se muestra al usuario junto al saldo.
Modo Sí Prepago: paquetes de unidades comprados por adelantado o Pospago: el uso se factura cuando termina el período. Una u otra, nunca las dos. Cambiarlo cambia el resto del formulario.
A la venta Sí Encendido por omisión. Apagado retira el medidor del catálogo: ya nadie puede comprarlo y sale del selector de límites. Lo que ya se compró no se toca, así que una cuenta con saldo sigue viendo el medidor en su bloque de saldo y en la página de detalle, y lo sigue gastando con consume(). Un medidor pospago no tiene saldo comprado que gastar, así que apagarlo detiene la medición: no se abre ningún periodo nuevo ni se cobra ninguno. Vuelve a ponerlo a la venta y se ofrece otra vez.
Contexto Sólo con dos adaptadores Ambos, Usuario o Equipo. Oculto cuando sólo está registrado el adaptador user.

Prepago: paquetes y qué pasa al llegar a cero

Campo Qué hace
Paquetes Un renglón por país, moneda y paquete: las unidades que otorga y su precio. Cada renglón se vuelve un precio de pago único en Stripe.
Bloquea al llegar a cero Encendido: la operación se detiene hasta que se compra un paquete o se reinicia la cuota del plan. Apagado: la operación pasa y el saldo sólo informa.
País Moneda Unidades Precio
default mxn 100 350.00
default mxn 500 1,200.00

Pospago: un precio por unidad y un aviso

Campo Qué hace
Precio por unidad Un renglón por país y moneda. Lo que cuesta cada unidad por encima de la cuota incluida.
Avisar a partir de El cargo estimado a partir del cual se le avisa al usuario, una vez por periodo, que su consumo está creciendo. Déjalo vacío para no avisar nunca.

No hay paquetes, porque nada se compra por adelantado, y no hay día de corte, porque el periodo es el ciclo de la suscripción.

Retirar un medidor

Un medidor sólo se puede eliminar mientras no tenga precios y nadie haya recibido saldo; eliminarlo archiva su producto en Stripe. En cuanto se vendió algo, apaga A la venta en su lugar.

Da una cuota con los límites del plan

Ninguna modalidad captura cuánto incluye un plan. Eso se define donde ya están los límites: en los Límites del plan y en los Límites de un complemento, eligiendo la llave del medidor. El selector de límites de ambos formularios lista las llaves de los medidores después de las que declara PlanLimits. Un plan con timbres = 50 da cincuenta timbres por ciclo; un complemento con timbres = 100 suma cien mientras se tenga. Los límites de plan y de complemento bajo la misma llave se suman, exactamente como en Límites del plan y consumo.

Mide una capacidad en lugar de un consumo

Una capacidad es la tercera clase de medidor, y se comporta al revés: no se gasta, se ocupa. Dispositivos conectados, asientos tomados, gigabytes guardados. El número sube cuando tu cliente toma algo y vuelve a bajar cuando lo libera, y nunca se cobra: el plan ya vendió el espacio.

Por eso un medidor de capacidad no vende paquetes, no abre periodos y rechaza consume(). No tiene precios propios. Lo que permite un plan o un complemento es un límite, escrito donde se escriben todos los demás.

El kit ya trae uno: disk_space, medido en gigabytes —o en megabytes cuando tu PlanLimits declara $diskSpaceUnit = 'megabyte'—, creado por MeterSeeder para que un proyecto nuevo lo tenga sin que nadie lo capture. No lleva precio ni cuota de plan: cuánto incluye cada plan, y cuánto cuesta el espacio extra, son tuyos de capturar como cualquier otra cifra comercial.

Lo que sí necesita un medidor es de dónde leer el nivel, y esa parte es tuya. Agrega a App\Classes\PlanLimits un método público con el mismo nombre que la clave del medidor:

class PlanLimits extends BasePlanLimits
{
    public function devices($owner = null)
    {
        $owner = $owner ?? auth()->user();
        return $owner ? Session::where('user_id', $owner->id)->count() : 0;
    }
}

No se guarda nada y no hay que mantener nada sincronizado: el método se lee en vivo cada vez que una pantalla muestra el número. Una capacidad que nadie declara sigue mostrando lo que el plan vendió, pero el uso a su lado siempre marca cero, y el panel de administración te lo dice mientras la configuras.

Con el mismo nombre que la clave incluye los guiones bajos. Un medidor con la clave disk_space se mide con un método llamado disk_space; diskSpace es otro nombre, así que el medidor no encuentra nada, el uso marca cero para todos, y en ningún lado se levanta un error que lo explique. Una clave de una sola palabra esconde esto; tu segundo medidor no.

Tu cifra no tiene que estar en la unidad del medidor. El espacio en disco se cuenta en bytes y se vende en la unidad que declara tu PlanLimits, gigabytes salvo que diga megabytes, así que el método divide antes de responder, redondeando hacia abajo para que un solo byte no se cobre como una unidad completa. En Límites del plan y consumo está qué hacer cuando ese redondeo vuelve imposible un aviso en una cuota pequeña.

Una línea más decide qué se le dice a tu cliente cuando el nivel está lleno:

public static function releasableLimits(): array
{
    return ['devices'];
}

Una clave que esté ahí es una que tu cliente puede bajar por su cuenta, así que la tarjeta le ofrece las dos salidas: liberar algo o comprar más capacidad. A una clave que no esté sólo se le ofrece la compra. Si mides algo que únicamente crece —un registro de actividad, un historial—, déjala fuera: decirle a alguien que libere espacio cuando no hay forma de liberarlo lo manda a buscar un botón que no existe, y deja como única salida la que cuesta dinero.

Gasta una unidad desde tu código

$result = $account->consume('timbres', 1, "CFDI {$invoice->folio}");
if (! $result->success) {
    return $result->message;
}

$account es la cuenta de facturación, $user->billingAccount. La firma es consume(string $key, float|int $units = 1, ?string $reason = null) y nunca lanza excepciones; devuelve un resultado que tú lees.

Propiedad Qué trae
success Si las unidades se gastaron, o se registraron en un medidor pospago.
message Vacío cuando sale bien. Al fallar, el texto para mostrar: por qué se rechazó y qué hacer.
remaining Prepago: unidades que quedan después de la llamada. Pospago: unidades que quedan dentro de la cuota incluida antes de que el medidor empiece a cobrar.
meter El medidor, o null cuando la llave no correspondió a ninguno.

Lo que pasa depende del medidor:

  • Una llave que no corresponde a ningún medidor falla de forma ruidosa: se escribe un error en el log con el id de la cuenta y la llave, y el mensaje dice que el medidor no existe. Tu código nunca puede descontar en silencio de un saldo que no existe. Estar fuera de venta no es lo mismo que no existir: un medidor retirado sigue respondiendo a su llave y su saldo se sigue gastando. Una cantidad de cero o menos también se rechaza.
  • Un medidor prepago corre una sola transacción con candado. Acredita la cuota del plan del ciclo si todavía no se había hecho, bloquea los lotes que aún tienen unidades, ordenados para que los que vencen primero vayan al frente y los comprados al final, y revisa el total. Si no alcanza y el medidor bloquea al llegar a cero, la llamada falla sin descontar nada, con un mensaje que dice cuánto se usó en este ciclo, cuándo se reinicia la cuota del plan e invita a comprar un paquete. Si alcanza, las unidades se toman de los lotes y se asienta un movimiento con el motivo. El saldo nunca es negativo: con el bloqueo apagado, el movimiento registra la cantidad completa y los lotes se detienen en cero. Dos llamadas al mismo tiempo sobre el mismo saldo son el caso cotidiano de un sistema de timbrado, y el candado es lo que impide que pasen las dos cuando sólo cabe una.
  • Un medidor pospago siempre sale bien. Registra el movimiento, mantiene abierto el periodo actual y manda el aviso de consumo si esta llamada hizo que el estimado cruzara Avisar a partir de. El pospago nunca bloquea.

Cuenta desde donde se escribe la cosa, y di por qué

Llama a consume() desde el lugar donde se crea lo que se cuenta, una vez por cosa, y nunca derives el saldo de tu propia tabla. Un medidor que contara filas subiría cuando las filas se borren o se cancelen, y no podría tomar candado sobre nada. La bitácora de movimientos que guarda el paquete es la única fuente de consumo: el cierre de periodo la suma, las pantallas la listan y un reclamo se resuelve leyéndola.

El motivo que mandas en cada llamada es lo que después explica un cargo al cliente. CFDI A-1042 le dice qué factura costó un timbre; Recordatorio de pago le dice qué correos formaron el excedente. Un motivo vacío convierte el historial en una lista de números, y el desglose del pospago lo agrupa bajo Otro.

Cómo funciona el saldo

Un saldo prepago no es un contador. Cada acreditación crea un lote con las unidades otorgadas, las que quedan, de dónde vino y, cuando lo tiene, su vencimiento:

Origen Lo crea Vence
Compra Un paquete pagado Nunca
Plan La cuota que incluye la suscripción Al final del ciclo
Manual creditMeter() desde tu código Cuando tú digas

El consumo gasta primero el lote que vence más pronto, así que la cuota del plan se usa antes que cualquier cosa pagada, y el usuario nunca pierde una unidad comprada por no haber gastado antes las incluidas.

La cuota del plan no la acredita ningún proceso. Cae la primera vez que el saldo se consulta o se consume dentro de un ciclo nuevo, como un lote por ciclo, y volver a consultarlo en el mismo ciclo no acredita nada más. La cuota es getLimitTotal() para la llave del medidor, así que los límites de plan y de complemento se suman. Una cuenta sin suscripción activa no tiene ciclo y no recibe cuota: sólo tiene lo que compró.

Un medidor prepago es independiente de la suscripción. Una cuenta en un plan gratis, o sin ninguno, compra paquetes, los gasta y ve su saldo exactamente como un suscriptor. Cancelar la suscripción no toca las unidades compradas: ya están pagadas.

Método Devuelve
$account->meterBalance('timbres') available, purchased, plan, plan_resets_at, used_this_cycle. Acredita primero la cuota del plan.
$account->meterUsage('correos') Para un medidor pospago: used, included, billable, unit_price, amount, currency, remaining, breakdown por motivo, charges_at, has_subscription.
$account->meterMovements('timbres') Una consulta con cada acreditación y cada consumo, del más reciente al más antiguo.
$account->creditMeter($key, $units, $source, $reference = null, $expiresAt = null) Agrega un lote y un movimiento positivo. El mismo origen y la misma referencia dos veces devuelven el lote que ya creó.
$account->meterExhaustedMessage('timbres') El mensaje que muestra un medidor que bloquea cuando se agota.

Vende paquetes

Los paquetes se compran con el mismo Checkout de pago único que usa un complemento de por vida.

  1. El usuario pulsa Comprar más timbres en la tarjeta de saldo y elige un paquete. Se abre una sesión de Stripe Checkout en modo pago para ese precio, y la sesión se registra antes de que el usuario se vaya. Una sesión pendiente abierta hace menos de un día se reutiliza, así que recargar o pulsar dos veces nunca abre otra.
  2. Stripe regresa al usuario a /account/add-ons?meter=timbres&checkout=pending, donde el bloque dice que el pago se está procesando. Nada se acredita al volver.
  3. El webhook checkout.session.completed confirma el pago. La sesión se marca como pagada y las unidades caen como un lote de compra que nunca vence, con el id de la sesión como referencia.

Una sesión ya pagada responde sin entregar de nuevo, y un lote es único por su referencia, así que un webhook que llega dos veces acredita una. Sin STRIPE_SECRET, o en un paquete sin precio de Stripe, el botón dice que las compras todavía no están disponibles.

Cobra el consumo cuando cierra el ciclo

billing:close-meter-periods corre a diario a las 02:00, y puedes correrlo a mano.

El periodo es el ciclo de la suscripción: a quien se le cobra el 15 le llega el consumo el 15 siguiente, como una línea más de la factura que ya espera. Un plan anual cierra cada mes, en meses anclados al día en que empezó el ciclo, porque un año de consumo cobrado de golpe no se puede defender. Se guarda una fila por cuenta, medidor y periodo, y una corrida que encuentra la fila ya cobrada la deja en paz: volver a correr el comando nunca cobra dos veces.

Lo que hace una corrida con cada periodo que terminó:

  1. Calcula las cifras una sola vez: las unidades usadas, sumadas de los movimientos de la ventana; las unidades incluidas, de los límites de plan y de complemento de la suscripción activa; el precio unitario del país de la cuenta; y el importe. Una fila que ya tiene importe nunca se recalcula.
  2. Ningún precio aplica: el medidor tiene precios, pero ninguno para el país de la cuenta ni uno por defecto, y se usaron unidades por encima de lo incluido. El periodo se cierra como no facturado.
  3. Importe cero: el periodo se cierra sin cargo.
  4. La cuenta no tiene cliente de Stripe: no hay en qué cobrarlo, así que el periodo se cierra como no facturado. Cerrarlo sin facturar solo escribe ese estado: no se envía ningún aviso a nadie, y al usuario nunca se le bloquea por eso, porque el pospago no bloquea.
  5. Plan anual, o una suscripción que terminó después de que empezó el periodo: el importe se cobra en su propia factura. Cualquier otro ciclo: se agrega como línea a la suscripción, para que vaya en la siguiente factura.
  6. Un error de Stripe marca el periodo como fallido con el motivo y le arranca un plazo propio. Las siguientes corridas lo reintentan con el importe que ya tiene, mientras BILLING_PAST_DUE_GRACE_DAYS lo permita — quince días desde esa falla, el mismo plazo que recibe una renovación fallida. Cumplido, el periodo deja de reintentarse: su importe pasa al adeudo pendiente de la cuenta y se le avisa una vez al usuario. Un periodo que cierra sin suscripción en la que cobrarlo pasa al adeudo de inmediato, porque no hay nada que esperar.

La línea de la factura dice Correos enviados: 1,120 correos (1 de septiembre de 2026 - 1 de octubre de 2026).

El aviso de consumo. Mientras el periodo está abierto, cada consumo recalcula el cargo estimado. La primera vez que alcanza Avisar a partir de, el usuario recibe una notificación con las unidades usadas, el estimado y la fecha en que se cobrará. Va una vez por periodo.

Lo que ve el usuario

/account/add-ons abre con el bloque Tu saldo arriba del catálogo, una tarjeta por cada medidor que puedes ver: los que están a la venta, más aquellos en los que todavía tienes saldo:

  • Una tarjeta prepago muestra las unidades disponibles en grande, luego N comprados, sin vencimiento, N incluidos en tu plan, se reinician el {fecha} y N usado este ciclo. Cuando el medidor bloquea y el saldo está en cero, una advertencia trae el mensaje de saldo agotado. Comprar más timbres despliega los paquetes con su precio; Ver movimientos abre el detalle.
  • Una tarjeta pospago muestra Usado este ciclo, Incluido en tu plan, la línea de excedente 1,120 × $0.10 = $112.00 MXN y Se facturará el {fecha} junto con tu suscripción, o, sin suscripción activa, El período cierra el {fecha}. No hay ninguna suscripción activa a la que facturarlo. Abajo, Desglose de uso lista las unidades por motivo.

/account/add-ons/meters/{llave} muestra la misma tarjeta y la tabla Movimientos: fecha, unidades (negativas para el consumo), motivo y tipo (Consumo, Compra de paquete, Incluido en tu plan, Crédito manual), veinte por página. Es la página que resuelve un reclamo sin abrir la base de datos.

En el panel de administración, al editar un usuario en /admin/users/{id}/edit aparece un panel de sólo lectura Meters con una frase por medidor: las unidades disponibles con lo comprado y lo que incluyó el plan, o las unidades usadas, incluidas y cobrables con el importe y su fecha.

Tres ejemplos reales

Timbres, prepago. Un medidor timbres, unidades timbre / timbres, paquetes de 100 por 350 MXN y de 500 por 1,200 MXN, con bloqueo en cero. El plan Pro define el límite timbres = 50. Un suscriptor tiene cincuenta timbres que se reinician cada ciclo más lo que compre; alguien sin plan compra un paquete y timbra hasta que se acaba. Al emitir el timbre corre la línea de arriba, y el mensaje al fallar ya dice cuánto se usó, cuándo se reinicia la cuota y que se puede comprar un paquete.

Espacio en disco, una capacidad que el kit trae. El medidor disk_space, unidades gigabyte / gigabytes, nada que se gaste y nada que se venda en el medidor mismo. El plan captura disk_space = 100 en sus medidores incluidos y el cliente lee "100 gigabytes incluidos". El espacio extra es un complemento vendido por escalones —1 GB, 5 GB, 20 GB— donde los gigabytes de cada opción viven en la columna Units de su renglón de precio, no en el valor de su renglón de límite; el cliente elige el tamaño en la página del complemento, no en el catálogo. Esa parte está en Vende complementos. Subir se rechaza en cuanto la cuenta llega a su total, en todas las pantallas que suben, y la tarjeta le ofrece las dos salidas porque disk_space está declarada liberable: borrar archivos, o comprar más.

Correos, pospago. Un medidor correos, 0.10 MXN por unidad y un aviso desde 100 MXN. El plan puede incluir mil o nada. Cada envío llama a $account->consume('correos', 1, 'Recordatorio de pago') y nada se rechaza. Al cierre, las unidades por encima de la cuota se cobran a 0.10 como una línea de la factura, y el desglose dice Recordatorio de pago 1,340 · Bienvenida 420 · Notificación de factura 360.

Cuando algo sale mal

  • Falló el cobro de un periodo. El periodo aparece como fallido con el error de Stripe. Corrige la causa, normalmente la tarjeta o el cliente en Stripe, y la siguiente corrida de billing:close-meter-periods lo reintenta con el mismo importe. Cada periodo fallido lleva su propio plazo, contado desde el día en que falló por primera vez, así que uno que falla tarde recibe la ventana entera y no lo que quedaba de la de otro. Cumplido el plazo, el importe queda en el adeudo pendiente de la cuenta y el periodo deja de leerse como un pago fallido. Pagar ese adeudo liquida todos los periodos que lo formaban: quedan como cobrados, contra la factura que los pagó.
  • Un periodo cerró sin facturar. Cuando el periodo cerró, la cuenta no tenía cliente de Stripe, o el medidor no tenía precio para el país de la cuenta ni uno por defecto mientras la cuenta usó más de lo incluido. Las cifras se conservan y no se avisó a nadie: el periodo solo lleva ese estado, y el paquete no lo cobra después por su cuenta. Una cuenta obtiene un cliente de Stripe cuando paga un plan o guarda una tarjeta; el precio que falta se agrega en el medidor, para ese país o como precio por defecto. Las dos cosas arreglan los periodos siguientes, no el que ya cerró.
  • Un medidor que nadie consume. Se puede configurar y vender, pero su saldo nunca baja, porque un medidor sólo cuenta lo que tu código le avisa. Una llamada con una llave que no corresponde a ningún medidor falla y queda en el log, así que se encuentra en el log y no en un cero silencioso.
  • Un webhook llega dos veces. El id de la sesión encuentra el checkout registrado; uno ya pagado no entrega nada de nuevo, y el lote detrás es único por referencia. Reenviar el evento desde el panel de Stripe es seguro.
  • Cambiaste un medidor de prepago a pospago. No lo hagas con gente que ya compró, pero no se pierde nada si lo haces: los lotes comprados se siguen gastando primero, y sólo lo que pasa de ellos se cobra.