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

Overview

Laravel Plans

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

Laravel Cashier


While Laravel Cashier does this job really well, there are some features that can be useful for SaaS apps:

  • Countable, limited features - If you plan to limit the amount of resources a subscriber can have and track the usage, this package does that for you.
  • Event-driven by nature - you can listen for events. What if you can give 3 free days to the next subscription if your users pay their invoice in time?

Installation


Install the package:

$ composer require abr4xas/laravel-plans

If your Laravel version does not support package discovery, add this line in the providers array in your config/app.php file:

Abr4xas\Plans\PlansServiceProvider::class,

Publish the config file & migration files:

$ php artisan vendor:publish --provider=Abr4xas\Plans\PlansServiceProvider

Migrate the database:

$ php artisan migrate

Add the HasPlans trait to your Eloquent model:

use Abr4xas\Plans\Traits\HasPlans;

class User extends Model {
    use HasPlans;
    ...
}

Creating plans


The basic unit of the subscription-like system is a plan. You can create it using Abr4xas\Plans\Models\Plan or your model, if you have implemented your own.

$plan = Plan::create([
    'name' => 'Enterprise',
    'description' => 'The biggest plans of all.',
    'duration' => 30, // in days
]);

Features


Each plan has features. They can be either countable, and those are limited or unlimited, or there just to store the information, such a specific permission.

Marking a feature type can be done using:

  • feature, is a single string, that do not needs counting. For example, you can store permissions.
  • limit, is a number. For this kind of feature, the limit attribute will be filled. It is meant to measure how many of that feature the user has consumed, from this subscription. For example, you can count how many build minutes this user has consumed during the month (or during the Cycle, which is 30 days in this example)

Note: For unlimited feature, the limit field will be set to any negative value.

To attach features to your plan, you can use the relationship features() and pass as many Abr4xas\Plans\Models\Featureinstances as you need:

$plan->features()->saveMany([
    new Feature([
        'name' => 'Vault access',
        'code' => 'vault.access',
        'description' => 'Offering access to the vault.',
        'type' => 'feature',
    ]),
    new Feature([
        'name' => 'Build minutes',
        'code' => 'build.minutes',
        'description' => 'Build minutes used for CI/CD.',
        'type' => 'limit',
        'limit' => 2000,
    ]),
    new Feature([
        'name' => 'Users amount',
        'code' => 'users.amount',
        'description' => 'The maximum amount of users that can use the app at the same time.',
        'type' => 'limit',
        'limit' => -1, // or any negative value
    ]),
    ...
]);

Later, you can retrieve the permissions directly from the subscription:

$subscription->features()->get(); // All features
$subscription->features()->code($codeId)->first(); // Feature with a specific code.
$subscription->features()->limited()->get(); // Only countable/unlimited features.
$subscription->features()->feature()->get(); // Uncountable, permission-like features.

Subscribing to plans


Your users can be subscribed to plans for a certain amount of days or until a certain date.

$subscription = $user->subscribeTo($plan, 30); // 30 days
$subscription->remainingDays(); // 29 (29 days, 23 hours, ...)

By default, the plan is marked as recurring, so it's eligible to be extended after it expires, if you plan to do so like it's explained in the Recurrency section below.

If you don't want a recurrent subscription, you can pass false as a third argument:

$subscription = $user->subscribeTo($plan, 30, false); // 30 days, non-recurrent

If you plan to subscribe your users until a certain date, you can pass strngs containing a date, a datetime or a Carbon instance.

If your subscription is recurrent, the amount of days for a recurrency cycle is the difference between the expiring date and the current date.

$user->subscribeToUntil($plan, '2018-12-21');
$user->subscribeToUntil($plan, '2018-12-21 16:54:11');
$user->subscribeToUntil($plan, Carbon::create(2018, 12, 21, 16, 54, 11));
$user->subscribeToUntil($plan, '2018-12-21', false); // no recurrency

Note: If the user is already subscribed, the subscribeTo() will return false. To avoid this, upgrade or extend the subscription.

Upgrading subscription


Upgrading the current subscription's plan can be done in two ways: it either extends the current subscription with the amount of days passed or creates another one, in extension to this current one.

Either way, you have to pass a boolean as the third parameter. By default, it extends the current subscription.

// The current subscription got longer with 60 days.
$currentSubscription = $user->upgradeCurrentPlanTo($anotherPlan, 60, true);

// A new subscription, with 60 days valability, starting when the current one ends.
$newSubscription = $user->upgradeCurrentPlanTo($anotherPlan, 60, false);

Just like the subscribe methods, upgrading also support dates as a third parameter if you plan to create a new subscription at the end of the current one.

$user->upgradeCurrentPlanToUntil($anotherPlan, '2018-12-21', false);
$user->upgradeCurrentPlanToUntil($anotherPlan, '2018-12-21 16:54:11', false);
$user->upgradeCurrentPlanToUntil($anotherPlan, Carbon::create(2018, 12, 21, 16, 54, 11), false);

Passing a fourth parameter is available, if your third parameter is false, and you should pass it if you'd like to mark the new subscription as recurring.

// Creates a new subscription that starts at the end of the current one, for 30 days and recurrent.
$newSubscription = $user->upgradeCurrentPlanTo($anotherPlan, 30, false, true);

Extending current subscription


Upgrading uses the extension methods, so it uses the same arguments, but you do not pass as the first argument a Plan model:

// The current subscription got extended with 60 days.
$currentSubscription = $user->extendCurrentSubscriptionWith(60, true);

// A new subscription, which starts at the end of the current one.
$newSubscription = $user->extendCurrentSubscriptionWith(60, false);

// A new subscription, which starts at the end of the current one and is recurring.
$newSubscription = $user->extendCurrentSubscriptionWith(60, false, true);

Extending also works with dates:

$user->extendCurrentSubscriptionUntil('2018-12-21');

Cancelling subscriptions


You can cancel subscriptions. If a subscription is not finished yet (it is not expired), it will be marked as pending cancellation. It will be fully cancelled when the expiration dates passes the current time and is still cancelled.

// Returns false if there is not an active subscription.
$user->cancelCurrentSubscription();
$lastActiveSubscription = $user->lastActiveSubscription();

$lastActiveSubscription->isCancelled(); // true
$lastActiveSubscription->isPendingCancellation(); // true
$lastActiveSubscription->isActive(); // false

$lastActiveSubscription->hasStarted();
$lastActiveSubscription->hasExpired();

Consuming countable features


To consume the limit type feature, you have to call the consumeFeature() method within a subscription instance.

To retrieve a subscription instance, you can call activeSubscription() method within the user that implements the trait. As a pre-check, don't forget to call hasActiveSubscription() from the user instance to make sure it is subscribed to it.

if ($user->hasActiveSubscription()) {
    $subscription = $user->activeSubscription();
    $subscription->consumeFeature('build.minutes', 10);

    $subscription->getUsageOf('build.minutes'); // 10
    $subscription->getRemainingOf('build.minutes'); // 1990
}

The consumeFeature() method will return:

  • false if the feature does not exist, the feature is not a limit or the amount is exceeding the current feature allowance
  • true if the consumption was done successfully
// Note: The remaining of build.minutes is now 1990

$subscription->consumeFeature('build.minutes', 1991); // false
$subscription->consumeFeature('build.hours', 1); // false
$subscription->consumeFeature('build.minutes', 30); // true

$subscription->getUsageOf('build.minutes'); // 40
$subscription->getRemainingOf('build.minutes'); // 1960

If consumeFeature() meets an unlimited feature, it will consume it and it will also track usage just like a normal record in the database, but will never return false. The remaining will always be -1 for unlimited features.

The revering method for consumeFeature() method is unconsumeFeature(). This works just the same, but in the reverse:

// Note: The remaining of build.minutes is 1960

$subscription->consumeFeature('build.minutes', 60); // true

$subscription->getUsageOf('build.minutes'); // 100
$subscription->getRemainingOf('build.minutes'); // 1900

$subscription->unconsumeFeature('build.minutes', 100); // true
$subscription->unconsumeFeature('build.hours', 1); // false

$subscription->getUsageOf('build.minutes'); // 0
$subscription->getRemainingOf('build.minutes'); // 2000

Using the unconsumeFeature() method on unlimited features will also reduce usage, but it will never reach negative values.

Events


When using subscription plans, you want to listen for events to automatically run code that might do changes for your app.

Events are easy to use. If you are not familiar, you can check Laravel's Official Documentation on Events.

All you have to do is to implement the following Events in your EventServiceProvider.php file. Each event will have it's own members than can be accessed through the $event variable within the handle() method in your listener.

$listen = [
    ...
    \Abr4xas\Plans\Events\CancelSubscription::class => [
        // $event->model = The model that cancelled the subscription.
        // $event->subscription = The subscription that was cancelled.
    ],
    \Abr4xas\Plans\Events\NewSubscription::class => [
        // $event->model = The model that was subscribed.
        // $event->subscription = The subscription that was created.
    ],
     \Abr4xas\Plans\Events\NewSubscriptionUntil::class => [
        // $event->model = The model that was subscribed.
        // $event->subscription = The subscription that was created.
    ],
    \Abr4xas\Plans\Events\ExtendSubscription::class => [
        // $event->model = The model that extended the subscription.
        // $event->subscription = The subscription that was extended.
        // $event->startFromNow = If the subscription is exteded now or is created a new subscription, in the future.
        // $event->newSubscription = If the startFromNow is false, here will be sent the new subscription that starts after the current one ends.
    ],
    \Abr4xas\Plans\Events\ExtendSubscriptionUntil::class => [
        // $event->model = The model that extended the subscription.
        // $event->subscription = The subscription that was extended.
        // $event->expiresOn = The Carbon instance of the date when the subscription will expire.
        // $event->startFromNow = If the subscription is exteded now or is created a new subscription, in the future.
        // $event->newSubscription = If the startFromNow is false, here will be sent the new subscription that starts after the current one ends.
    ],
    \Abr4xas\Plans\Events\UpgradeSubscription::class => [
        // $event->model = The model that upgraded the subscription.
        // $event->subscription = The current subscription.
        // $event->startFromNow = If the subscription is upgraded now or is created a new subscription, in the future.
        // $event->oldPlan = Here lies the current (which is now old) plan.
        // $event->newPlan = Here lies the new plan. If it's the same plan, it will match with the $event->oldPlan
    ],
    \Abr4xas\Plans\Events\UpgradeSubscriptionUntil::class => [
        // $event->model = The model that upgraded the subscription.
        // $event->subscription = The current subscription.
        // $event->expiresOn = The Carbon instance of the date when the subscription will expire.
        // $event->startFromNow = If the subscription is upgraded now or is created a new subscription, in the future.
        // $event->oldPlan = Here lies the current (which is now old) plan.
        // $event->newPlan = Here lies the new plan. If it's the same plan, it will match with the $event->oldPlan
    ],
    \Abr4xas\Plans\Events\FeatureConsumed::class => [
        // $event->subscription = The current subscription.
        // $event->feature = The feature that was used.
        // $event->used = The amount used.
        // $event->remaining = The total amount remaining. If the feature is unlimited, will return -1
    ],
     \Abr4xas\Plans\Events\FeatureUnconsumed::class => [
        // $event->subscription = The current subscription.
        // $event->feature = The feature that was used.
        // $event->used = The amount reverted.
        // $event->remaining = The total amount remaining. If the feature is unlimited, will return -1
    ],
];

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

You might also like...
JsonQ is a simple, elegant PHP package to Query over any type of JSON Data

php-jsonq JsonQ is a simple, elegant PHP package to Query over any type of JSON Data. It'll make your life easier by giving the flavour of an ORM-like

Do you want CronJob to follow the solar date?You need this package to solve this problem.
Do you want CronJob to follow the solar date?You need this package to solve this problem.

Shamsic Maybe it happened to you that you wanted to use CronJob in your project and you realized that you cannot manage the exact dates that are in th

Paddle.com API integration for Laravel with support for webhooks/events

Laravel Paddle This package provides an integration with Paddle.com for Laravel. Read the blogpost about the introduction of the package! Features Sup

Paddle.com API integration for Laravel with support for webhooks/events

Laravel Paddle This package provides an integration with Paddle.com for Laravel. Read the blogpost about the introduction of the package! Features Sup

Laravel Pipeline with DB transaction support, events and additional methods
Laravel Pipeline with DB transaction support, events and additional methods

Laravel Enhanced Pipeline Laravel Pipeline with DB transaction support, events and additional methods #StandWithUkraine Installation Install the packa

Dispatcher is a Laravel artisan command scheduling tool used to schedule artisan commands within your project so you don't need to touch your crontab when deploying.
Dispatcher is a Laravel artisan command scheduling tool used to schedule artisan commands within your project so you don't need to touch your crontab when deploying.

Dispatcher Dispatcher allows you to schedule your artisan commands within your Laravel project, eliminating the need to touch the crontab when deployi

The Current US Version of PHP-Nuke Evolution Xtreme v3.0.1b-beta often known as Nuke-Evolution Xtreme. This is a hardened version of PHP-Nuke and is secure and safe. We are currently porting Xtreme over to PHP 8.0.3
The Current US Version of PHP-Nuke Evolution Xtreme v3.0.1b-beta often known as Nuke-Evolution Xtreme. This is a hardened version of PHP-Nuke and is secure and safe. We are currently porting Xtreme over to PHP 8.0.3

2021 Nightly Builds Repository PHP-Nuke Evolution Xtreme Developers TheGhost - Ernest Allen Buffington (Lead Developer) SeaBeast08 - Sebastian Scott B

Fact Extraction and VERification Over Unstructured and Structured information

Repository for Fact Extraction and VERification Over Unstructured and Structured information (FEVEROUS), used for the FEVER Workshop Shared Task at EMNLP2021.

This Statamic addon allows you to modify the tags rendered by the Bard fieldtype, giving you full control over the final HTML.

Bard Mutator This Statamic addon allows you to modify the tags rendered by the Bard fieldtype, giving you full control over the final HTML. You can ad

Comments
Owner
ángel
Backend developer
ángel
QaraTMS is open source test case, test suites, test plans and test runs management tool.

QaraTMS - Open Source Test Management System QaraTMS is open source test management software for managing test suites, test cases, test plans, test ru

Alex H 29 Dec 22, 2022
laminas-memory manages data in an environment with limited memory

Memory objects (memory containers) are generated by the memory manager, and transparently swapped/loaded when required.

Laminas Project 5 Jul 26, 2022
zend-memory manages data in an environment with limited memory

Memory objects (memory containers) are generated by the memory manager, and transparently swapped/loaded when required.

Zend Framework 16 Aug 29, 2020
EBook-Apps - The eBook Apps is a web application that helps you browse ebooks from anywhere using your smartphone and laptop.

⚡️ eBook Apps The eBook Apps is a web application that helps you browse ebooks from anywhere using your smartphone and laptop. ?? Getting Started To s

Ahmad Fauzy 32 Nov 14, 2022
A plugin to make life easier for users who need to edit specific functions of a world and also create, rename and delete worlds quickly using commands or the world management menu.

A plugin to make life easier for users who need to edit specific functions of a world and also create, rename and delete worlds quickly using commands or the world management menu.

ImperaZim 0 Nov 6, 2022
Stores the customer_user for WooCommerce orders and subscriptions in the post_author column of posts table.

Post Author Optimization for WooCommerce Requires PHP: 7.0 WP requires at least: 5.7 WP tested up to: 5.7 WC requires at least: 5.6.0 WC tested up to:

Devin Price 9 Apr 2, 2022
An open source tool that lets you create a SaaS website from docker images in 10 minutes.

简体中文 Screenshots for members ( who subscribe the plan ) for admin ⚠️ This document was translated into English by deepl and can be improved by PR An o

Easy 669 Jan 5, 2023
Agora Open source SaaS billing system for software companies

About Agora Invocing Billing and subscription management for SaaS & other software businesses. Handling signups, provisioning, billing and support Ago

Ladybird Web Solution 158 Dec 17, 2022
Enterprise Modular SAAS Framework, Design from the growndup to grow vertically.

Kwerio Enterprise Modular SAAS Framework, Design from the growndup to grow vertically. Explore the docs » View Demo · Report Bug · Request Feature Tab

null 18 Jan 5, 2022
Easy management of Virtualization technologies including KVM, Xen, OpenVZ, Virtuozzo, and LXC/LXD including unified commands, monitoring, template management, and many more features.

ProVirted About Easy management of Virtualization technologies including KVM, Xen, OpenVZ, Virtuozzo, and LXC/LXD including unified commands, monitori

null 2 Aug 22, 2022