{"id":36995235,"url":"https://github.com/php-monsters/laravel-sass-subscription","last_synced_at":"2026-01-13T23:47:33.391Z","repository":{"id":267323495,"uuid":"862595512","full_name":"php-monsters/laravel-sass-subscription","owner":"php-monsters","description":"A simple interface for managing subscriptions and feature usage in Laravel-based SaaS applications.","archived":false,"fork":false,"pushed_at":"2025-07-11T21:47:40.000Z","size":24,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-19T12:11:31.028Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/php-monsters.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-24T21:36:43.000Z","updated_at":"2025-07-11T21:38:37.000Z","dependencies_parsed_at":"2024-12-09T18:03:49.318Z","dependency_job_id":"600372a6-0f75-4cba-bdd7-12ee7a3b4452","html_url":"https://github.com/php-monsters/laravel-sass-subscription","commit_stats":null,"previous_names":["php-monsters/laravel-sass-subscription"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/php-monsters/laravel-sass-subscription","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/php-monsters%2Flaravel-sass-subscription","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/php-monsters%2Flaravel-sass-subscription/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/php-monsters%2Flaravel-sass-subscription/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/php-monsters%2Flaravel-sass-subscription/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/php-monsters","download_url":"https://codeload.github.com/php-monsters/laravel-sass-subscription/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/php-monsters%2Flaravel-sass-subscription/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28405308,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T21:51:37.118Z","status":"ssl_error","status_checked_at":"2026-01-13T21:45:14.585Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-13T23:47:33.324Z","updated_at":"2026-01-13T23:47:33.379Z","avatar_url":"https://github.com/php-monsters.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"![SAAS Subscription](https://img.freepik.com/premium-vector/subscription-prices-onboarding-mobile-app-screens-templates-walkthrough-website-vector-pages-digital-services-costs-tariff-plans-steps-smartphone-payment-web-page-layout_106317-10479.jpg?w=2000)\n\n# Sasscription (SAAS Subscription)\n\nA flexible Laravel package for seamless subscription management in standalone projects and multi-tenant SAAS applications. It supports billing, subscription plans, and item consumption, offering a complete solution for managing user subscriptions.\n\n## Requirements\n- PHP 8.3+\n- Laravel 11+\n\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require php-montsers/sasscription\n```\n\nThe package migrations are loaded automatically, but you can still publish them with this command:\n\n```bash\nphp artisan vendor:publish --tag=\"sasscription-migrations\"\nphp artisan migrate\n```\n\n## Usage\n\nTo start using it, you just have to add the given trait to your `User` model (or any entity you want to have subscriptions):\n\n```php\n\u003c?php\n\nnamespace App\\Models;\n\nuse PhpMonsters\\Sasscription\\Models\\Concerns\\HasSubscriptions;\n\nclass User\n{\n    use HasSubscriptions;\n}\n```\n\nAnd that's it!\n\n### Setting Features Up\n\nFirst things first, you have to define the features you'll offer. In the example below, we are creating two features: one to handle how much minutes each user can spend with deploys and if they can use subdomains.\n\n```php\n\u003c?php\n\nnamespace Database\\Seeders;\n\nuse Illuminate\\Database\\Seeder;\nuse PhpMonsters\\Sasscription\\Enums\\PeriodicityType;\nuse PhpMonsters\\Sasscription\\Models\\Feature;\n\nclass FeatureSeeder extends Seeder\n{\n    public function run()\n    {\n        $deployMinutes = Feature::create([\n            'consumable'       =\u003e true,\n            'name'             =\u003e 'deploy-minutes',\n            'periodicity_type' =\u003e PeriodicityType::Day,\n            'periodicity'      =\u003e 1,\n        ]);\n\n        $customDomain = Feature::create([\n            'consumable' =\u003e false,\n            'name'       =\u003e 'custom-domain',\n        ]);\n    }\n}\n```\n\nBy saying the `deploy-minutes` is a consumable feature, we are telling the users can use it a limited number of times (or until a given amount). On the other hand, by passing `PeriodicityType::Day` and 1 as its `periodicity_type` and `periodicity` respectively, we said that it should be renewed everyday. So a user could spend his minutes today and have it back tomorrow, for instance.\n\n\u003e It is important to keep in mind that both plans and consumable features have its periodicity, so your users can, for instance, have a monthly plan with weekly features.\n\nThe other feature we defined was `$customDomain`, which was a not consumable feature. By being not consumable, this feature implies only that the users with access to it can perform a given action (in this case, use a custom domain).\n\n#### Postpaid Features\n\nYou can set a feature so it can be used over its charges. To do so, you just have to set the `postpaid` attribute to `true`:\n\n```php\n$cpuUsage = Feature::create([\n    'consumable' =\u003e true,\n    'postpaid'   =\u003e true,\n    'name'       =\u003e 'cpu-usage',\n]);\n```\n\nThis way, the user will be able to use the feature until the end of the period, even if he doesn't have enough charges to use it (and you can charge him later, for instance).\n\n#### Quota Features\n\nWhen creating, for instance, a file storage system, you'll have to increase and decrease feature consumption as your users upload and delete files. To achieve this easily, you can use quota features. These features have an unique, unexpirable consumption, so they can reflect a constant value (as used system storage in this example).\n\n```php\nclass FeatureSeeder extends Seeder\n{\n    public function run()\n    {\n        $storage = Feature::create([\n            'consumable' =\u003e true,\n            'quota'      =\u003e true,\n            'name'       =\u003e 'storage',\n        ]);\n    }\n}\n\n...\n\nclass PhotoController extends Controller\n{\n    public function store(Request $request)\n    {\n        $userFolder = auth()-\u003eid() . '-files';\n\n        $request-\u003efile-\u003estore($userFolder);\n\n        $usedSpace = collect(Storage::allFiles($userFolder))\n            -\u003emap(fn (string $subFile) =\u003e Storage::size($subFile))\n            -\u003esum();\n\n        auth()-\u003euser()-\u003esetConsumedQuota('storage', $usedSpace);\n\n        return redirect()-\u003eroute('files.index');\n    }\n}\n```\n\nIn the example above, we set `storage` as a quota feature inside the seeder. Then, on the controller, our code store an uploaded file on a folder, calculate this folder size by retrieving all of its subfiles, and, finally, set the consumed `storage` quota as the directory total size.\n\n### Creating Plans\n\nNow you need to define the plans available to subscription in your app:\n\n```php\n\u003c?php\n\nnamespace Database\\Seeders;\n\nuse Illuminate\\Database\\Seeder;\nuse PhpMonsters\\Sasscription\\Enums\\PeriodicityType;\nuse PhpMonsters\\Sasscription\\Models\\Plan;\n\nclass PlanSeeder extends Seeder\n{\n    public function run()\n    {\n        $bronze = Plan::create([\n            'name'             =\u003e 'bronze',\n            'periodicity_type' =\u003e PeriodicityType::Month,\n            'periodicity'      =\u003e 1,\n        ]);\n        \n        $silver = Plan::create([\n            'name'             =\u003e 'silver',\n            'periodicity_type' =\u003e PeriodicityType::Month,\n            'periodicity'      =\u003e 1,\n        ]);\n\n        $gold = Plan::create([\n            'name'             =\u003e 'gold',\n            'periodicity_type' =\u003e PeriodicityType::Month,\n            'periodicity'      =\u003e 1,\n        ]);\n    }\n}\n```\n\nEverything here is quite simple, but it is worth to emphasize: by receiving the periodicity options above, the two plans are defined as monthly.\n\n#### Plans Without Periodicity (\"Free Plans\" or \"Permanent Plans\")\n\nYou can define plans without periodicity, so your users can subscribe to them permanently (or until they cancel their subscriptions). To do so, just pass a `null` value to the `periodicity_type` and `periodicity` attributes:\n\n```php\n$free = Plan::create([\n    'name'             =\u003e 'free',\n    'periodicity_type' =\u003e null,\n    'periodicity'      =\u003e null,\n]);\n```\n\n#### Grace Days\n\nYou can define a number of grace days to each plan, so your users will not loose access to their features immediately on expiration:\n\n```php\n$gold = Plan::create([\n    'name'             =\u003e 'gold',\n    'periodicity_type' =\u003e PeriodicityType::Month,\n    'periodicity'      =\u003e 1,\n    'grace_days'       =\u003e 7,\n]);\n```\n\nWith the configuration above, the subscribers of the \"gold\" plan will have seven days between the plan expiration and their access being suspended.\n\n### Associating Plans with Features\n\nAs each feature can belong to multiple plans (and they can have multiple features), you have to associate them:\n\n```php\nuse PhpMonsters\\Sasscription\\Models\\Feature;\n\n// ...\n\n$deployMinutes = Feature::whereName('deploy-minutes')-\u003efirst();\n$subdomains    = Feature::whereName('subdomains')-\u003efirst();\n\n$silver-\u003efeatures()-\u003eattach($deployMinutes, ['charges' =\u003e 15]);\n\n$gold-\u003efeatures()-\u003eattach($deployMinutes, ['charges' =\u003e 25]);\n$gold-\u003efeatures()-\u003eattach($subdomains);\n```\n\nIt is necessary to pass a value to `charges` when associating a consumable feature with a plan.\n\nIn the example above, we are giving 15 minutes of deploy time to silver users and 25 to gold users. We are also allowing gold users to use subdomains.\n\n### Subscribing\n\nNow that you have a set of plans with their own features, it is time to subscribe users to them. Registering subscriptions is quite simple:\n\n```php\n\u003c?php\n\nnamespace App\\Listeners;\n\nuse App\\Events\\PaymentApproved;\n\nclass SubscribeUser\n{\n    public function handle(PaymentApproved $event)\n    {\n        $subscriber = $event-\u003euser;\n        $plan       = $event-\u003eplan;\n\n        $subscriber-\u003esubscribeTo($plan);\n    }\n}\n```\n\nIn the example above, we are simulating an application that subscribes its users when their payments are approved. It is easy to see that the method `subscribeTo` requires only one argument: the plan the user is subscribing to. There are other options you can pass to it to handle particular cases that we're gonna cover below.\n\n\u003e By default, the `subscribeTo` method calculates the expiration considering the plan periodicity, so you don't have to worry about it.\n\n#### Defining Expiration and Start Date\n\nYou can override the subscription expiration by passing the `$expiration` argument to the method call. Below, we are setting the subscription of a given user to expire only in the next year.\n\n```php\n$subscriber-\u003esubscribeTo($plan, expiration: today()-\u003eaddYear());\n```\n\nIt is possible also to define when a subscription will effectively start (the default behavior is to start it immediately):\n\n```php\n\u003c?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Http\\Requests\\StudentStoreFormRequest;\nuse App\\Models\\Course;\nuse App\\Models\\User;\nuse PhpMonsters\\Sasscription\\Models\\Plan;\n\nclass StudentController extends Controller\n{\n    public function store(StudentStoreFormRequest $request, Course $course)\n    {\n        $student = User::make($request-\u003evalidated());\n        $student-\u003ecourse()-\u003eassociate($course);\n        $student-\u003esave();\n\n        $plan = Plan::find($request-\u003einput('plan_id'));\n        $student-\u003esubscribeTo($plan, startDate: $course-\u003estarts_at);\n\n        return redirect()-\u003eroute('admin.students.index');\n    }\n}\n```\n\nAbove, we are simulating an application for a school. It has to subscribe students at their registration, but also ensure their subscription will make effect only when the course starts.\n\n### Switching Plans\n\nUsers change their mind all the time and you have to deal with it. If you need to change the current plan of a user, simply call the method `switchTo`:\n\n```php\n$student-\u003eswitchTo($newPlan);\n```\n\nIf you don't pass any arguments, the method will suppress the current subscription and start a new one immediately.\n\n\u003e This call will fire a `SubscriptionStarted(Subscription $subscription)` event.\n\n### Fetching Current Balance\n\nIf you need remaining charges of a user, simply call the method `balance`. Imagine a scenario where a student has consumable feature named `notes-download`. To get remaining downloads limit:\n```php\n$student-\u003ebalance('notes-download');\n```\n\n\u003e This is just an alias of `getRemainingCharges` added to enrich the developer experience. \n\n#### Scheduling a Switch\n\nIf you want to keep your user with the current plan until its expiration, pass the `$immediately` parameter as `false`:\n\n```php\n$primeMonthly = Plan::whereName('prime-monthly')-\u003efirst();\n$user-\u003esubscribeTo($primeMonthly);\n\n...\n\n$primeYearly = Plan::whereName('prime-yearly')-\u003efirst();\n$user-\u003eswitchTo($primeYearly, immediately: false);\n```\n\nIn the example above, the user will keep its monthly subscription until its expiration and then start on the yearly plan. This is pretty useful when you don't want to deal with partial refunds, as you can bill your user only when the current paid plan expires.\n\nUnder the hood, this call will create a subscription with a start date equal to the current expiration, so it won't affect your application until there.\n\n\u003e This call will fire a `SubscriptionScheduled(Subscription $subscription)` event.\n\n#### Renewing\n\nTo renew a subscription, simply call the `renew()` method:\n\n```php\n$subscriber-\u003esubscription-\u003erenew();\n```\n\n\u003e This method will fire a `SubscriptionRenewed(Subscription $subscription)` event.\n\nIt will calculate a new expiration based on the current date.\n\n#### Expired Subscriptions\n\nIn order to retrieve an expired subscription, you can use the `lastSubscription` method:\n\n```php\n$subscriber-\u003elastSubscription();\n```\n\nThis method will return the last subscription of the user, regardless of its status, so you can, for instance, get an expired subscription to renew it.:\n\n```php\n$subscriber-\u003elastSubscription()-\u003erenew();\n```\n\n#### Canceling\n\n\u003e There is a thing to keep in mind when canceling a subscription: it won't revoke the access immediately. To avoid making you need to handle refunds of any kind, we keep the subscription active and just mark it as canceled, so you just have to not renew it in the future. If you need to suppress a subscription immediately, give a look on the method `suppress()`.\n\nTo cancel a subscription, use the method `cancel()`:\n\n```php\n$subscriber-\u003esubscription-\u003ecancel();\n```\n\nThis method will mark the subscription as canceled by filling the column `canceled_at` with the current timestamp.\n\n\u003e This method will fire a `SubscriptionCanceled(Subscription $subscription)` event.\n\n#### Suppressing\n\nTo suppress a subscription (and immediately revoke it), use the method `suppress()`:\n\n```php\n$subscriber-\u003esubscription-\u003esuppress();\n```\n\nThis method will mark the subscription as suppressed by filling the column `suppressed_at` with the current timestamp.\n\n\u003e This method will fire a `SubscriptionSuppressed(Subscription $subscription)` event.\n\n#### Starting\n\nTo start a subscription, use the method `start()`:\n\n```php\n$subscriber-\u003esubscription-\u003estart(); // To start it immediately\n$subscriber-\u003esubscription-\u003estart($startDate); // To determine when to start\n```\n\n\u003e This method will fire a `SubscriptionStarted(Subscription $subscription)` event when no argument is passed, and fire a `SubscriptionStarted(Subscription $subscription)` event when the provided start date is future.\n\nThis method will mark the subscription as started (or scheduled to start) by filling the column `started_at`.\n\n### Feature Consumption\n\nTo register a consumption of a given feature, you just have to call the `consume` method and pass the feature name and the consumption amount (you don't need to provide it for not consumable features):\n\n```php\n$subscriber-\u003econsume('deploy-minutes', 4.5);\n```\n\nThe method will check if the feature is available and throws exceptions if they are not: `OutOfBoundsException` if the feature is not available to the plan, and `OverflowException` if it is available, but the charges are not enough to cover the consumption.\n\n\u003e This call will fire a `FeatureConsumed($subscriber, Feature $feature, FeatureConsumption $featureConsumption)` event.\n\n#### Check Availability\n\nTo check if a feature is available to consumption, you can use one of the methods below:\n\n```php\n$subscriber-\u003ecanConsume('deploy-minutes', 10);\n```\n\nTo check if a user can consume a certain amount of a given feature (it checks if the user has access to the feature and if he has enough remaining charges).\n\n```php\n$subscriber-\u003ecantConsume('deploy-minutes', 10);\n```\n\nIt calls the `canConsume()` method under the hood and reverse the return.\n\n```php\n$subscriber-\u003ehasFeature('deploy-minutes');\n```\n\nTo simply checks if the user has access to a given feature (without looking for its charges).\n\n```php\n$subscriber-\u003emissingFeature('deploy-minutes');\n```\n\nSimilarly to `cantConsume`, it returns the reverse of `hasFeature`.\n\n### Feature Tickets\n\nTickets are a simple way to allow your subscribers to acquire charges for a feature. When a user receives a ticket, he is allowed to consume its charges, just like he would do in a normal subscription. Tickets can be used to extend regular subscriptions-based systems (so you can, for instance, sell more charges of a given feature) or even to **build a fully pre-paid service**, where your users pay only for what they want to use.\n\n#### Enabling Tickets\n\nIn order to use this feature, you have to enable tickets in your configuration files. First, publish the package configs:\n\n```bash\nphp artisan vendor:publish --tag=\"sasscription-config\"\n```\n\nFinally, open the `sasscription.php` file and set the `feature_tickets` flag to `true`. That's it, you now can use tickets!\n\n#### Creating Tickets\n\nTo create a ticket, you can use the method `giveTicketFor`. This method expects the feature name, the expiration and optionally a number of charges (you can ignore it when creating tickets for not consumable features):\n\n```php\n$subscriber-\u003egiveTicketFor('deploy-minutes', today()-\u003eaddMonth(), 10);\n```\n\n\u003e This method will fire a `FeatureTicketCreated($subscriber, Feature $feature, FeatureTicket $featureTicket)` event.\n\nIn the example above, the user will receive ten more minutes to execute deploys until the next month.\n\n#### Not Consumable Features\n\nYou can create tickets for not consumable features, so your subscribers will receive access to them just for a certain period:\n\n```php\nclass UserFeatureTrialController extends Controller\n{\n    public function store(FeatureTrialRequest $request, User $user)\n    {\n        $featureName = $request-\u003einput('feature_name');\n        $expiration = today()-\u003eaddDays($request-\u003einput('trial_days'));\n        $user-\u003egiveTicketFor($featureName, $expiration);\n\n        return redirect()-\u003eroute('admin.users.show', $user);\n    }\n}\n```\n\n#### Non-Expirable Tickets\n\nYou can create tickets that never expire, so your subscribers will receive access to them forever:\n\n```php\n$subscriber-\u003egiveTicketFor('deploy-minutes', null, 10);\n```\n\n\u003e Don't forget to remove these tickets when your user cancels his subscription. Otherwise, they will be able to consume the charges forever.\n\n## Testing\n\n```bash\ncomposer test\n```\n\n## Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.\n\n## Security Vulnerabilities\n\nPlease review [our security policy](../../security/policy) on how to report security vulnerabilities.\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphp-monsters%2Flaravel-sass-subscription","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphp-monsters%2Flaravel-sass-subscription","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphp-monsters%2Flaravel-sass-subscription/lists"}