Activa los planes y el cobro
Las llaves, banderas y el webhook que hacen existir las suscripciones, y qué aparece cuando existen.
El cobro viene apagado en una instalación nueva. Esta guía cubre todo lo que configuras antes de poder vender un plan: las llaves de Stripe, las banderas que hacen existir las pantallas, las variables de Cashier, el webhook y cómo probarlo. Crear los planes en sí está en Crea y pon precio a un plan.
Sobre qué corre
Las suscripciones son Laravel Cashier 15 sobre Stripe. Stripe es la fuente de verdad: las tablas locales reflejan lo que Stripe reporta por el webhook, y nada se cobra ni se suscribe fuera de Stripe. El código vive en el paquete local packages/billing-core (weblabormx/billing-core), que la aplicación requiere por ruta.
El cliente de Cashier no es el usuario. Cada usuario posee una fila BillingAccount (tabla billing_accounts), y esa cuenta guarda el id de cliente de Stripe, el resumen del método de pago y las suscripciones. App\Models\User llega a ella por el trait App\Traits\HasPlans, así que en tu código sigues escribiendo $user->subscription(), $user->currentPlan() o $user->reachedLimit('key'); el trait reenvía a la cuenta. El trait no tiene dependencia compilada con el paquete, y por eso la aplicación arranca con el cobro removido.
Los interruptores
Nada relacionado con cobro aparece hasta que esto está puesto. .env.example lista las llaves de Stripe y las dos banderas; las variables BILLING_* no están en él porque su valor por omisión ya es encendido.
| Variable | Valor por omisión | Qué controla |
|---|---|---|
STRIPE_KEY |
vacío | Llave publicable. La carga Stripe.js en la pantalla de métodos de pago. |
STRIPE_SECRET |
vacío | Llave secreta. Toda llamada a Stripe, y una condición para que los recursos Planes y Complementos del panel de administración existan siquiera. |
STRIPE_WEBHOOK_SECRET |
vacío | Secreto de firma del endpoint del webhook. Las peticiones sin una firma válida se rechazan. |
FEATURE_PLANS_ENABLED |
false |
Planes: el recurso del panel, /account/plans, /account/billing, /account/payment-methods, el middleware de suscripción, la columna y el filtro de plan en la lista de usuarios. |
FEATURE_ADDONS_ENABLED |
false |
Complementos: el recurso del panel, /account/add-ons, el panel de complementos en el formulario de usuario. Ver Vende complementos. |
BILLING_CORE_ENABLED |
true |
El paquete completo. En false el proveedor no registra nada: ni migraciones, rutas, comandos, políticas, observadores ni configuración de Cashier. |
BILLING_USER_ENABLED |
true |
Sólo el adaptador de usuario: las rutas /account/* de cobro, el listener que suscribe a un usuario nuevo al plan gratuito, y el alias del middleware ensure.subscribed. Las tablas compartidas y los recursos del panel siguen disponibles para otro adaptador. |
Las dos banderas se leen de config/features.php (plans_enabled, addons_enabled); las dos variables BILLING_* de config/billing-core.php (enabled, user_adapter.enabled).
Con FEATURE_PLANS_ENABLED y FEATURE_ADDONS_ENABLED apagadas, el panel de administración no muestra ningún grupo Planes: ni Planes, ni Complementos. Es el error de despliegue más común: las llaves están puestas, la migración corrió, y la barra lateral sigue sin nada. Pasa lo mismo con la bandera encendida y STRIPE_SECRET vacío, porque las políticas de plan y complemento rechazan toda acción cuando falta el secreto. Pon la bandera y el secreto, limpia la caché de configuración, y el grupo aparece para quien tenga el permiso retrieve billing_plan.
Los planes se consideran vivos para los usuarios sólo cuando tres cosas son ciertas a la vez: la bandera está encendida, STRIPE_SECRET está puesto, y existe al menos un plan. Eso es lo que revisa BillingPlan::isActive(), y es la condición detrás de cada pantalla del usuario. Hasta que creas el primer plan, el menú de la cuenta no muestra nada y el middleware de suscripción deja pasar a todos.
Las variables de Cashier
Cashier lee su propia configuración de vendor/laravel/cashier/config/cashier.php, que el kit no publica. Estas son las variables que lee y qué hace cada una en esta aplicación.
| Variable | Valor por omisión | Qué hace aquí |
|---|---|---|
CASHIER_CURRENCY |
usd |
Moneda de respaldo de Cashier: la que usa cuando un monto se formatea o se cobra sin moneda propia. Los precios de suscripción no la usan, porque cada Stripe Price trae su propia moneda. Los cobros únicos sí; ver Cobra una vez con Stripe Checkout. |
CASHIER_CURRENCY_LOCALE |
en |
Configuración regional para formatear dinero, como los totales de facturas en /account/billing. Cualquier valor distinto de en necesita la extensión intl de PHP. |
CASHIER_PATH |
stripe |
Prefijo de las rutas que Cashier registra por su cuenta: /stripe/payment/{id} (la página donde un cliente confirma un pago que requiere autenticación) y /stripe/webhook. |
CASHIER_PAYMENT_NOTIFICATION |
vacío | Clase de notificación que se envía cuando Stripe reporta invoice.payment_action_required. Ponle Laravel\Cashier\Notifications\ConfirmPayment para enviar al cliente un correo con el enlace a la página de confirmación. Vacío significa sin correo. |
CASHIER_LOGGER |
vacío | Canal de config/logging.php para los mensajes propios del SDK de Stripe. |
CASHIER_INVOICE_RENDERER |
renderizador Dompdf | Renderizador de PDF para $invoice->download(). Disponible pero no lo usa ninguna función del kit: la pantalla de cobro lista las facturas y no ofrece PDF. |
CASHIER_PAPER |
letter |
Tamaño de papel de ese PDF. Mismo estatus. |
CASHIER_REMOTE_ENABLED |
false |
Si ese PDF puede cargar recursos remotos. Mismo estatus. |
STRIPE_WEBHOOK_TOLERANCE |
300 |
Segundos de desfase de reloj aceptados en la firma del webhook. App\Providers\AppServiceProvider lo fija en 300, así que cambiar la variable no tiene efecto. |
AppServiceProvider también copia STRIPE_WEBHOOK_SECRET a cashier.webhook.secret, así que las tres variables STRIPE_* se ponen una sola vez, en .env.
La configuración del paquete
config/billing-core.php es el archivo propio del paquete. Rara vez lo cambias; cuando lo hagas, publícalo primero:
php artisan vendor:publish --tag=billing-core-config
| Llave | Valor por omisión | Propósito |
|---|---|---|
user_adapter.model |
App\Models\User |
La clase dueña de una cuenta de cobro de usuario. |
user_adapter.country_resolver |
App\Services\CountryDetectionService |
De dónde sale el país de un visitante cuando el usuario no tiene uno. Ver Detecta el país del visitante. |
user_adapter.plan_limits |
App\Classes\PlanLimits |
La clase cuyos métodos públicos son las llaves de límite. Ver Límites de plan y consumo. |
user_adapter.plan_features |
App\Classes\PlanFeatures |
La clase cuyos métodos públicos son las capacidades de los complementos. |
adapters |
vacío | Dueños de cobro adicionales declarados sin proveedor, cada uno como ['class' => ..., 'enabled' => ...]. El archivo trae un ejemplo comentado que lee BILLING_SUITE_ENABLED; nada lee esa variable hasta que lo descomentas. |
models, morph_types, host |
clases del paquete | Las clases de modelo, los nombres de tipo polimórfico y las clases del proyecto (modelo de evento de webhook, filtro de selección, modelo de categoría) con las que el paquete habla. Déjalas. |
config/pricing.php guarda los ajustes comerciales: los intervalos de cobro, la moneda principal y el proveedor de tipos de cambio, cubiertos en Monedas, intervalos y tipos de cambio, y los interruptores de prueba y cupones, cubiertos en Pruebas gratuitas y cupones.
Las tablas
php artisan migrate crea billing_accounts, subscriptions, subscription_items, billing_plans, billing_addons, billing_plan_addon, billing_prices, billing_price_variations, billing_limits, billing_entitlements y currency_exchange_rates. La migración es aditiva: en una base que ya tiene datos de Cashier en users, crea una cuenta de cobro por cada usuario con datos de Stripe y enlaza las suscripciones existentes, sin borrar nada. Revertirla conserva las tablas a propósito.
Después siembra los permisos que necesitan los nuevos recursos, retrieve, create, update y delete sobre billing_plan y billing_addon:
php artisan db:seed --class=PermissionSeeder
php artisan db:seed --class=RoleSeeder
El rol de administrador recibe todos los permisos. Cualquier otro rol necesita que se le otorguen a mano; ver Roles y permisos.
El webhook
Stripe le cuenta a la aplicación qué pasó: se creó una suscripción, se renovó, se canceló, falló un pago. Sin el webhook nada cambia localmente después del Checkout, y el usuario que acaba de pagar sigue viendo "Sin plan".
Registra un endpoint en el Dashboard de Stripe, en Developers, Webhooks:
https://your-project.test/api/stripe/webhook
Debe enviar estos eventos:
| Evento | Qué hace la aplicación con él |
|---|---|
customer.subscription.created |
Crea la suscripción local y sus elementos. |
customer.subscription.updated |
Actualiza estatus, elementos, fin de prueba y fecha de cancelación; después prende o apaga las capacidades de complementos para que coincidan con los elementos. |
customer.subscription.deleted |
Marca la suscripción como cancelada, registra la fecha y el motivo de cancelación, y retira las capacidades que la suscripción otorgaba. |
customer.updated |
Refresca el método de pago predeterminado guardado localmente. |
customer.deleted |
Cancela las suscripciones locales y limpia los ids de Stripe de la cuenta. |
payment_method.automatically_updated |
Refresca el resumen de la tarjeta cuando la red de tarjetas la actualiza. |
invoice.payment_action_required |
Envía CASHIER_PAYMENT_NOTIFICATION si configuraste una. |
invoice.payment_succeeded |
Lo maneja el comportamiento por omisión de Cashier; el kit no le agrega nada. |
invoice.paid |
Dispara SubscriptionPaid para montos pagados, que paga recompensas de referidos y registra tracking, sincroniza las capacidades de complementos, y es la fuente de la gráfica de ingresos y de las capturas de tipos de cambio. |
php artisan cashier:webhook --url=https://your-project.test/api/stripe/webhook crea el endpoint desde la línea de comandos con los primeros ocho eventos; agrégale invoice.paid en el Dashboard después. En cualquier caso, copia el secreto de firma que muestra el Dashboard en STRIPE_WEBHOOK_SECRET.
Registra /api/stripe/webhook, no /stripe/webhook. Cashier también registra el segundo, con su controlador de fábrica, que no sabe nada de complementos, referidos ni de la bitácora de eventos.
Cada petición que recibe el endpoint se guarda en webhook_events con su payload, la respuesta y cualquier error, y se lista en /admin/webhook-events. Esa lista es donde compruebas que Stripe te alcanza; ver Bitácoras y eventos de webhook.
Probarlo en local
Stripe no puede llegar a your-project.test, así que reenvía los eventos con la CLI de Stripe:
stripe login
stripe listen --forward-to your-project.test/api/stripe/webhook
El comando imprime un secreto de firma que empieza con whsec_. Ponlo en STRIPE_WEBHOOK_SECRET mientras corre el listener; no es el mismo del Dashboard. Después suscríbete con una tarjeta de prueba como 4242 4242 4242 4242 y mira llegar los eventos en la terminal y en /admin/webhook-events. Para repetir un evento:
stripe trigger customer.subscription.created
Qué ve el usuario
Cuando BillingPlan::isActive() es verdadero, el usuario con sesión iniciada obtiene:
- Mis planes en el menú de la cuenta, en
/account/plans: las tarjetas de planes con un selector de intervalo, y dos enlaces, Ver historial de pagos (/account/billing) y Métodos de pago (/account/payment-methods). Con los complementos encendidos, el catálogo de complementos se muestra debajo de los planes. - Un bloque Plan arriba de la barra lateral de la aplicación con el nombre del plan actual y un enlace Mejorar plan a
/account/plans. - Tarjetas de consumo en el tablero de la aplicación, una por llave de límite, con las gráficas de uso.
- Una sección de precios en la página pública de inicio, cuyos botones llevan al registro.
- Un plan gratuito en el momento en que se registra, cuando existe exactamente un plan gratuito para su país. El registro dispara el evento
Registeredde Laravel, y el listener del paquete suscribe la nueva cuenta a él, sin tarjeta. - El middleware
ensure.subscribeden toda ruta bajo/app. Sin una suscripción activa el usuario es enviado a/account/planscon un mensaje pidiéndole una.
En iOS, detectado por la cookie app-platform de la app envuelta, /account/plans muestra un aviso en lugar de los planes, porque ahí no se permiten compras dentro de la app fuera de la App Store.
No hay Stripe Customer Portal. Las tarjetas se agregan, se vuelven predeterminadas y se eliminan dentro de la aplicación, mediante Stripe Elements y un SetupIntent, así que el número de tarjeta nunca toca tu servidor.
Qué ve el administrador
- Un grupo Planes en la barra lateral del panel con Planes (
/admin/billing-plans) y Add Ons (/admin/billing-addons), que conserva su etiqueta en inglés. - Una columna Plan actual y un filtro Plan en la lista de usuarios, y un panel Complementos en el formulario de usuario cuando los complementos están encendidos.
- Tres métricas en el tablero del panel: Ingresos diarios, en la moneda principal, Suscripciones creadas y Suscripciones totales acumuladas, las dos últimas sólo mientras los planes están vivos.
- La bitácora del webhook en
/admin/webhook-events.
Apagarlo
BILLING_CORE_ENABLED=false deshabilita toda ruta, pantalla y comando de cobro mientras el paquete sigue instalado. Para quitar el paquete de forma definitiva, mientras su carpeta aún existe:
composer remove weblabormx/billing-core
php artisan optimize:clear
Después borra la entrada de ruta packages/billing-core del composer.json raíz y la carpeta misma, y corre composer dump-autoload. Ningún paso borra las tablas de cobro ni los identificadores de Stripe; esa es una decisión aparte y deliberada.