Cargando…

Esto está tardando más de lo esperado.

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 Activa los planes y el cobro.

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 clave del medidor. Si prefieres no capturar el medidor a mano en cada entorno, decláralo en el seeder de medidores; consulta Declara un medidor en código.

Ve a Planes → Medidores en el panel de administración, en /admin/billing-meters. El índice lista nombre, clave, modo 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 Planes, Medidores

Campo Obligatorio Qué hace
Nombre Sí El título de la tarjeta de saldo. También el nombre del producto en Stripe, una vez que el medidor tiene un precio de pago: un medidor sin ningún precio mayor a cero no tiene producto en Stripe, y un precio de cero nunca se vuelve un precio en Stripe.
Clave Sí Lo que tu código nombra cuando gasta una unidad. Sigue al nombre mientras creas el medidor (Stamps se vuelve stamps), puedes editarla antes de guardar y nunca cambia después: el campo queda deshabilitado al editar y un cambio de clave se rechaza. Minúsculas, dígitos y guiones bajos, única.
Unidad, Unidad (plural) Sí stamp / stamps. Toda frase sobre el saldo las usa. Escríbelas en inglés, 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, Pospago: el uso se factura cuando termina el período o Capacidad: un nivel en uso que sube y baja y nunca se cobra. Uno de los tres, nunca 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 su página de detalle, y sigue gastando ese saldo con consume(). Un medidor pospago no tiene saldo comprado que gastar, así que retirarlo de la venta 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 a nadie se le haya abonado nada; eliminarlo archiva su producto en Stripe. En cuanto se vendió algo, apaga A la venta en su lugar.

Declara un medidor en código

En lugar de capturar un medidor y sus paquetes a mano en cada entorno, puedes describirlos una vez en database/seeders/MeterSeeder.php. El deploy ya corre los seeders (php artisan db:seed --force), así que el medidor se crea en el primer deploy y se mantiene al día en cada uno después. Cada entrada va por la clave del medidor:

$meters = [
    'stamps' => [
        'name' => 'Stamps',
        'description' => 'CFDI stamps for your invoices.',
        'unit_singular' => 'stamp',
        'unit_plural' => 'stamps',
        'mode' => 'prepaid',
        'blocks_at_zero' => true,
        'packages' => [
            ['country' => 'mx', 'currency' => 'mxn', 'units' => 100, 'amount' => 250],
            ['country' => 'mx', 'currency' => 'mxn', 'units' => 500, 'amount' => 750],
        ],
        'defaults' => ['is_active' => true],
    ],
    'emails' => [
        'name' => 'Emails',
        'unit_singular' => 'email',
        'unit_plural' => 'emails',
        'mode' => 'postpaid',
        'unit_prices' => [
            ['country' => 'mx', 'currency' => 'mxn', 'amount' => 0.10],
        ],
        'defaults' => ['is_active' => true],
    ],
];

name, description, unit_singular, unit_plural, blocks_at_zero, threshold_amount (el aviso de un medidor pospago), adapter (el contexto, null para ambos), packages y unit_prices se mantienen al paso del código. Una clave que dejas fuera se queda como está. Cada paquete es una cantidad de unidades y su precio en un país y una moneda, así que un medidor puede venderse sólo en pesos. Sólo un medidor prepago vende paquetes: una entrada con paquetes en un medidor pospago o de capacidad se omite y el deploy dice por qué. unit_prices es el Precio por unidad de un medidor pospago, un renglón por país y moneda, y sólo un medidor pospago lo acepta. defaults lleva lo que sigues decidiendo en el panel, como A la venta, y sólo se aplica cuando se crea el medidor.

mode se fija al crear el medidor y no cambia después: una entrada que más tarde declara otro modo conserva el guardado, y el deploy imprime una advertencia. Cambiarlo bajo saldos ya comprados no sería seguro.

Volver a correr el seeder no duplica nada. Un paquete cuyo monto no cambió conserva su precio en Stripe; un monto distinto lo reemplaza como lo haría quitarlo y escribir el nuevo; un paquete que sale de la entrada se retira, y 'packages' => [] los retira todos. El precio por unidad sigue las mismas reglas, y 'unit_prices' => [] lo retira. Los saldos ya comprados nunca se tocan: viven en los lotes del cliente, no en los precios.

Lo que el panel te deja cambiar

Un medidor creado por el seeder abre con un aviso Controlado por código. Su nombre, descripción, unidades, modo, contexto, aviso, Bloquea al llegar a cero, paquetes y precio por unidad son de sólo lectura ahí, y cada deploy los regresa a lo que dice el código. A la venta sigue editable.

Libéralo del código

Cuando quieras manejar un medidor a mano de ahí en adelante, pulsa Liberar del código en el aviso y confirma. Todos los campos se vuelven editables y el seeder no vuelve a tocar ese medidor, aunque su entrada siga en el código. Esto no se puede deshacer desde el panel.

Pasar a código un medidor que ya existe

Un medidor que ya existe con la clave de la entrada se toma sólo si todo lo que la entrada declara coincide con lo guardado: los textos, las unidades, el modo, el aviso, Bloquea al llegar a cero, el contexto y los mismos paquetes o el mismo precio por unidad, sin ningún otro precio guardado. Entonces abre con el aviso Controlado por código desde el siguiente deploy, sin tocar sus paquetes ni sus saldos, y defaults no se aplica. Si algo difiere, se deja en paz y el deploy imprime qué campos difieren. Un medidor liberado del código no se vuelve a tomar, y uno que alguien eliminó no se trae de vuelta.

Pasar a código un medidor que difiere de la entrada

El caso habitual es un medidor que alguien creó a mano, con sus saldos, y que todavía no tiene paquetes mientras la entrada los declara. El seeder lo deja en paz porque difiere, y seguirá dejándolo: nada en el panel devuelve un medidor al código. Hazlo con una migración de tu proyecto que lo marque. Desde el siguiente deploy el seeder lo trata como suyo, carga los paquetes o el precio por unidad, crea sus precios en Stripe y devuelve los textos a lo que dice la entrada. Los saldos y los lotes nunca se tocan.

return new class extends Migration {
    public function up(): void
    {
        if (! Schema::hasTable('billing_meters')) {
            return;
        }

        DB::table('billing_meters')
            ->where('key', 'stamps')
            ->whereNull('released_from_code_at')
            ->update(['managed_by_code' => true]);
    }
};

Declara primero la entrada en MeterSeeder, o en el mismo deploy: la migración corre antes que los seeders y el seeder encuentra el medidor ya marcado. La misma migración no hace daño en una base donde el medidor aún no existe, porque el seeder lo crea bajo código. Un medidor liberado del código se salta a propósito: liberar es de un solo sentido y esto no lo trae de vuelta. La migración es la misma en todos los entornos, así que nadie tiene que capturar los paquetes a mano en cada uno.

Los planes y los complementos se pasan a código igual, con billing_plans y su slug, o billing_addons y su feature_key; mira Pasar a código un plan que ya existe y Pasar a código un complemento que ya existe.

Da una cuota con los límites del plan

Ningún modo captura cuánto incluye un plan. Eso se define donde ya están los límites, eligiendo la clave del medidor. En el formulario del plan, un medidor prepago o pospago va en Medidores incluidos, con lo que el plan trae en Incluido en cada ciclo, y un medidor de capacidad va en Capacidad y límites, junto a las claves que declara PlanLimits. En el formulario del complemento toda clave va en Límites, cuyo selector lista las claves de los medidores después de las que declara PlanLimits. Un plan con stamps = 50 da cincuenta timbres por ciclo; un complemento con stamps = 100 suma cien mientras se tenga. Los límites de plan y de complemento bajo la misma clave se suman, exactamente como en Límites del plan y consumo.

Para un medidor pospago, el renglón es además lo que permite que los suscriptores del plan lo usen. Un medidor pospago que ni el plan ni un complemento que tenga la cuenta listan entre sus límites no se puede usar en absoluto: consume() lo rechaza, como se describe abajo. Un renglón en cero permite usarlo y lo cobra desde la primera unidad. El formulario del plan muestra lo que declara hoy en la tarjeta de sólo lectura Medidores pospago, y las capacidades que permite en Medidores de capacidad. Un medidor prepago no se restringe así: su renglón es sólo la cuota, y una cuenta sin renglón gasta lo que compró.

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'—, declarado en MeterSeeder para que un proyecto nuevo lo tenga sin que nadie lo capture; un proyecto que ya lo tenía lo pasa a código en el siguiente deploy si sigue coincidiendo. 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 exactamente el mismo nombre que la clave del medidor:

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

Base cuenta los dispositivos como los lista el perfil: uno por navegador, sistema y dirección IP, y sólo los usados en los últimos 30 días, así que el medidor y la tarjeta Dispositivos conectados siempre muestran el mismo número.

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.

Exactamente 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('stamps', 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 clave no correspondió a ninguno.
url A dónde mandar al usuario para resolver un rechazo: la página de planes cuando un medidor pospago no está incluido en el plan; si no, null.

Lo que pasa depende del medidor:

  • Una clave 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 clave, 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 de la venta sigue respondiendo a su clave 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 de capacidad se rechaza: mide un nivel, no se consume.
  • Un medidor pospago se rechaza, sin registrar nada, cuando la cuenta no tiene suscripción activa o su plan no incluye el medidor, con el mensaje "Tu plan no incluye Correos. Elige un plan que sí lo incluya para continuar." y url apuntando a la página de planes. Si no, 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. Pasar de la cuota incluida nunca lo 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; Payment reminder 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 Other.

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, o Abonar unidades en el panel de administración Cuando tú digas; nunca, desde el panel de administración

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 job. 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 clave 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('stamps') available, purchased, plan, plan_quota, plan_resets_at, used_this_cycle. Acredita primero la cuota del plan.
$account->meterUsage('emails') Para un medidor pospago: used, included, billable, unit_price, amount, declared (si el plan lo incluye), unpriced, currency, remaining, breakdown por motivo, charges_at, period_start, period_end, has_subscription.
$account->meterCapacity('disk_space') Para un medidor de capacidad: allowed (null cuando nada lo limita), used, exact_used, declared, releasable, reached, remaining.
$account->meterMovements('stamps') 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, $reason = null) Agrega un lote y un movimiento positivo que lleva $reason, y devuelve el lote, o null cuando la clave no corresponde a ningún medidor o las unidades no son mayores que cero. El mismo origen y la misma referencia dos veces devuelven el lote que ya creó y no registran nada de nuevo.
$account->meterExhaustedMessage('stamps') 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. Se reutiliza solo en el idioma con que se abrió: quien cambia el idioma de la aplicación y vuelve a comprar recibe una sesión nueva, así que la página de Stripe sale en el idioma que ahora usa.
  2. Stripe regresa al usuario a /account/add-ons?meter=stamps&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 sólo 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 saldo 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 saldo de inmediato, porque no hay nada que esperar.

La línea de la factura dice Emails sent: 1,120 emails (1 September 2026 - 1 October 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 usados este ciclo. La línea del plan sólo aparece cuando tu plan incluye unidades de ese medidor. 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/{clave} 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 un medidor pospago esa página también enlaza Ver períodos cerrados, que abre /account/add-ons/meters/{clave}/periods: la tabla Períodos cerrados, del más reciente al más antiguo y veinte por página, con el periodo, las unidades usadas, las incluidas, el excedente, el importe y el cobro, que dice Cobrado, Pago fallido, Agregado al saldo o No facturado.

En el panel de administración, la página de un usuario, /admin/users/{id}, muestra un panel de sólo lectura Medidores con una frase por cada medidor a la venta: las unidades disponibles con lo comprado y lo que incluyó el plan, las unidades usadas, incluidas y cobrables con el importe y su fecha, o, en una capacidad, lo que está en uso contra lo permitido. Aparece en la página del usuario, no en el formulario de edición. Cuando existe un medidor prepago y puedes crear medidores, el panel tiene la acción Abonar unidades: elige el Medidor, las Unidades y un Motivo, que el usuario lee junto al crédito en sus movimientos. Las unidades caen como un lote manual que nunca vence.

Tres ejemplos reales

Timbres, prepago. Un medidor stamps, unidades stamp / stamps, paquetes de 100 por 350 MXN y de 500 por 1,200 MXN, con bloqueo en cero. El plan Pro define el límite stamps = 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 Capacidad y límites 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 Unidades 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 emails, 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('emails', 1, 'Payment reminder') 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 Payment reminder 1,340 · Welcome 420 · Invoice notification 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 periodo. Cumplido el plazo, el importe queda en el saldo pendiente de la cuenta y el periodo deja de leerse como un pago fallido. Pagar ese saldo 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 sólo 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 dice. Una llamada con una clave 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.
Volver