Cargando…

Esto está tardando más de lo esperado.

Volver al centro de ayuda

Vende complementos

Declara una capacidad en código, ponle precio en el panel de administración y deja que la gente la active, la compre o la reciba con su plan.

Un complemento es una capacidad que tu proyecto enciende para una cuenta: un módulo, un extra de pago, una cuota. El código declara qué capacidades existen; el panel de administración decide cómo se vende cada una; la cuenta de facturación responde si una cuenta la tiene. Esta guía cubre el camino completo, desde el método en PlanFeatures hasta el botón que pulsa el usuario.

Enciéndelo

Los complementos viven en el paquete Billing Core, así que el paquete tiene que estar encendido. Los planes no: un complemento gratis funciona sin plan, sin suscripción y sin llave de Stripe.

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, los interruptores desaparecen del formulario de usuario y nada se otorga ni se vende.
Billing Core BILLING_CORE_ENABLED true Apagado: el paquete no carga modelos, migraciones, rutas ni webhook.
Facturación de usuarios BILLING_USER_ENABLED true Apagado: las rutas de facturación bajo /account/... no se registran, complementos incluidos.
Stripe STRIPE_KEY, STRIPE_SECRET, STRIPE_WEBHOOK_SECRET vacío Necesarias para vender cualquier cosa. Cada guardado de un complemento o de un precio habla con Stripe.

Dos preguntas deciden qué se muestra una vez encendida la bandera:

  • BillingAddon::hasCatalog() es verdadero cuando la bandera está encendida y al menos un complemento está a la venta. Muestra el enlace Complementos en el menú de la cuenta. No consulta Stripe.
  • BillingAddon::isActive() pide lo mismo más STRIPE_SECRET. Decide si la página de inicio pública muestra el catálogo de complementos a los visitantes.

La bandera también programa billing:sync-exchange-rates a diario a las 12:00, el mismo comando que programan los planes. La instalación del paquete, las llaves y el webhook están en Enciende los planes y la facturación.

Declara la capacidad en código

Lo único que el código declara es qué capacidades existen. Abre app/Classes/PlanFeatures.php y agrega un método público por capacidad. El nombre del método es la llave de la capacidad. El valor que devuelve dice si la capacidad ya está liberada en código, así que puedes vender en preventa algo que todavía no enciende nada.

namespace App\Classes;

class PlanFeatures extends BasePlanFeatures
{
    public function electronic_wallet()
    {
        return true;
    }

    public function white_label()
    {
        return false;   // se vende en preventa, todavía no enciende nada
    }
}

BasePlanFeatures lee la clase por reflexión:

Método Devuelve
PlanFeatures::availableFeatures() Las llaves, una por método público no estático.
PlanFeatures::featureOptions() ['electronic_wallet' => 'Electronic Wallet', ...], la lista que ofrece el formulario del panel.
PlanFeatures::isReleased('white_label') true sólo cuando la llave está declarada y su método devuelve true.

La clase se conecta en config/billing-core.php bajo user_adapter.plan_features. Weblabor Base viene sin métodos, así que una instalación limpia no muestra el campo Capacidad ni el catálogo. Una llave guardada en un complemento que el código ya no declara se conserva y se marca en el formulario con "This capability is not declared in code, so the add-on will not switch anything on"; el complemento se puede seguir vendiendo. Si guardas un complemento sin elegir capacidad, la llave se deriva del nombre (Extra Storage se vuelve extra_storage).

Créalo en el panel de administración

Ve a Planes → Add On en el panel de administración, en /admin/billing-addons. El índice lista nombre, llave y si está a la venta, y tiene un botón Categorías que abre /admin/categories/billing-addons, donde creas las categorías por las que filtra el catálogo. Crear abre /admin/billing-addons/create; editar es /admin/billing-addons/{id}/edit. El formulario muestra una advertencia cuando STRIPE_SECRET no está definida: ponla primero, porque guardar crea o actualiza el producto en Stripe.

Campo Obligatorio Qué hace
Nombre Título de la tarjeta. También el nombre del producto en Stripe.
Categorías No Múltiples, del tipo de categoría billing-addons. El catálogo filtra por ellas.
Descripción Texto corto en la tarjeta y en Stripe.
Capacidad No La llave declarada que este complemento enciende. Sólo se muestra cuando el código declara al menos una.
Descripción completa No Se muestra en la página de detalle bajo "Acerca de este complemento".
Ícono No Imagen cuadrada, hasta 5 MB, guardada a 400×400. Se muestra en la tarjeta.
Capturas No Hasta seis imágenes, hasta 5 MB cada una, guardadas a 1600×1200, agregadas una por una. Se muestran como galería en la página de detalle.
A la venta Encendido por omisión. Apagado lo quita del catálogo y rechaza compras nuevas; quien ya paga lo conserva y puede cancelarlo.
Contexto Sólo con dos adaptadores Ambos, Usuario o Equipo. Filtra qué dueño de facturación lo ve. Oculto cuando sólo está registrado el adaptador user.
Precios No Una entrada por país: país, moneda y un importe por intervalo de config/pricing.php (monthly y yearly por omisión). Déjalo vacío para una capacidad gratis.
Límites No Llave de límite de App\Classes\PlanLimits y un valor entero. La cuota que aporta el complemento.

Los precios son precios de Stripe y los precios de Stripe son inmutables. Cada importe que guardas crea un precio recurrente en Stripe; un importe existente no se puede editar. Para cambiar un precio, quita el intervalo y agrégalo de nuevo con el importe nuevo. Un intervalo con suscriptores no se puede quitar. Un complemento con precios tampoco se puede eliminar, porque suscripciones y facturas los referencian: apaga A la venta en su lugar. Eliminar un complemento sin precios archiva su producto en Stripe.

Cada guardado sincroniza con Stripe: crear el complemento crea un producto con el id del complemento en sus metadatos, actualizarlo actualiza nombre y descripción, y cada variación de precio crea un precio con unit_amount en centavos, la moneda, y el intervalo y el conteo de config/pricing.php.

Las cinco formas en que un usuario obtiene un complemento

Se pueden combinar en el mismo complemento. El acceso se resuelve desde la más fuerte, en este orden:

lifetime  >  plan  >  subscription  >  trial  >  free
Origen Cómo se otorga Se pierde cuando Flujo en el catálogo
free El usuario pulsa Activar, o el administrador enciende el interruptor El usuario o el administrador lo apagan
plan El plan incluye el complemento La suscripción deja de estar activa
subscription El complemento es una línea de la suscripción de Stripe Se cancela la línea o la suscripción
trial grantAddOn(..., 'trial', ['ends_at' => ...]) desde código Pasa ends_at No
lifetime grantAddOn(..., 'lifetime') desde código Nunca, salvo que lo revoques No

La inclusión en el plan y las líneas de la suscripción se leen en vivo del pivote del plan y de los ítems de la suscripción, así que no pueden desfasarse. Las activaciones gratis, las pruebas y las compras de por vida son filas en billing_entitlements:

Columna Significado
billing_account_id La cuenta que lo tiene.
feature_key La capacidad.
source free, trial o lifetime. Una fila por cuenta, llave y origen.
status active por omisión. Cualquier otro valor se ignora.
quantity Unidades que se tienen, 1 por omisión.
starts_at, ends_at Ambos opcionales. Una fila cuenta sólo entre los dos.
reference Texto libre para tu propio registro, como el id de una sesión de Stripe.

Gratis

Un complemento sin ningún precio mayor a cero es gratis. El catálogo muestra Gratis y un botón Activar. Activarlo escribe un derecho free y no necesita tarjeta, suscripción, plan ni llave de Stripe. Desactivar borra sólo la fila free, así que un plan o una compra que también otorgue la capacidad la sigue otorgando.

Incluido en un plan

El formulario del plan tiene un selector múltiple Complementos incluidos en este plan que escribe el pivote billing_plan_addon. Mientras la suscripción default de la cuenta esté activa, cada complemento de su plan se otorga con origen plan y el catálogo muestra Incluido. Los límites de los complementos incluidos se suman a los límites del plan.

Suscripción

El catálogo muestra el precio del intervalo que la cuenta ya paga y un botón Comprar. Lo que pasa depende de la cuenta:

  • Todavía no hay suscripción, o la cuenta está en un plan gratis sin tarjeta guardada: se abre un Stripe Checkout con el plan gratis de ese intervalo más el complemento. Debe existir exactamente una variación de plan gratis activa para ese intervalo, o el usuario ve "No free plan available for :interval billing. Please subscribe to a plan first." Si las pruebas gratis están encendidas en config/pricing.php, la prueba cubre plan y complemento. El éxito regresa al dashboard con ?checkout=success; la cancelación regresa a /account/add-ons?checkout=cancel.
  • Una suscripción de pago, o un plan gratis con tarjeta: el precio del complemento se agrega a la suscripción y se factura de inmediato. La página consulta cada tres segundos hasta que el webhook registra el ítem nuevo, luego se recarga y muestra Cancelar.

Pulsar Cancelar quita el precio de la suscripción sin prorrateo. Los webhooks customer.subscription.updated, invoice.paid y customer.subscription.deleted recalculan el acceso de cada complemento con llave en esa cuenta. Ese recálculo pregunta a todos los orígenes, así que un webhook que ya no ve un complemento entre los ítems no puede apagar una capacidad que una compra de por vida o una activación gratis sigue otorgando.

Prueba

No hay flujo en el catálogo para iniciar una prueba. Otórgala desde código con una fecha de fin:

$account->grantAddOn('module_ai', 'trial', ['ends_at' => now()->addDays(15)]);

La fila deja de contar en el momento en que pasa ends_at. Mira "Por vencimiento" más abajo para saber qué refresca y qué no.

De por vida

Tampoco hay flujo en el catálogo para comprar una capacidad de una vez. Cuando tu propio checkout confirme el pago, otórgala desde código:

$account->grantAddOn('white_label', 'lifetime', ['reference' => $sessionId]);

Un derecho de por vida gana sobre cualquier otro origen, así que el catálogo muestra Adquirido y el administrador no puede apagarlo. Confirmar el pago es el tema de Cobra una vez con Stripe Checkout.

Activar y desactivar

Desde el catálogo

Los complementos gratis alternan con Activar y Desactivar. Los de pago con Comprar y Cancelar. Tanto la página del catálogo como la de detalle ejecutan las mismas acciones.

Desde el panel de administración

Al editar un usuario en /admin/users/{id}/edit aparece un panel Add-ons con un interruptor por cada complemento que tenga llave. El interruptor refleja lo que la cuenta realmente tiene desde todos los orígenes. Encenderlo escribe un derecho free; apagarlo borra el free. Una capacidad otorgada por un plan, una suscripción, una prueba o una compra de por vida se marca "Add-on gestionado por Stripe" y no se puede apagar ahí: el formulario se niega con el mensaje de complemento bloqueado. Cancélala donde se compró.

Desde código

Todo es un método de la cuenta de facturación. Obtenla desde el dueño:

use WeblaborMx\BillingCore\BillingManager;

$account = $user->billingAccount;                                  // relación
$account = app(BillingManager::class)->context($user, 'user')->account;
Método Qué hace
$account->grantAddOn($key, $source, $attributes = []) Crea o actualiza el derecho para esa llave y origen, pone status en active y refresca la caché. $attributes puede llevar quantity, starts_at, ends_at, reference.
$account->revokeAddOn($key, $source) Borra esa única fila y refresca la caché. Los demás orígenes no se tocan.
$account->toggleAddOn($variation) Compra o cancela un complemento de pago, dada una BillingPriceVariation. Devuelve ['success' => bool, 'message' => ..., 'action' => 'buy' o 'cancel'], o ['success' => true, 'url' => ...] cuando hay que abrir un Checkout.
$user->toggleAddOn($variation) Lo mismo a través del dueño.

Por vencimiento

No hay job de vencimiento, ni cola, ni comando programado. Una prueba o cualquier derecho con ends_at simplemente deja de contar cuando pasa la fecha, porque hasAddOn() lee las filas en el momento en que se le pregunta. Lo que no se actualiza solo es la caché features descrita más abajo: se escribe cuando algo llama a refreshFeatureCache(), y el vencimiento no llama a nada. Si tu código lee hasFeature(), programa tu propio refresco, por ejemplo un comando que recorra BillingEntitlement::where('ends_at', '<', now()) y llame a $account->refreshFeatureCache($key) por cada una, o lee hasAddOn() en su lugar.

Verifica el acceso en tu código

Pregúntale a la cuenta. Es lo único que conoce todos los orígenes.

Método Devuelve
$account->hasAddOn('electronic_wallet') true o false.
$account->addOnSource('electronic_wallet') 'lifetime', 'plan', 'subscription', 'trial', 'free' o null.
$account->addOnQuantity('extra_storage') Unidades que se tienen: el mayor entre 1, la cantidad del ítem de la suscripción y el quantity del derecho. 0 cuando no se tiene.
$account->entitlements() Las filas de billing_entitlements; ->granted() acota a las que cuentan ahora.
$user->hasLockedAddOn('white_label') true cuando el origen es cualquiera menos free.
$user->getIncludedAddons() Los complementos del plan actual.
$user->hasFeature('electronic_wallet') La caché, ver abajo.

hasFeature() lee el json features. Vive en billing_accounts.features, y también en la tabla del dueño cuando esa tabla tiene una columna features, que es como un producto con módulos de equipo mantiene funcionando sus llamadas a hasFeature(). La tabla users de Weblabor Base no tiene esa columna, así que para usuarios sólo existe la caché de la cuenta. Se escribe con refreshFeatureCache() después de cada otorgamiento, revocación y webhook. Es una caché de lectura, nunca donde se toma una decisión, y se atrasa en los vencimientos.

Límites: $user->getLimitTotal('activities') suma el límite del plan, los límites de los complementos que son ítems de la suscripción y los límites de los complementos incluidos en el plan. Los límites de complementos que sólo se tienen como free, trial o lifetime no se cuentan.

Lo que ve el usuario

El catálogo es /account/add-ons, accesible desde la entrada Complementos del menú de la cuenta en cuanto un complemento está a la venta. Renderiza el mismo componente que usa la página de inicio, así que ambos se ven igual.

  • Un encabezado, "Mejora tu plan", y una píldora que cambia el intervalo. Sólo se ofrecen los intervalos que algún complemento realmente cotiza y que están activos en config/pricing.php. El intervalo arranca en el que la cuenta ya paga, o mensual.
  • Un buscador sobre nombre y descripción, y un selector de categoría que sólo lista categorías con al menos un complemento a la venta.
  • Una tarjeta por complemento a la venta: ícono o una pieza de rompecabezas como relleno, nombre que enlaza al detalle, hasta dos categorías, la descripción, el precio del intervalo y el botón. Los precios se eligen para el país de la cuenta y caen a la entrada de país default.
  • La página de detalle, /account/add-ons/{id}, agrega la galería de capturas, la descripción completa y "Qué incluye", construido a partir de los límites del complemento.

El botón dice lo que la cuenta puede hacer:

Etiqueta Cuándo
Adquirido El origen es lifetime. Deshabilitado.
Incluido El origen es plan. Deshabilitado.
Activar / Desactivar Complemento gratis, sin activar o ya activado.
Comprar / Cancelar Complemento de pago, sin ser o siendo ya ítem de la suscripción.
No disponible Complemento de pago sin precio para el intervalo elegido. Deshabilitado, con una nota.

En la página de inicio pública el catálogo aparece sólo cuando BillingAddon::isActive() es verdadero y el visitante no está en iOS. Ahí cada botón dice Obtener complemento y manda al visitante a la página de registro.

Lo que no está construido

El resolutor entiende pruebas, compras de por vida y cantidades, y el código de arriba las crea, pero el catálogo no tiene flujo para iniciar una prueba, comprar una capacidad de una vez o comprar varias unidades. Los medidores, los paquetes prepagados y el consumo facturado al cierre del mes no existen; el paquete incluye WeblaborMx\BillingCore\Support\UsageCharge, una calculadora de unidades facturables, pero nada en el kit la llama.