PHPackages                             emeefe/subscriptions - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Payment Processing](/categories/payments)
4. /
5. emeefe/subscriptions

ActiveLibrary[Payment Processing](/categories/payments)

emeefe/subscriptions
====================

A laravel package for manage subscriptions

v1.1.1(5y ago)1481MITPHPPHP &gt;=7.0.0

Since Jul 9Pushed 5y ago1 watchersCompare

[ Source](https://github.com/rama-emeefe/subscriptions)[ Packagist](https://packagist.org/packages/emeefe/subscriptions)[ RSS](/packages/emeefe-subscriptions/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (2)Versions (8)Used By (0)

Emeefe Subscriptions
====================

[](#emeefe-subscriptions)

Instalación
-----------

[](#instalación)

1. Instalar el paquete vía composer:

    ```
    composer require emeefe/subscriptions
    ```
2. Publicar recursos (migraciones y archivo de configuración):

    ```
    php artisan vendor:publish --provider='Emeefe\Subscriptions\SubscriptionsServiceProvider'
    ```
3. Configurar

El archivo de configuración `emeefe.subscriptions` permite definir los nombres de las tablas a usar/crear antes de ejecutar migraciones o después en caso de renombrarlas, además permite especificar los modelos que serán usados. Por default la configuración es la siguiente:

```
[
    'tables' => [
        'plans' => 'plans',
        'plan_types' => 'plan_types',
        'plan_features' => 'plan_features',
        'plan_type_feature' => 'plan_type_feature',
        'plan_feature_values' => 'plan_feature_values',
        'plan_periods' => 'plan_periods',
        'plan_subscriptions' => 'plan_subscriptions',
        'plan_subscription_usage' => 'plan_subscription_usage',
    ],

    'models' => [
        'plan' => \Emeefe\Subscriptions\Models\Plan::class,
        'feature' => \Emeefe\Subscriptions\Models\PlanFeature::class,
        'period' => \Emeefe\Subscriptions\Models\PlanPeriod::class,
        'subscription' => \Emeefe\Subscriptions\Models\PlanSubscription::class,
        'type' => \Emeefe\Subscriptions\Models\PlanType::class,
    ]
]
```

4. Ejecutar migraciones ```
    php artisan migrate
    ```

Subscriptions
=============

[](#subscriptions)

### Tipos de plan (PlanType)

[](#tipos-de-plan-plantype)

Un tipo de plan engloba un conjunto de caracteristicas permitidas, puede usarse para separar multiples tipos de planes como planes para Empresa, para Usuarios, de almacenamiento, etc.

- Un tipo de plan puede estar ligado a una o más características.

### Características de plan (PlanFeature)

[](#características-de-plan-planfeature)

Las caracteristicas de plan, como su nombre lo dice, permiten definir características, permisos, etc que puede tener un tipo de plan.

- Una característica puede ser contable(`limit`) o no contable(`feature`).
- Una característica no almacena los límites que tiene sino solo su información básica.
- Una característica puede estar ligada a uno o varios tipos de plan permitiendo casos en los que distintos tipos de plan comparten una misma caracteristica.

### Planes (Plan)

[](#planes-plan)

Un plan pertenece a un tipo de plan y este puede tener asociados los límites de las características del tipo de plan(a través de relaciones), siempre y cuando sean carcateristicas contables(`limit`).

- Plan default: Un plan default es el plan que se obtiene por defecto al suscribir a un usuario u otra instancia, solo puede existir un plan default dentro de un mismo tipo de plan.
- Un plan pertenece solo a un tipo de plan
- Un plan puede ser `visible` o `hidden` (oculto)

### Periodos de plan (PlanPeriod)

[](#periodos-de-plan-planperiod)

Un periodo de plan indica el tiempo que dura un ciclo, los usuarios o instancias se suscriben directamente al periodo y no al plan dado que los periodos pueden variar en tiempo.

- Un periodo puede tener un costo o ser gratuito
- Un periodo pertenece a un solo plan
- Puede tener dias de prueba
- Puede variar en duración
    - Puede ser recurrente: Cada cierto tiempo debe renovarse(`recurring`)
    - Puede ser no recurrente: No puede renovarse
        - Puede ser limitado: Tiene definido una unidad de tiempo como Día, Mes y Año además de la cantidad de unidades de tiempo, por ejemplo **5 días**, **6 meses**, **1 año**, pasado este periodo no se vuelve a repetir, termina la suscripción. (`limited`)
        - Puede ser ilimitado: Puede no tener definido una unidad de tiempo ni cantidad de unidades, en otras palabras, **nunca caduca**. (`unlimited`)
- Un periodo de plan puede tener visibilidad `visible` o `hidden`
- Puede tener días de tolerancia para renovación.
- Puede haber solo un periodo default dento del mismo plan.

### Subscripción (PlanSubscription)

[](#subscripción-plansubscription)

Una subscripción es una relación creada entre un usuario u otra instancia con un plan a través de un periodo, la suscripción obtiene informacion actual del plan y del periodo y la mantiene en la suscripción, parecido a crear una copia, para evitar efectos colaterales cuando cambian datos del plan, precios, etc. pudiendo definir el comportamiento cuando ocurren estas situaciones a través de la escucha de eventos.

Cuando se crea una suscripción, esta se vuelve independiente del plan y del periodo aunque es posible obtener las relaciones a dichos modelos para obtener información actualizada u otras acciones necesarias.

- La suscripción trabaja con relaciones polimórficas pudiendo tener relaciones con cualquier modelo y no solo con el de usuario.
- Mantiene la información del plan y periodo en el momento en el que se crea.
    - Días de tolerancia
    - Precio
    - Moneda
    - Periodo de recurrencia
    - Límites de features
- **Un modelo puede suscribirse solo a un periodo de plan dentro del mismo tipo** por lo que para suscribirse a un nuevo periodo del mismo tipo de plan se debe cancelar primero dicha suscripción existente. En otras palabras, solo puede existir cero o una suscripción no cancelada relacionada con el modelo.
- Dependiendo el periodo al que se suscribe, la suscripción puede variar en duración
    - Puede ser recurrente: Cada cierto tiempo debe renovarse(`recurring`)
    - Puede ser no recurrente: No puede renovarse
        - Puede ser limitado: Tiene definido una unidad de tiempo como Día, Mes y Año además de la cantidad de unidades de tiempo, por ejemplo **5 días**, **6 meses**, **1 año**, pasado este periodo no se vuelve a repetir, no se puede renovar y es cancelada. (`limited`)
        - Puede ser ilimitado: Puede no tener definido una unidad de tiempo ni cantidad de unidades, en otras palabras, **nunca caduca**. Si puede ser cancelada pero no renovada. (`unlimited`)
- Una subscripción puede tener los siguientes estatus según su validez en tiempo:
    - **Suscripción en periodo de prueba**: Suscripción que aun se encuentra en su periodo de prueba sin importar si está cancelada o no.
    - **Suscripción activa**:
        - Suscripción limitada que se encuentra si y solo si dentro del periodo normal de la suscripción, sin importar si está cancelada o no, debe tomarse en cuenta que una suscripción en días de prueba no es activa.
        - Suscripción ilimitada no cancelada
    - **Suscripción expirada en rango de tolerancia**: Suscripción que no ha sido cancelada, ha expirado pero aún se encuentra en un rango de tolerancia, una suscripción ilimitada nunca presentará este estatus.
    - **Suscripción expirada (Full expired)**:
        - Suscripción que no ha sido cancelada, ha expirado y ya han pasado los días de tolerancia. En caso de haber renovación, esta será tomada a partir de la fecha actual.
        - Suscripción ilimitada que ha sido cancelada
- Adicional a los estatus anteriores exiten otras condiciones que pueden ser usadas para comparar la suscripción.
    - **Suscripción cancelada**: Suscripción que ha sido cancelada, ya no puede renovarse. Se debe crear una nueva suscripción para esto.
    - **Suscripción válida**: Es el estatus con el que se debería comparar para saber si el suscriptor aún tiene acceso a la suscripción.
        - Suscripción no cancelada que no ha expirado y no ha pasado de sus días de tolerancia.
        - Suscripción cancelada que no ha expirado, en este caso se ignoran los días de tolerancia.
    - **Suscripción ilimitada**: Suscripción que nunca termina.
    - **Suscripción limitada**: Suscripción que tiene una fecha de expiración.

[![Status](docs/images/SubscriptionsStatus.png)](docs/images/SubscriptionsStatus.png)

---

[![Periods](docs/images/SubscriptionsPeriods.png)](docs/images/SubscriptionsPeriods.png)

---

Creación de tipo de plan
------------------------

[](#creación-de-tipo-de-plan)

La creación de un tipo de plan se realiza a través de su modelo `PlanType` de la siguiente manera:

```
use Emeefe\Subscriptions\Models\PlanType;
...

$planType = new PlanType();
$planType->type = 'user_plan';
$planType->description = 'The user plan for basic subscriptions on profile'
$planType->save();
```

### Creación de features

[](#creación-de-features)

Para crear features de pla se realiza a través de su modelo `PlanFeature` de la siguiente manera:

```
use Emeefe\Subscriptions\Models\PlanFeature;
...

$planFeature = new PlanFeature();
$planFeature->display_name = 'Allowed images on galery';
$planFeature->code = 'gallery_images';
$planFeature->description = 'The number of images a user can have in his gallery';
$planFeature->type = PlanFeature::TYPE_LIMIT;
$planFeature->metadata = [
    'formats' => ['jpg', 'png'],
    'max_size_bytes' => '1024'
];
$planFeature->save();
```

Es importante notar que la propiedad `code` se define como una abreviatura ya que esta podrá ser usada para realizar consultas de manera mas sencilla y descriptiva.

El modelo `PlanFeature` ofrece dos constntes para definir el tipo, estas son:

- `PlanFeature::TYPE_LIMIT`
- `PlanFeature::TYPE_FEATURE`

La propiedad `metadata` tiene un cast a `array` por lo que se pueden manejar directamente asignaciones de arrays y estos se guardarán con formato JSON.

### Asignar features a un tipo de plan

[](#asignar-features-a-un-tipo-de-plan)

La asignación de features a un tipo de plan se realiza utilizando el método `attachFeature` de la siguiente manera:

```
$planType->attachFeature($limitFeature)
    ->attachFeature($unlimitFeature);
```

que no es mas que un alias a

```
$planType->features()->saveMany([
    $limitFeature,
    $unlimitFeature
]);
```

### Verificación y obtenciónde features desde el tipo de plan

[](#verificación-y-obtenciónde-features-desde-el-tipo-de-plan)

El tipo de plan permite verificar si hay features ligados a él así como obtenerlos

```
if($planType->hasFeature('gallery_images')){
    $theFeature = $planType->getFeatureByCode('gallery_images')->id
}
```

### Creación de un plan

[](#creación-de-un-plan)

Una vez que tenemos el tipo de plan y sus features asociados, podemos crear un nuevo plan dentro del tipo de plan, esto se realiza usando su modelo `Plan` de la siguiente manera:

```
use Emeefe\Subscriptions\Models\Plan;
...

$plan = new Plan();
$plan->display_name = 'Free';
$plan->code = 'free';
$plan->description = 'Free plan for users';
$plan->type_id = $planType->id;
$plan->is_default = true;
$plan->metadata = [
    'order' => 1
];
$plan->is_visible = true;
$plan->save();
```

La propiedad `code` puede repetirse en los planes siempre y cuando sea de distinto tipo, esto se realiza internamente en el evento `saving` del modelo. En caso de que ya exista el código dentro del tipo se lanzará la excepción `Emeefe\Subscriptions\RepeatedCodeException`

Para indicar que un plan es el plan default dentro del tipo de plan se asigna la propiedad `is_default` a `true`, si esto sucede y ya hay un plan default dentro del tipo de plan entonces el antiguo plan default ya no será default definiendo su propiedad `is_default` a `false`. Esto funciona gracias al evento `saving` del modelo.

Por default un plan se crea visible a menos que se especifique en su campo `is_visible` como `false`, se aplica un scope global para devolver solo los planes visibles, en la sección del modelo se explica esto.

### Asignación y obtención de límites de features del tipo limit

[](#asignación-y-obtención-de-límites-de-features-del-tipo-limit)

Para asignar los límites que tendrá un feture del tipo `limit` en un plan determinado se hace a través del modelo `Plan` de la siguiente manera:

```
$plan->assignFeatureLimitByCode('images_feature', 10);
$plan->getFeatureLimitByCode('images_feature');

...

if(!$plan->hasFeature('images_feature')){
    throw new \Exception("You do not have the feature");
}
```

Solo se permite asignar límites a las características del tipo `limit`, ver más casos en documentación del modelo.

Si no se hace la asignación de límite a un feature del tipo `limit` y se trata de obtener su límite entonces se devolverá un `0`.

### Crear periodos de plan

[](#crear-periodos-de-plan)

Para crear un periodo de plan **se debe utilizar el builder** `PeriodBuilder` devuelto por el método `period` de la clase `Subscriptions` en lugar de usar directamente el modelo `PlanPeriod` para evitar incongruencias.

El método builder tiene la siguiente estructura: `period(string $displayName, string $code, Plan $plan)`

Donde

- `$displayName`: Nombre a asignar al periodo
- `$code`: Código a asignar al periodo, debe ser único dentro de los periodos del plan
- `$plan`: Es la instancia del plan al que se asociará el periodo.

Los métodos del `PeriodBuilder` disponibles y sus acciones default son:

#### `setPrice(float $price)`

[](#setpricefloat-price)

Define el precio del periodo

- `$price`: Precio a asignar

Si no se llama este método o se ejecuta con un valor menor a 0 entoces se definirá el precio a `0`

#### `setCurrency(string $currency)`

[](#setcurrencystring-currency)

Define la moneda a usar con el periodo

- `$currency`: Precio a asignar a 3 carcateres ISO 4217

Si no se llama este método se definirá la moneda a `MXN`

#### `setTrialDays(int $trialDays)`

[](#settrialdaysint-trialdays)

Define los días de prueba que tendrá el periodo.

- `$trialDays`: Días de prueba

Si no se llama a este método o se ejecuta con un valor menor a 0 entonces se definirá a `0`

#### `setRecurringPeriod(int $count, string $unit)`

[](#setrecurringperiodint-count-string-unit)

Define como recurrente el periodo asignando una unidad y cantidad de unidades.

- `$count`: Cantidad de unidades
- `$unit`: Unidad de periodo, usar a través de las constantes `PlanPeriod::UNIT_DAY`, `PlanPeriod::UNIT_MONTH` y `PlanPeriod::UNIT_YEAR`

Si no se llama a este método o al método `setLimitedNonRecurringPeriod` se definirá como ilimitado no recurrente.

#### `setLimitedNonRecurringPeriod(int $count, string $unit)`

[](#setlimitednonrecurringperiodint-count-string-unit)

Define como no recurrente el periodo y asignan la unidad y cantidad de unidades que tendrá su único ciclo.

- `$count`: Cantidad de unidades
- `$unit`: Unidad de periodo, usar a través de las constantes `PlanPeriod::UNIT_DAY`, `PlanPeriod::UNIT_MONTH` y `PlanPeriod::UNIT_YEAR`

Si no se llama a este método o al método `setRecurringPeriod` se definirá como ilimitado no recurrente.

#### `setHidden()`

[](#sethidden)

Define el periodo de plan como oculto. Si no se llama a este método entonces el periodo de plan se definirá como visible.

#### `setToleranceDays(int $toleranceDays)`

[](#settolerancedaysint-tolerancedays)

Define los días de tolerancia que se tendrán para renovar una vez terminado el periodo.

- `$toleranceDays`: Días de tolerancia

Si no se llama a este método o se ejecuta con un valor menor a 0 entonces se definirá a `0`

#### `setDefault()`

[](#setdefault)

Define el periodo como el periodo default, solo puede haber un periodo default en un mismo plan por lo que si ya existia un periodo default será remplazado como default por el actual.

#### `create()`

[](#create)

Termina la construcción del periodo creando una nueva instancia en base de datos, esto devuleve una instancia de `PlanPeriod`.

**Importante:** Si no se llama a ninguno de los métodos `setRecurringPeriod` y `setLimitedNonRecurringPeriod` entonces la suscripción será ilimitada ignorando el periodo de prueba y los días de tolerancia.

### Ejemplo de uso

[](#ejemplo-de-uso)

```
use Subscriptions;
use Emeefe\Subscriptions\Models\PlanPeriod;

...

$period = Subscriptions::period('Monthly', 'monthly', $plan)
    ->setPrice(100)
    ->setTrialDays(10)
    ->setRecurringPeriod(1, PlanPeriod::UNIT_MONTH)
    ->setToleranceDays(5)
    ->create();
```

### Suscribir modelos

[](#suscribir-modelos)

Como se mencionó anteriormente se utilizan relaciones polimórficas para que cualquier modelo pueda suscribirse a periodos y sea mas sencillo y limpio su uso. Para esto se utiliza el trait `CanSubscribe` de la siguiente manera:

```
use Emeefe\Subscriptions\Traits\CanSubscribe;

...

class User extends Model{
    use CanSubscribe;
}
```

```
if($user->subscribeTo($period)){
    echo "Suscrito";
}else{
    echo "No se pueden tener dos suscripciones sobre el mismo tipo de plan";
}
```

Hecho esto se creará una suscripción ligada al modelo, en este caso usuario, al plan y al periodo.

La estructura de la función es la siguiente:

`subscribeTo(PlanPeriod $period, int $periodCount = 1)`

- `$period`: Instancia del periodo al que se suscribirá el modelo
- `$periodCount`: Ciclos del periodo por los que se suscribirá inicialmente el modelo, solo cuando no es ilimitado

Para la suscripción a periodos mensuales se usan las isguientes reglas especiales:

- Si se suscribe en una fecha con día 29, 30 o 31 y que no todos los meses lo pueden tener entonces al sumar un mes se pone el mismo día o el menor más cercano siempre tomando en cuenta el día de inicio, por ejemplo:
    - Un modelo se suscribe el 31 de enero de 2020 por un mes, entonces caduca el 29 de febrero de 2020.
    - Si dicha suscripción se renueva otro mes no se define la fecha de expiración al 29 de Marzo si no que se conserva el día 31 definiendo 31 de Marzo de 2020.
    - Las siguientes fechas por cada mes serian 30 de Abril, 31 de Mayo, 30 de Junio, etc.

### Verificar suscripción

[](#verificar-suscripción)

Se puede verificar la suscripción de un modelo a un tipo de plan por medio del método `hasSubscription($planTypeOrType)` donde `$planTypeOrType` es una instancia del tipo de modelo o el `string` definido en la propiedad `type` del tipo de plan.

```
if($user->hasSubscription('user_membership')){
    echo "El usuario está suscrito al tipo de plan con tipo = user_membership";
}else{
    echo "El usuario no está suscrito al tipo de plan con tipo = user_membership";
}
```

### Obtener suscripción

[](#obtener-suscripción)

Para obtener la suscripción actual se usa el método `currentSubscription($planTypeOrType)`, la suscripción actual es la última suscripción creada sobre el modelo que se suscribe, aunque la suscripción esté cancelada será devuelta por este método.

```
if($user->currentSubscription($planType)){
    echo "El usuario tiene una suscripción";
}else{
    echo "El usuario no tiene una suscripción";
}
```

### Renovar suscripción

[](#renovar-suscripción)

Cuando una suscripción es recurrente esta puede ser renovada por un numero entero de periodos. Una suscripción no recurrente no puede ser renovada.

```
$subscription = $user->currentSubscription($planType);

if($subscription->renew(3)){
    echo "La suscripción ha sido renovada 3 periodos";
}else{
    echo "La suscripción no puede ser renovada";
}
```

### Cancelar suscripción

[](#cancelar-suscripción)

Una suscripción puede ser cancelada usando el método `cancel()` de la suscripción.

```
$subscription = $user->currentSubscription($planType);

if($subscription->cancel()){
    echo "La suscripción ha sido cancelada";
}else{
    echo "La suscripción ya ha sido cancelada";
}
```

### Actualizar suscripción

[](#actualizar-suscripción)

Actualiza de una suscripción válida a una nueva cambiando de periodo ya sea del mismo plan o de otro plan por medio del método `updateSubscriptionTo` del trait `CanSubscribe`.

Una actualización de suscripción no es más que una cancelación de la suscripción válida actual y una asignación a una nueva, cuando la actualización se ejecuta de la manera aquí explicada no se lanza el evento `CancelSubscription` sino el evento `UpdatedSubscription`.

**IMPORTANTE:** Otro punto a tomar en cuenta es que al cambiar de plan se sincronizan los features de límite a la nueva suscripción definiendo el consumo en la nueva suscripción como el mínimo entre el consumo de la antigua suscripción y el límite de la nueva suscripción, si se desea realizar alguna acción extra en la lógica de la aplicación se deberá ejecutar en un Listener del evento `UpdatedSubscription`.

```
...

if($user->updateSubscriptionTo($newPeriod)){
    echo "La suscripción ha sido actualizada";
}else{
    echo "La actualización de suscripción no se pudo realizar";
}
```

Modelos
=======

[](#modelos)

PlanType
--------

[](#plantype)

### Métodos

[](#métodos)

#### `hasFeature(string $featureCode)`

[](#hasfeaturestring-featurecode)

Verifica si el tipo de plan contiene el feature con el código `$featureCode` y devuelve un `boolean` dependiendo el caso. Si el `$featureCode` no existe o no esta asociado al tipo devuelve `false`.

#### `attachFeature(PlanFeature $planFeature)`

[](#attachfeatureplanfeature-planfeature)

Asigna una instancia de `PlanFeature` al tipo de plan y regresa la instancia de `PlanType` para encadenar varias asignaciones de features. En caso de mandar un feature que ya está ligado ignora la asignación sin devolver errores.

#### `getFeatureByCode(string $featureCode)`

[](#getfeaturebycodestring-featurecode)

Obtiene una instancia de `PlanFeature` a través de un código pasado en `$featureCode`, en caso de no existir una relción con el tipo de plan devuelve `null`.

### Relaciones

[](#relaciones)

#### `features`

[](#features)

Obtiene la colección de `PlanFeatures` relacionados al tipo de plan.

#### `plans`

[](#plans)

Obtiene la colección de `Plan` relacionados al tipo de plan.

#### `subscriptions`

[](#subscriptions-1)

Obtiene la colección de `PlanSubscription` relacionados al tipo de plan.

---

PlanFeature
-----------

[](#planfeature)

### Scopes

[](#scopes)

#### `scopeLimitType($query)`

[](#scopelimittypequery)

Filtra features por tipo `limit`

#### `scopeFeatureType($query)`

[](#scopefeaturetypequery)

Filtra features por tipo `feature`

---

Plan
----

[](#plan)

El modelo de plan aplica un scope global para devolver siempre los planes visibles, esto se puede desactivar usando el scope `withHiddens` explicado más abajo.

### Métodos

[](#métodos-1)

#### `assignFeatureLimitByCode(int $limit, string $featureCode)`

[](#assignfeaturelimitbycodeint-limit-string-featurecode)

Asigna un feature del tipo `limit` al plan así como su límite, en el caso de aún no tener límite asignado entonces lo asigna y para el caso en que ya ha sido definido un límite lo actualiza.

- `$limit`: Límite a asignar, número mayor o igual a 1
- `$featureCode`: Código del feature

Devuelve:

- `true`: Cuando se pudo asignar el feature y límite
- `false`: Cuando no se pudo asignar el límite debido a que no existe el feature dentro del tipo de plan o el feature no es del tipo `limit`

#### `assignUnlimitFeatureByCode(string $featureCode)`

[](#assignunlimitfeaturebycodestring-featurecode)

Asigna un feature del tipo `feature` al plan.

- `$featureCode`: Código del feature

Devuelve:

- `true`: Cuando se pudo asignar el feature
- `false`: Cuando no se pudo asignar debido a que no existe el feature dentro del tipo de plan o el feature no es del tipo `feature`

#### `getFeatureLimitByCode($featureCode)`

[](#getfeaturelimitbycodefeaturecode)

Obtiene el límite de un feature del tipo `limit` a partir de su código

- `$featureCode`: El código del feature

Devuelve:

- int mayor a 0: Cuando el feature existe en el tipo de plan y tiene límite registrado
- `0`: Cuando el feature existe en el tipo de plan pero no tiene límite asignado
- `-1`: Cuando el feature no existe en el tipo de plan o existe pero no es del tipo `limit`

#### `hasFeature(string $featureCode)`

[](#hasfeaturestring-featurecode-1)

Verifica si el tipo del plan tiene un feature asociado.

- `$featureCode`: El código del feature

Devuelve:

- `true`: Cuando el feature existe en el tipo del plan
- `false`: Cuando el feature no existe en el tipo del plan

#### `setAsDefault()`

[](#setasdefault)

Define el plan como default dentro del tipo plan, si ya existia un plan que era el default entonces se reasigna esta característica actualizando el nuevo y quitando la característica al antiguo default.

Devuelve `bool`

#### `setAsVisible()`

[](#setasvisible)

Define el plan como visible

Devuelve `bool`

#### `setAsHidden()`

[](#setashidden)

Define el plan como oculto

Devuelve `bool`

Relaciones
----------

[](#relaciones-1)

#### `type`

[](#type)

Obtiene la el tipo de plan al que se encuentra asociado el plan.

#### `features`

[](#features-1)

Obtiene la colección de features asociados al plan a través de su tipo

Scopes
------

[](#scopes-1)

#### `scopeByType($query, string $type)`

[](#scopebytypequery-string-type)

Obtiene planes según la clave de su tipo.

- `$type`: Clave del tipo de plan

#### `scopeVisible($query)`

[](#scopevisiblequery)

Filtra planes visibles

#### `scopewithHiddens($query)`

[](#scopewithhiddensquery)

Permite obtener también los planes ocultos

#### `scopeHidden($query)`

[](#scopehiddenquery)

Filtra planes ocultos, es necesario llamar antes a `scopewithHiddens($query)` para obtenerlos

---

PlanPeriod
----------

[](#planperiod)

El modelo de PlanPeriod aplica un scope global para devolver siempre los periodos visibles, esto se puede desactivar usando el scope `withHiddens` explicado más abajo.

### Métodos

[](#métodos-2)

#### `isRecurring()`

[](#isrecurring)

Checa si el periodo es recurrente

Devuelve `bool`

#### `isLimitedNonRecurring()`

[](#islimitednonrecurring)

Checa si el periodo es no recurrente limitado

Devuelve `bool`

#### `isUnlimitedNonRecurring()`

[](#isunlimitednonrecurring)

Checa si el periodo es no recurrente ilimitado

Devuelve `bool`

#### `isVisible()`

[](#isvisible)

Checa si el el periodo es visible

Devuelve `bool`

#### `isHidden()`

[](#ishidden)

Checa si el periodo está oculto

#### `isDefault`

[](#isdefault)

Checa si el periodo es el periodo default dentro del tipo de plan

Devuelve `bool`

#### `isFree()`

[](#isfree)

Checa si el periodo es gratuito, en otras palabras, su precio es `0`

Devuelve `bool`

#### `hasTrial()`

[](#hastrial)

Checa si el periodo tiene periodo de prueba

Devuelve `bool`

#### `setAsDefault()`

[](#setasdefault-1)

Define el periodo como default dentro del plan, si ya existia un periodo que era el default entonces se reasigna esta característica actualizando el nuevo y quitando la característica al antiguo default.

Devuelve `bool`

#### `setAsVisible()`

[](#setasvisible-1)

Define el periodo como visible

Devuelve `bool`

#### `setAsHidden()`

[](#setashidden-1)

Define el periodo como oculto

Devuelve `bool`

### Relaciones

[](#relaciones-2)

#### `plan`

[](#plan-1)

Devuelve el plan asociado

#### `subscriptions`

[](#subscriptions-2)

Devuelve las suscripciones asociadas

### Scopes

[](#scopes-2)

#### `scopeVisible($query)`

[](#scopevisiblequery-1)

Filtra periodos visibles

#### `scopewithHiddens($query)`

[](#scopewithhiddensquery-1)

Permite obtener también los periodos ocultos

#### `scopeHidden($query)`

[](#scopehiddenquery-1)

Filtra periodos ocultos, es necesario llamar antes a `scopewithHiddens($query)` para obtenerlos

---

PlanSubscription
----------------

[](#plansubscription)

Métodos
-------

[](#métodos-3)

#### `isOnTrial()`

[](#isontrial)

Checa si la suscripción se encuentra en periodo de prueba.

Devuelve:

- `true`: En caso de si estar en periodo de prueba
- `false`: En caso de no estar en periodo de prueba

#### `isActive()`

[](#isactive)

Checa si la suscripción se encuentra en periodo normal.

Devuelve:

- `true`: En caso de si estar activa
- `false`: En caso de no estar activa

#### `isValid()`

[](#isvalid)

Checa si la suscripción es válida.

Devuelve:

- `true`: En caso de si ser válida
- `false`: En caso de no ser válida

#### `isExpiredWithTolerance()`

[](#isexpiredwithtolerance)

Checa si la suscripción ha llegado a su fecha de expiración pero se encuentra en el periodo de tolerancia, muy útil para verificar pagos.

Devuelve:

- `true`: En caso de si encontrarse en periodo de tolerancia
- `false`: En caso de no encontrarse en periodo de tolerancia

#### `isFullExpired()`

[](#isfullexpired)

Checa si la suscripción ha expirado y no se encuentra dentro de un periodo de tolerancia, muy útil para verificar pagos.

Devuelve:

- `true`: En caso de estar expirada
- `false`: En caso de no estar expirada

#### `isCancelled()`

[](#iscancelled)

Checa si la sucripción está cancelada

Devuelve:

- `true`: En caso de estar cancelada
- `false`: En caso de no estar cancelada

#### `isUnlimited()`

[](#isunlimited)

Checa si la sucripción es ilimitada

Devuelve:

- `true`: En caso de ser ilimitadaa
- `false`: En caso de no ser ilimitada

#### `isLimited()`

[](#islimited)

Checa si la sucripción es limitada

Devuelve:

- `true`: En caso de ser limitada
- `false`: En caso de no ser limitada

#### `remainingTrialDays()`

[](#remainingtrialdays)

Devuelve la cantidad de días de prueba restantes

Devuelve:

- `int`: Días restantes

#### `renew(int $periods = 1)`

[](#renewint-periods--1)

Renueva la suscripción solo si es recurrente y no está cancelada

- `$periods`: Cantidad de periodos a renovar, por default `1`

Devuelve:

- `true`: Cuando la suscripción es recurrente y se renueva exitósamente
- `false`: Cuando la suscripción es no recurrente o está cancelada

#### `cancel(string $reason = null)`

[](#cancelstring-reason--null)

Cancela la suscripción solo si no está cancelada. Si se cancela una suscripción **ilimitada** entonces define su fecha de expiración a la fecha en que se cancela.

- `$reason`: Razón por la cuál se cancela la suscripción.

Devuelve `bool`

#### `hasFeature(string $featureCode)`

[](#hasfeaturestring-featurecode-2)

Checa si la suscripción tiene un feature a partir de su código.

- `$featureCode`: Código del feature

Devuelve `bool`

#### `consumeFeature(string $featureCode, int $units = 1)`

[](#consumefeaturestring-featurecode-int-units--1)

Consume una unidad de las unidades disponibles en la suscripción de un feature siempre y cuando la suscripción no esté cancelada.

- `$featureCode`: Código del feature
- `$units`: Unidades a consumir, por default `1`

Devuelve `bool`

- `true`: Si se puede consumir
- `false`: Si no se puede consumir debido a que ya llegó al límite o las unidades a consumir son mayores a las disponibles.

#### `unconsumeFeature(string $featureCode, int $units = 1)`

[](#unconsumefeaturestring-featurecode-int-units--1)

Desconsume una unidad de las unidades consumidas en la suscripción de un feature siempre y cuando la suscripción no esté cancelada.

- `$featureCode`: Código del feature
- `$units`: Unidades a desconsumir, por default `1`

Devuelve `bool`

- `true`: Si se puede desconsumir
- `false`: Si no se puede desconsumir debido a que la cantidad de unidades consumidas es `0`

#### `getUnitsOf(string $featureCode)`

[](#getunitsofstring-featurecode)

Devuelve el total de un feature limitado relacionado a la suscripción

- `$featureCode`: Código del feature

Devuelve `int` o `null`

- `int`: Si se puede obtener el total
- `null`: Si el feature no está relacionado o no es del tipo `limit`

#### `getUsageOf(string $featureCode)`

[](#getusageofstring-featurecode)

Devuelve el uso de un feature relacionado a la suscripción

- `$featureCode`: Código del feature

Devuelve `int` o `null`

- `int`: Si se puede obtener el uso
- `null`: Si el feature no está relacionado o no es del tipo `limit`

#### `getRemainingOf(string $featureCode)`

[](#getremainingofstring-featurecode)

Devuelve el uso restante de un feature relacionado a la suscripción

- `$featureCode`: Código del feature

Devuelve `int` o `null`

- `int`: Si se puede obtener el uso restante
- `null`: Si el feature no está relacionado o no es del tipo `limit`

Relaciones
----------

[](#relaciones-3)

#### `period`

[](#period)

Devuelve el periodo relacionado

#### `subscriber`

[](#subscriber)

Devuelve el modelo suscriptor asociado por la relación polimórfica

#### `plan_type`

[](#plan_type)

Devuelve el tipo de plan asociado

Scopes
------

[](#scopes-3)

#### `scopeByType($query, PlanType $planType)`

[](#scopebytypequery-plantype-plantype)

Filtra suscripciones por su tipo de plan

#### `scopeCanceled($query)`

[](#scopecanceledquery)

Filtra suscripciones canceladas

#### `scopeFree($query)`

[](#scopefreequery)

Filtra suscripciones gratuitas donde su campo `price` es `0`

#### `recurring($query)`

[](#recurringquery)

Filtra suscripciones recurrentes

Eventos
=======

[](#eventos)

Este paquete ofrece eventos que son lanzados en las diversas circunstancias más importantes o que nos permiten adaptar las suscripciones a la mayoria de casos posibles.

`Emeefe\Subscriptions\Events\FeatureLimitChangeOnPlan`Se lanza cuando se actualiza el límite de un feature en un plan, tanto para la primera vez que se asigna límite como también cuando se actualiza.

- `$event->plan`: El plan al que se asigna el feature limit
- `$event->feature`: El feature al que se le asignará el límite
- `$event->limit`: El nuevo límite que se asigna

`Emeefe\Subscriptions\Events\PlanPeriodChange`Se lanza cuando un periodo de plan es actualizado en alguno de los campos `price`, `currency`, `trial_days`, `period_unit`, `period_count`, `is_recurring`, `is_visible` o `tolerance_days`.

- `$event->oldPlanPeriod`: Antiguo periodo de plan
- `$event->newPlanPeriod`: Periodo de plan actualizado

`Emeefe\Subscriptions\Events\NewFeatureOnPlan`Se lanza cuando un feature es asignado a un plan

- `$event->plan`: Plan al que se asignó el feature
- `$event->feature`: Feature asignado
- `$event->limit`: Límite definido en caso de ser feature del tipo `limit`, en otro caso es `null`

`Emeefe\Subscriptions\Events\NewSubscription`Se lanza cuando un modelo se suscribe a un plan por medio de un periodo

- `$event->model`: El modelo que se suscribe
- `$event->subscription`: La suscripción creada

`Emeefe\Subscriptions\Events\RenewSubscription`Se lanza cuando una suscripción es renovada/extendida

- `$event->model`: El modelo al que pertenece la suscripción
- `$event->subscription`: La suscripción que se renueva
- `$event->cycles`: Cantidad de ciclos a renovar

`Emeefe\Subscriptions\Events\CancelSubscription`Se lanza cuando una subscripción es cancelada, usando el método `cancel`. Cuando es una cancelación por actualización de plan usando el método `updateSubscriptionTo` del trait `CanSubscribe` no se hace la llamada a este evento y se define su motivo de cancelación a `PlanSubscription::CANCEL_REASON_UPDATE_SUBSCRIPTION`

- `$event->subscription`: La suscripción cancelada
- `$event->reason`: El motivo de la cancelación proporcionado en `cancel($reason)`

`Emeefe\Subscriptions\Events\FeatureConsumed`Se lanza cuando un feature de la suscripción es consumido

- `$event->subscription`: Suscripción de la cuál se consume
- `$event->model`: Modelo suscrito
- `$event->units`: Unidades consumidas

`Emeefe\Subscriptions\Events\FeatureUnconsumed`Se lanza cuando un feature de la suscripción es "desconsumido"

- `$event->subscription`: Suscripción de la cuál se consume
- `$event->model`: Modelo suscrito
- `$event->units`: Unidades "desconsumidas"

`Emeefe\Subscriptions\Events\FeatureRemovedFromPlan`Se lanza cuando un feature es eliminado del plan

- `$event->plan`: Plan del que se eliminó el feature
- `$event->feature`: Feature eliminado del plan

`Emeefe\Subscriptions\Events\UpdatedSubscription`Se lanza cuando se actualiza suscripción usando el método `updateSubscriptionTo` del trait `CanSubscribe`

- `$event->model`: Modelo suscrito
- `$event->oldSubscription`: Suscripción anterior
- `$event->subscription`: Nueva suscripción

###  Health Score

26

—

LowBetter than 41% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity10

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity55

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 59.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~12 days

Recently: every ~18 days

Total

7

Last Release

2114d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/87ad10cb888c2954bd44c0572a4435c8fc03bd925206831926dec73afbbdd530?d=identicon)[abraham\_emeefe](/maintainers/abraham_emeefe)

---

Top Contributors

[![richiexjosemeefe](https://avatars.githubusercontent.com/u/63668256?v=4)](https://github.com/richiexjosemeefe "richiexjosemeefe (34 commits)")[![abraham-emeefe](https://avatars.githubusercontent.com/u/53948102?v=4)](https://github.com/abraham-emeefe "abraham-emeefe (21 commits)")[![abrahamf24](https://avatars.githubusercontent.com/u/42507152?v=4)](https://github.com/abrahamf24 "abrahamf24 (2 commits)")

---

Tags

laravelsubscriptionssubscriptionplanplansperiodsemeefe

### Embed Badge

![Health badge](/badges/emeefe-subscriptions/health.svg)

```
[![Health](https://phpackages.com/badges/emeefe-subscriptions/health.svg)](https://phpackages.com/packages/emeefe-subscriptions)
```

###  Alternatives

[duncanmcclean/statamic-cargo

Comprehensive e-commerce addon for Statamic. Build bespoke e-commerce sites without the complexity.

3416.9k](/packages/duncanmcclean-statamic-cargo)[ptuchik/billing

Billing package for Laravel 5.5 supporting packages, plans, coupons, addons, payments and subscriptions

3021.2k](/packages/ptuchik-billing)[creatydev/plans

Laravel Plans is a package for SaaS apps that need management over plans, features, subscriptions, events for plans or limited, countable features.

421.2k](/packages/creatydev-plans)[helori/laravel-saas

Software as a Service scaffholding for Laravel based on Vue 3, Tailwindcss and Stripe. Inspired by Laravel Jetstream and Spark.

121.4k](/packages/helori-laravel-saas)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
