├── sponsors.png ├── .assets ├── ko-fi.png ├── paypal.png ├── patreon.png └── buymeacoffee.png └── README.md /sponsors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laragear/SubscriptionsDemo/HEAD/sponsors.png -------------------------------------------------------------------------------- /.assets/ko-fi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laragear/SubscriptionsDemo/HEAD/.assets/ko-fi.png -------------------------------------------------------------------------------- /.assets/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laragear/SubscriptionsDemo/HEAD/.assets/paypal.png -------------------------------------------------------------------------------- /.assets/patreon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laragear/SubscriptionsDemo/HEAD/.assets/patreon.png -------------------------------------------------------------------------------- /.assets/buymeacoffee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Laragear/SubscriptionsDemo/HEAD/.assets/buymeacoffee.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Subscriptions 2 | 3 | Manage subscriptions on-premises, without any payment systems! 4 | 5 | ```php 6 | use Illuminate\Support\Facades\Auth; 7 | use Laragear\Subscriptions\Models\Plan; 8 | 9 | $subscription = Auth::user()->subscribeTo(Plan::find(1)); 10 | 11 | return "You are now subscribed to $subscription->name!"; 12 | ``` 13 | 14 | ## [Download it](https://github.com/sponsors/DarkGhostHunter/sponsorships?sponsor=DarkGhostHunter&tier_id=195145&preview=false) 15 | 16 | [![](sponsors.png)](https://github.com/sponsors/DarkGhostHunter/sponsorships?sponsor=DarkGhostHunter&tier_id=294921) 17 | 18 | [Become a Sponsor and get instant access to this package](https://github.com/sponsors/DarkGhostHunter/sponsorships?sponsor=DarkGhostHunter&tier_id=294921). 19 | 20 | ## Requirements 21 | 22 | * Laravel 11 or later. 23 | 24 | ## Installation 25 | 26 | You can install the package via Composer. Open your `composer.json` and point the location of the private repository under the `repositories` key. 27 | 28 | ```json 29 | { 30 | // ... 31 | "repositories": [ 32 | { 33 | "type": "vcs", 34 | "name": "laragear/subscriptions", 35 | "url": "https://github.com/laragear/subscriptions.git" 36 | } 37 | ], 38 | } 39 | ``` 40 | 41 | Then call Composer to retrieve the package. 42 | 43 | ```bash 44 | composer require laragear/subscriptions 45 | ``` 46 | 47 | You will be prompted for a personal access token. If you don't have one, follow the instructions or [create one here](https://github.com/settings/tokens/new?scopes=repo). It takes just seconds. 48 | 49 | > [!TIP] 50 | > 51 | > You can find more information about in [this article](https://darkghosthunter.medium.com/php-use-your-private-repository-in-composer-without-ssh-keys-da9541439f59). 52 | 53 | ## How this works? 54 | 55 | Laragear's Subscriptions package uses the power of Laravel Eloquent Models with some magic to handle a small but flexible Subscription system. 56 | 57 | The basic approach is simple: a Plan acts like a blueprint to create Subscriptions, which are marked to renew by certain intervals. A model, like your included User, can have one subscription, or even multiple subscriptions at the same time. 58 | 59 | This enables multiple ways to handle subscriptions across your app: 60 | 61 | - Upgrade only to a group of Plans. 62 | - Share a subscription between multiple users. 63 | - Lock and unlock Plans... 64 | 65 | ... and much more. 66 | 67 | ## Set up 68 | 69 | Before creating plans and subscribing users to a Plan, you will need to install the application and setup some minor models so you're ready to go. Don't worry, it only takes 5 minutes. 70 | 71 | ### 1. Install the files 72 | 73 | Install the migrations tables, the policies, and the plans blueprints file. You can install everything with the convenient `subscriptions::install` Artisan command. 74 | 75 | ```shell 76 | php artisan subscriptions:install 77 | ``` 78 | 79 | ### 2. Set the relationship 80 | 81 | > [!TIP] 82 | > 83 | > If you will be subscribing only your `App\Models\User` model, and access them using `subscribers()`, you can skip this step. 84 | 85 | To add your Users as subscribers, and be able to access them easily through the Subscription model, use `Subscription::macroRelation()`. For polymorphic relations, use `Subscription::macroPolymorphicRelation()`. You can do this in your `App\Providers\AppServiceProvider` class, or in your `bootstrap/app.php` file. 86 | 87 | These are methods that use [dynamic relationships](https://laravel.com/docs/11.x/eloquent-relationships#dynamic-relationships) but with a more complete relation callback and `name => class` convenience. 88 | 89 | ```php 90 | use Laragear\Subscriptions\Models\Subscription; 91 | use Illuminate\Foundation\Application; 92 | use App\Models\Adult; 93 | use App\Models\Kid; 94 | 95 | return Application::configure(basePath: dirname(__DIR__)) 96 | ->booted(function () { 97 | // For a simple monomorphic relationship. 98 | Subscription::macroRelation('adults', Adult::class); 99 | 100 | // For all of your polymorphic relationships. 101 | Subscription::macroPolymorphicRelations([ 102 | 'adults' => Adult::class, 103 | 'kids' => Kids::class, 104 | ]); 105 | })->create(); 106 | ``` 107 | 108 | This way you will be able to access subscribers from a Subscription model using the relation name: 109 | 110 | ```php 111 | use Laragear\Subscriptions\Models\Subscription; 112 | 113 | $actors = Subscription::find('bc728326...')->adults; 114 | ``` 115 | 116 | > [!TIP] 117 | > 118 | > More details in the [polymorphic section](#polymorphic-relations). Don't worry, is just some paragraphs with code. 119 | 120 | ### 3. Migrate the tables. 121 | 122 | If you don't require any further [change to the migrations files](MIGRATIONS.md), you can just migrate your database tables like it was another day in the office: 123 | 124 | ```shell 125 | php artisan migrate 126 | ``` 127 | 128 | > [!TIP] 129 | > 130 | > Migrations create 3 tables: `plans`, `subscribable` and `subscriptions`. 131 | 132 | ### 4. Add the `WithSubscription` trait 133 | 134 | Finally, add the `Laragear\Subscriptions\WithSubscriptions` trait to your models, which will enable it to handle subscriptions easily with just a few methods. 135 | 136 | ```php 137 | namespace App\Models; 138 | 139 | use Illuminate\Foundation\Auth\User as Authenticatable; 140 | use Laragear\Subscriptions\WithSubscriptions; 141 | 142 | class User extends Authenticatable 143 | { 144 | use WithSubscriptions; 145 | 146 | // ... 147 | } 148 | ``` 149 | 150 | ## Plans 151 | 152 | First, we need to create the Plans. We can go inside the `plans/plans.php` and create them using the convenient Plan builder. You're free to remove the example Plan. 153 | 154 | ```php 155 | use Laragear\Subscriptions\Facades\Plan; 156 | 157 | // Plan::called('Free')->capability('coolness', 10)->monthly(); 158 | ``` 159 | 160 | > [!TIP] 161 | > 162 | > A Plan works like a blueprint for a Subscription: when a Subscription is created, the Plan data is copied into the Subscription. 163 | 164 | For example, you may create two plans for a Delivery app: one for free, and the other paid. We will differentiate them by their names and the number of deliveries per month allowed. 165 | 166 | ```php 167 | use Laragear\Subscriptions\Facades\Plan; 168 | 169 | Plan::called('Free')->capability('deliveries', 1)->monthly(); 170 | 171 | Plan::called('Basic')->capability('deliveries', 8)->monthly(); 172 | ``` 173 | 174 | > [!WARNING] 175 | > 176 | > Plans require an interval. You will get an error if the interval is not set. You can easily make lifetime plans by [renewing them automatically](#renew--extend) in your app. 177 | 178 | Once plans are _declared_, we will need to push them into the database. For that, we can use `subscriptions:plans`. The command will read the plans and create them into the database. 179 | 180 | ```shell 181 | php artisan subscriptions:plans 182 | 183 | # Created 2 plans: 184 | # - Free 185 | # - Basic 186 | ``` 187 | 188 | That's it, from here you can subscribe entities to it, which can be users, companies, or whatever you want. 189 | 190 | Before going into the subscriptions themselves, let's check how we can configure a Plan thoroughly. 191 | 192 | ### Plan Capabilities 193 | 194 | Capabilities are the core of Laragear's Subscriptions. Simple Plans may not need any, but complex ones may need a list of values to allow (or not) a user to do a given action. 195 | 196 | The capabilities set in the Plan will get copied over new Subscriptions. This makes the Plan immutable, and make changes only affect new Subscriptions. 197 | 198 | Capabilities can be set using `capability()`. For example, we will set the number of deliveries for the Basic Plan: 199 | 200 | ```php 201 | use Laragear\Subscriptions\Facades\Plan; 202 | 203 | Plan::called('Basic')->capability('deliveries', 8)->monthly(); 204 | ``` 205 | 206 | You can also set a list of capabilities in one go using an array. 207 | 208 | ```php 209 | use Laragear\Subscriptions\Facades\Plan; 210 | 211 | Plan::called('Basic')->capability([ 212 | 'deliveries' => 8, 213 | 'delivery' => [ 214 | 'priority' => 'normal', 215 | 'groceries' => false, 216 | ] 217 | ])->monthly(); 218 | ``` 219 | 220 | Capabilities can be accessed using `dot.notation`. In your Subscription, you can access them later with `capability()`. 221 | 222 | ```php 223 | $priority = $user->subscription->capability('delivery.priority'); 224 | ``` 225 | 226 | ### Metadata 227 | 228 | Metadata is like a note in the plan itself, which are is visible at serialization by default. This metadata replicated into the subscription itself. To attach metadata to a plan use `withMetadata()`. 229 | 230 | ```php 231 | use Laragear\Subscriptions\Facades\Plan; 232 | 233 | Plan::called('Free')->withMetadata([ 234 | 'payment' => 'not_allowed', 235 | 'remember' => 'weekly', 236 | ])->monthly(); 237 | ``` 238 | 239 | Metadata is just a `Collection` instance, so it's easy to retrieve and save values to it later in the Subscription. 240 | 241 | ```php 242 | echo $subscription->metadata('payment'); // "not_allowed" 243 | ``` 244 | 245 | ### Custom columns 246 | 247 | Since the migration files are editable with new columns, you can use `withColumns()` to add primitive values to their respective columns. 248 | 249 | ```php 250 | use Laragear\Subscriptions\Facades\Plan; 251 | 252 | Plan::called('Basic')->withColumns([ 253 | 'price' => 29.90, 254 | ])->monthly(); 255 | ``` 256 | 257 | Later, these values can be accessed like a normal property from its Subscription, as these values will be copied over them as long the column exists. 258 | 259 | ```php 260 | echo $subscription->price; // 29.90 261 | ``` 262 | 263 | ### Plan groups and levels 264 | 265 | Plan groups allows to Subscriptions to be upgraded only to Plans of the same group, and only for "better" plans. This makes a clear path to subscription upgrades, like from "Free" to "Basic" and vice versa. 266 | 267 | With `group()` and the name on the group, you can add your Plans from the most basic to the more powerful. 268 | 269 | ```php 270 | use Laragear\Subscriptions\Facades\Plan; 271 | 272 | Plan::group('deliveries', [ 273 | 0 => Plan::called('Free')->capability('deliveries', 1)->monthly(), 274 | 1 => Plan::called('Basic')->capability('deliveries', 8)->monthly(), 275 | ]); 276 | ``` 277 | 278 | With that, users won't be able to downgrade to a lesser plan, or subscribe to two plans of the same group, when checking these actions with the [authorization gates](#authorization). 279 | 280 | ```php 281 | use Laragear\Subscriptions\Models\Plan; 282 | use Illuminate\Support\Facades\Auth; 283 | 284 | $plan = Plan::find('b6954722...'); 285 | 286 | if (Auth::user()->cannot('upgradeTo', $plan)) { 287 | return "You cannot upgrade to the $plan->name."; 288 | } 289 | 290 | Auth::user()->upgradeTo($plan); 291 | ``` 292 | 293 | Since downgrades are disabled, you can enable them using `downgradeableGroup()` through the `Plan` Facade. 294 | 295 | ```php 296 | use Laragear\Subscriptions\Facades\Plan; 297 | 298 | Plan::downgradeableGroup('deliveries', [ 299 | 0 => Plan::called('Free')->capability('deliveries', 1)->monthly(), 300 | 1 => Plan::called('Basic')->capability('deliveries', 8)->monthly(), 301 | ]); 302 | ``` 303 | 304 | > [!IMPORTANT] 305 | > 306 | > This doesn't remove the ability for an entity to subscribe to another Plan outside the group. 307 | 308 | ### Shareable subscriptions 309 | 310 | Normally, a subscription would be attached to one entity, like a user or a company, but you can set a subscription to be "shareable" between multiple entities. For example, you can use this to create subscriptions for families or teams. 311 | 312 | Use the `sharedUpTo()` to set a number of maximum entities to share the subscription. 313 | 314 | ```php 315 | use Laragear\Subscriptions\Facades\Plan; 316 | 317 | Plan::called('Newlyweds special')->monthly()->sharedUpTo(2); 318 | ``` 319 | 320 | An entity will be still able to be attached to another entity subscription, even if it's the same Plan and is marked as _unique_. To disable this, you may want to check if the entity already is attached to Plan before attaching it to another: 321 | 322 | ```php 323 | use Laragear\Subscriptions\Models\Plan; 324 | use App\Models\User; 325 | 326 | if (User::find(1)->hasOneSubscription()) { 327 | return "The user cannot be attached to two or more active subscriptions."; 328 | } 329 | 330 | $plan = Plan::find('b6954722...'); 331 | 332 | if (User::find(2)->isSubscribedTo($plan)) { 333 | return "The user is already subscribed to $plan->name."; 334 | } 335 | ``` 336 | 337 | #### Open shared subscriptions 338 | 339 | Normally, the shared subscription slots can only be filled by the admin, but you can enable "open" subscriptions where users don't need prior approval to join in. You can easily set this using `openlySharedUpTo()`. 340 | 341 | ```php 342 | use Laragear\Subscriptions\Facades\Plan; 343 | 344 | Plan::called('Sunday golf')->monthly()->openlySharedUpTo(4); 345 | ``` 346 | 347 | Later in your app, you can use the [convenient authorization gates](#authorization) to check if a user can be attached to an open or closed subscription: 348 | 349 | ```php 350 | if ($user->cant('attachTo', [$subscription, $user])) { 351 | return 'Only the owner of this subscription can invite you.'; 352 | } 353 | ``` 354 | 355 | You can programmatically open and close a subscription with `shareOpenly()` and `dontShareOpenly()`, respectively: 356 | 357 | ```php 358 | $user->subscription->shareOpenly(); 359 | 360 | $user->subscription->dontShareOpenly(); 361 | ``` 362 | 363 | ### Renewal cycle 364 | 365 | One problem that Laragear Subscriptions fixes is setting by how much a Plan cycle runs before having to be renewed. You have access to a multitude of helpers to set that interval. 366 | 367 | | Method | Description | 368 | |-------------------|-------------------------------------------| 369 | | `days($amount)` | Sets a cycle based on a number of days. | 370 | | `daily()` | Sets a daily cycle. | 371 | | `weeks($amount)` | Sets a cycle based on a number of weeks. | 372 | | `weekly()` | Sets a weekly cycle. | 373 | | `biweekly()` | Sets a biweekly cycle (two weeks). | 374 | | `months($amount)` | Sets a cycle based on a number of months. | 375 | | `monthly()` | Sets a monthly cycle. | 376 | | `bimonthly()` | Sets a bimonthly cycle (two months). | 377 | | `quarterly()` | Sets a quarterly cycle (three months). | 378 | | `biannual()` | Sets a semester cycle (six months). | 379 | | `yearly()` | Sets a yearly cycle. | 380 | | `biennial()` | Sets a two-year cycle. | 381 | | `triennial()` | Sets a three-year cycle. | 382 | 383 | If none of these cycles adjusts to your liking, you fine tune the interval with `every()`, which accepts an interval. 384 | 385 | ```php 386 | use Laragear\Subscriptions\Facades\Plan; 387 | use Carbon\CarbonInterval; 388 | 389 | Plan::called('Special')->every(CarbonInterval::month()->addDays(5)); 390 | ``` 391 | 392 | > [!IMPORTANT] 393 | > 394 | > Monthly intervals never overflow to the next month. For example, a monthly subscription that starts 31st of January will expire at 28th/29th of February, and continue until the 31st of March. 395 | 396 | #### Not renewable plans 397 | 398 | Plans are renewable by default, but you may want to create one-time plans with subscriptions that only last one cycle, like a "trial". For that, you can use `notRenewable()`. 399 | 400 | ```php 401 | use Laragear\Subscriptions\Facades\Plan; 402 | 403 | Plan::called('Basic')->monthly(); 404 | 405 | Plan::called('Trial')->weekly()->notRenewable(); 406 | ``` 407 | 408 | You can also use the helpers `forOnlyOneDay()`, `forOnlyOneWeek()`, and `forOnlyOneMonth()` to create not-renewable plans for one day, week or month, respectively. 409 | 410 | ```php 411 | use Laragear\Subscriptions\Facades\Plan; 412 | 413 | Plan::called('Trial')->forOnlyOneWeek(); 414 | ``` 415 | 416 | ### Plan subscribers limit 417 | 418 | Plans can have a limited number of subscriptions. For example, we may set a "Premium" to not have more than 10 Subscriptions using `limitedTo()`. 419 | 420 | ```php 421 | use Laragear\Subscriptions\Facades\Plan; 422 | 423 | Plan::called('Premium')->monthly()->limitedTo(10)); 424 | ``` 425 | 426 | By default, a Plan will be always subscribable as long there is slots available. A slot is freed when its Subscription is no longer active. You may disable freeing slots with `permanentlyLimitedTo()`: even if a Subscription is cancelled, nobody will be able to subscribe to it once all slots were taken. 427 | 428 | ```php 429 | use Laragear\Subscriptions\Facades\Plan; 430 | 431 | Plan::called('Premium')->monthly()->permanentlyLimitedTo(10); 432 | ``` 433 | 434 | For example, if the 10th subscription is no longer active, no new Subscriptions for the Plan will be able to be created. This makes the `subscribeTo` [authorization gate](#authorization) fail if there are no free slots. 435 | 436 | ```php 437 | if (Auth::user()->cant('subscribeTo', $plan) && $plan->isFilled()) { 438 | return 'There is no more available Subscriptions for this Plan.'; 439 | } 440 | ``` 441 | 442 | The Plan's Subscription limit can be resized later manually in the Plan Model, from anywhere in your application. 443 | 444 | ```php 445 | use Laragear\Subscriptions\Models\Plan; 446 | 447 | $plan = Plan::find('b6954722...'); 448 | 449 | // Resize the limit of subscriptions 450 | $plan->resizeLimit(20); 451 | 452 | // Or remove the limits altogether. 453 | $plan->removeLimit(); 454 | ``` 455 | 456 | Resizing a Plan doesn't unsubscribe users from it, even if the new limit is lower than the active Subscriptions. If you want to forcefully [terminate](#unsubscribe--terminate) subscriptions outside the limit, it's recommended to do it from the oldest. 457 | 458 | ```php 459 | use Laragear\Subscriptions\Models\Subscription; 460 | use Laragear\Subscriptions\Models\Plan; 461 | 462 | $plan = Plan::find('b6954722...'); 463 | 464 | Subscription::whereBelongsTo($plan) 465 | ->oldest() 466 | ->limit(1000)->skip(20) 467 | ->each(fn (Subscription $subscription) => $subscription->terminate()); 468 | ``` 469 | 470 | ### Locking and Unlocking plans 471 | 472 | Plans are by default open to all subscribers, but you can manually lock them using the `lock()` method at runtime. This disables new subscriptions when using the [authorization gates](#authorization). 473 | 474 | ```php 475 | use Laragear\Subscriptions\Models\Plan; 476 | use Illuminate\Support\Facades\Auth; 477 | 478 | $plan = Plan::find('b6954722...'); 479 | 480 | $plan->lock(); 481 | 482 | if (Auth::user()->cant('subscribeTo', $plan) && $plan->isLocked()) { 483 | return 'This plan has been locked for new subscribers.'; 484 | } 485 | ``` 486 | 487 | > [!IMPORTANT] 488 | > 489 | > Locking a plan disables new subscriptions, or subscription upgrades to it, but not renewals. You can [disable Plan renewals](#not-renewable-plans). 490 | 491 | To revert the operation, use `unlock()`. 492 | 493 | ```php 494 | use Laragear\Subscriptions\Models\Plan; 495 | 496 | Plan::find('b6954722...')->unlock(); 497 | ``` 498 | 499 | ### Hidden plans 500 | 501 | All plans are retrieved and shown publicly when queried. You can hide plans using `hidden()`. This sets a flag on the Plan to not be shown on queries, but still be available to be subscribed normally. 502 | 503 | ```php 504 | use Laragear\Subscriptions\Facades\Plan; 505 | 506 | Plan::called('Unrestricted plan')->monthly()->hidden(); 507 | ``` 508 | 509 | To show hidden plans in your query, use the `withHidden()` to include hidden plans. 510 | 511 | ```php 512 | use Laragear\Subscriptions\Models\Plan; 513 | 514 | $plans = Plan::withHidden()->get(); 515 | ``` 516 | 517 | > [!TIP] 518 | > 519 | > It's not necessary to use `withHidden()` when retrieving the Plan from the `Subscription` model instance, the underlying scope is automatically removed so the Plan is always shown. 520 | 521 | ### Unique plans 522 | 523 | By default, an entity can be subscribed to multiple Plans simultaneously. For example, a user can be subscribed to a "Food deliveries" and "Groceries deliveries" at the same time. 524 | 525 | Sometimes you will want to not allow a user to subscribe to other Plans. You may set that Plan to be _unique_, meaning, the user won't be able to subscribe to any other plans as long it's subscribed to that unique plan. This can be done with `unique()` on the plan itself. 526 | 527 | ```php 528 | use Laragear\Subscriptions\Facades\Plan; 529 | 530 | $plan = Plan::called('All groceries')->monthly()->unique(); 531 | ``` 532 | 533 | When checking for permissions using the [authorization gate](#authorization), the user won't be able to subscribe if the Plan is subscribed to was marked as unique. 534 | 535 | ```php 536 | use Laragear\Subscriptions\Models\Plan; 537 | use Illuminate\Support\Facades\Auth; 538 | 539 | $plan = Plan::find('b6954722...'); 540 | 541 | if (Auth::user()->cant('subscribeTo', $plan) && Auth::user()->subscription->isUnique()) { 542 | return 'You cannot be subscribed to any other Plan.'; 543 | } 544 | ``` 545 | 546 | Alternatively, you may want to use [groups](#plan-groups-and-levels) to make an entity only be subscribed to one plan of a Plan group. 547 | 548 | ### Custom plans 549 | 550 | While your plans with a set amount of capabilities may work for many subscribers, you may find your app in a position to offer "customizable" plans. You can create these like with any other plans, on demand. 551 | 552 | For example, let's say we want to let a company create its own plan through a web interface. It's easy as just building a new plan, and attaching it immediately to the company using `createFor()`. 553 | 554 | ```php 555 | use App\Models\Company; 556 | use Illuminate\Http\Request; 557 | use Laragear\Subscriptions\Facades\Plan; 558 | 559 | public function createPlan(Request $request) 560 | { 561 | $request->validate([ 562 | // ... 563 | ]); 564 | 565 | $company = Company::find($request->company_id); 566 | 567 | $subscription = Plan::called("Custom Plan for $company->name") 568 | ->capabilities([ 569 | 'deliveries' => 50, 570 | 'on_weekdays' => 50, 571 | 'priority' => 1.0, 572 | ]) 573 | ->monthly() 574 | ->createFor($company); 575 | 576 | return 'Your are now subscribed to a custom plan for you!'; 577 | } 578 | ``` 579 | 580 | What `createFor()` does is relatively simple: 581 | 582 | - It [hides the plan](#hidden-plans), so it's not shown publicly by default. 583 | - It [locks the plan](#locking-and-unlocking-plans), so nobody else can subscribe to it. 584 | 585 | You can also combine the Plan with `unique()` to make the subscriber be only subscribed to that Plan only. 586 | 587 | ```php 588 | use Laragear\Subscriptions\Facades\Plan; 589 | 590 | $subscription = Plan::called('Custom for Company LLC.') 591 | ->capabilities([ 592 | // ... 593 | ]) 594 | ->monthly() 595 | ->unique() 596 | ->createFor($company); 597 | ``` 598 | 599 | ### Custom ID 600 | 601 | Plans ID are UUID v4, and are generated automatically when these are saved into the database. This allows to hide the number of existing plans in your application, especially if you create [custom plans](#custom-plans). 602 | 603 | You may want to change the UUID generator using the static property `Plans::$useWithUuid`, which accepts a Closure that returns a UUID. 604 | 605 | ```php 606 | use Laragear\Subscriptions\Models\Plan; 607 | use Ramsey\Uuid\Uuid; 608 | 609 | Plan::$useUuid = function (Plan $plan) { 610 | return Uuid::uuid6(); 611 | } 612 | ``` 613 | 614 | If you want to use the [_ordered UUID_](https://laravel.com/docs/11.x/helpers#method-str-ordered-uuid) from Laravel, just call `Plans::useOrderedUuid()`. 615 | 616 | ```php 617 | use Laragear\Subscriptions\Models\Plan; 618 | use Laragear\Subscriptions\Models\Subscription; 619 | 620 | Plan::useOrderedUuid(); 621 | Subscription::useOrderedUuid(); 622 | ``` 623 | 624 | ## Subscriptions 625 | 626 | After your plans are set, the next step is to subscribe entities, like a user or a company. It doesn't matter which model you set to be subscribed, as this package handles the subscribers as a polymorphic relationship. 627 | 628 | ### Understanding Subscriptions 629 | 630 | A Subscriptions contains some pieces of additional information to manage temporal calculations, apart from the data copied from the Plan it belongs to: 631 | 632 | - `begins_at`: The date from when cycles are calculated from. 633 | - `starts_at`: The current cycle start. 634 | - `ends_at`: The current cycle end. 635 | - `cycles`: The number of cycles since the subscription began. 636 | - `grace_ends_at`: The additional grace time after the cycle end to consider the Subscription _active_, if any. 637 | 638 | You're free to check this data in your Subscription model instance, but don't change it unless you know what you're doing. Instead, use the helpers, which will make modifying a Subscription painless. 639 | 640 | ### Subscribing to a Plan 641 | 642 | The most common task is subscribing to a Plan. You can use `subscribeTo()` with the Plan instance. It returns a persisted `Subscription` model. 643 | 644 | ```php 645 | use Laragear\Subscriptions\Models\Plan; 646 | use Illuminate\Support\Facades\Auth; 647 | 648 | $plan = Plan::find('b6954722...'); 649 | 650 | if (Auth::user()->cannot('subscribeTo', $plan)) { 651 | return "You cannot subscribe to the plan $plan->name."; 652 | } 653 | 654 | $subscription = Auth::user()->subscribeTo($plan); 655 | ``` 656 | 657 | You can use a second parameter to add or override custom attributes to the subscription. For example, we can add custom metadata for one subscription in particular. 658 | 659 | ```php 660 | $subscription = $user->subscribeTo($plan, [ 661 | 'metadata' => ['is_cool' => 'true'] 662 | ]); 663 | ``` 664 | 665 | Alternatively, you can use the `Subscribe` facade to attach multiple entities to one subscription, like when doing "Family" or "Team" plans. When doing this, only the first entity will be considered the _admin_. 666 | 667 | ```php 668 | use App\Models\User; 669 | use Laragear\Subscriptions\Models\Plan; 670 | use Laragear\Subscriptions\Facades\Subscribe; 671 | 672 | $plan = Plan::find('b6954722...'); 673 | 674 | $users = User::where('family', 'Doe Family')->sortBy('age', 'desc')->lazy(); 675 | 676 | $subscription = Subscribe::to($plan, $users, [ 677 | 'metadata' => ['is_cool' => true] 678 | ]); 679 | ``` 680 | 681 | #### Attaching data to the pivot 682 | 683 | Behind the scenes, Laragear's Subscription package uses the `Laragear\Subscriptions\Models\Subscribable` Pivot Model. This pivot allows to bind one or multiple entities to a Subscription. 684 | 685 | When you subscribe a single model to a Plan, change it, or retrieve one from it, you can access the pivot model as `subscribable`. 686 | 687 | ```php 688 | use Illuminate\Support\Facades\Auth; 689 | 690 | Auth::user()->subscription->subscribable->metadata('is_cool'); // "false" 691 | ``` 692 | 693 | You may set any metadata you want for that particular subscriber at subscribing time, or even when changing it. There is no need to add `is_admin` or `metadata`, as the pivot table already comes with these columns, but, for additional columns, you will have to [edit the migration](#3-migrate-the-tables). 694 | 695 | ```php 696 | use Illuminate\Support\Facades\Auth; 697 | use Laragear\Subscriptions\Models\Plan; 698 | 699 | // Subscribe the user to a plan and add metadata for only him. 700 | $subscription = Auth::user()->subscribeTo(Plan::first(), pivotAttributes: [ 701 | 'metadata' => ['is_cool' => true] 702 | ]); 703 | 704 | $subscription->subscribable->metadata('is_cool'); // "true" 705 | ``` 706 | 707 | ### Postponed beginning 708 | 709 | When a subscription is created, it starts immediately. You may want to defer the starting moment using `postponedTo()`, which moves the subscription starting date forward by an amount of days. You can also use a function that should return the moment when it will begin. Until then, the subscription won't be active. 710 | 711 | ```php 712 | use Laragear\Subscriptions\Facades\Plan; 713 | use Illuminate\Support\Facades\Auth; 714 | 715 | if (Auth::user()->cannot('subscribeTo', $plan)) { 716 | return "You cannot subscribe to the plan $plan->name."; 717 | } 718 | 719 | $subscription = Auth::user()->subscribeTo($plan)->postponedTo(function ($start) { 720 | return $start->addDay()->setTime(9, 0); 721 | }); 722 | 723 | $subscription->save(); 724 | ``` 725 | 726 | When postponing the subscription, the user will be still subscribed to the Plan, but the subscription won't be considered _active_ until the postponed date. 727 | 728 | If you're looking to only change when the cycles should be calculated from, you may want to [push the cycle start forward](#push-cycle-start). 729 | 730 | ### Push cycle start 731 | 732 | The Subscription begins from the exact moment is created. You can "move" the subscription cycle to accommodate a date (like, from next monday) using `pushedTo()` and the number of days, or a function that returns the date to extend to and the start the cycle. 733 | 734 | For example, let's assume a user subscribes on Friday 24th at 12:30 PM. By default, the next monthly cycle will start the same day at the next month. With `pushedTo()`, we can push the cycle start to Monday 27th 00:00, giving him the whole weekend "for free". 735 | 736 | ```php 737 | use Laragear\Subscriptions\Facades\Plan; 738 | use Illuminate\Support\Facades\Auth; 739 | 740 | if (Auth::user()->cannot('subscribeTo', $plan)) { 741 | return "You cannot subscribe to the plan $plan->name."; 742 | } 743 | 744 | $subscription = Auth::user() 745 | ->subscribeTo($plan) 746 | ->pushedTo(function ($start) { 747 | return $start->nextMonday(); 748 | }); 749 | 750 | $subscription->save(); 751 | ``` 752 | 753 | During this _extended_ time, the Subscription will be considered active. 754 | 755 | > [!TIP] 756 | > 757 | > If you need to move the subscription start instead of pushing the start, you can [postpone it](#postponed-beginning). 758 | 759 | ### Modifying the Interval 760 | 761 | If you need to change the subscription interval to be different from the parent Plan, use `updateInterval()` with the interval you want to force into the Subscription. 762 | 763 | ```php 764 | use Laragear\Subscriptions\Models\Plan; 765 | use Illuminate\Support\Facades\Auth; 766 | use Carbon\CarbonInterval; 767 | 768 | // Subscribe the user to a monthly plan. 769 | $subscription = Auth::user()->subscribeTo(Plan::find('b6954722...')); 770 | 771 | // Replace the monthly interval to yearly. 772 | $subscription->updateInterval(CarbonInterval::year()); 773 | ``` 774 | 775 | > [!DANGER] 776 | > 777 | > This only works with freshly created subscriptions. Forcefully changing the interval **after** the first cycle will corrupt the cycle count and start/end dates. 778 | 779 | ### Retrieving the subscription 780 | 781 | Once a subscription is created, it can be accessed with the `subscription` property, like a normal Eloquent Relationship. This method automatically retrieves **the latest active** subscription for the entity, or returns `null` if there is none. 782 | 783 | ```php 784 | use App\Models\User; 785 | 786 | $user = User::find(1); 787 | 788 | $subscription = $user->subscription; 789 | ``` 790 | 791 | For the case of users attached to multiple subscriptions, it always returns the latest subscribed from all active. You can use the ID of a subscription to retrieve it directly, as long it's active. It will also return `null` if there is no active Subscription found. 792 | 793 | ```php 794 | use App\Models\User; 795 | 796 | $user = User::find(1); 797 | 798 | $subscription = $user->subscription('bc728326...'); 799 | ``` 800 | 801 | #### Retrieving all subscriptions 802 | 803 | You can find all subscriptions for an entity using the `subscriptions()` method, just like any Eloquent Relationship. This will actually find all subscriptions, include those expired, cancelled, and future. 804 | 805 | ```php 806 | use App\Models\User; 807 | 808 | $user = User::find(1); 809 | 810 | $subscriptions = $user->subscriptions()->get(); 811 | ``` 812 | 813 | Since using this method alone can be a little hectic, the relation includes some useful scopes to filter the records to retrieve: 814 | 815 | | Method | Description | 816 | |------------------|----------------------------------------------------------| 817 | | `active()` | Filters for active (not expired) subscriptions. | 818 | | `expired()` | Filters for expired (inactive) subscriptions. | 819 | | `soon()` | Filters for subscriptions yet to start. | 820 | | `cancelled()` | Filters for subscriptions that where actively cancelled. | 821 | | `notCancelled()` | Filters for subscriptions that where not cancelled. | 822 | 823 | For example, you can use the scopes to retrieve subscriptions that where cancelled and no longer active. 824 | 825 | ```php 826 | use Laragear\Subscriptions\Models\Subscription; 827 | 828 | $cancelled = Subscription::cancelled()->expired()->get(); 829 | ``` 830 | 831 | Also, Subscriptions do have timestamps, so you can use `oldest()` to query your subscriptions like any other Eloquent Model. 832 | 833 | ```php 834 | use Laragear\Subscriptions\Models\Subscription; 835 | 836 | $oldestExpired = Subscription::oldest()->expired()->get(); 837 | ``` 838 | 839 | ### Renew / Extend 840 | 841 | To renew a subscription for another cycle, use the `renewOnce()` method. It will _extend_ the subscription one cycle. 842 | 843 | ```php 844 | use Illuminate\Support\Facades\Auth; 845 | 846 | $user = Auth::user(); 847 | 848 | echo $user->subscription->ends_at; // "2020-02-28 23:59:59" 849 | 850 | echo $user->subscription->renewOnce()->ends_at; // "2020-03-31 23:59:59" 851 | ``` 852 | 853 | You may also extend a subscription for more than one cycle using `renewBy()` along the number of cycles. 854 | 855 | ```php 856 | echo $user->subscription->renewBy(2)->ends_at; // "2020-04-30 23:59:59" 857 | ``` 858 | 859 | > [!IMPORTANT] 860 | > 861 | > Since renewing a subscription removes the past [grace period](#grace-period), ensure you call `graceTo()` after renewing or [upgrading/downgrading](#upgrading--downgrading). 862 | 863 | ### Grace period 864 | 865 | When a Subscription is not renewed, the subscription will expire at the end of the cycle, rendering it non-active. This can be relatively problematic on some scenarios, for example, when you don't expect users to renew their subscriptions in your premises on a weekend when the store is closed. To avoid this, you can set a grace period in which the subscription will stay active after the cycle has ended. 866 | 867 | You can use `graceTo()` with the amount of days to take as a grace period, or a function that returns the time the grace period should end. 868 | 869 | ```php 870 | use Illuminate\Support\Facades\Auth; 871 | 872 | Auth::user() 873 | ->subscription 874 | ->renewOnce() 875 | ->graceTo(function ($start, $end) { 876 | return $end->nextFriday()->endOfDay(); 877 | }) 878 | ->save(); 879 | ``` 880 | 881 | If a subscription has a grace period or not, you can use `hasGracePeriod()` and `doesntHaveGracePeriod()`, respectively: 882 | 883 | ```php 884 | if ($subscription->hasGracePeriod()) { 885 | return 'Your subscription will terminate ' . $subscription->ends_at->diffForHumans(); 886 | } 887 | ``` 888 | 889 | You can check if the subscription is on grace period or not using `isOnGracePeriod()` and `isNotOnGracePeriod()`, respectively: 890 | 891 | ```php 892 | if ($subscription->isOnGracePeriod()) { 893 | return 'Your subscription already expired. Renew it to not lose access.' 894 | } 895 | ``` 896 | 897 | > [!WARNING] 898 | > 899 | > Ensure that you use `graceTo()` after [renewing it](#renew--extend), as a renewal will invalidate the previous grace period. 900 | 901 | ### Unsubscribe / Terminate 902 | 903 | To terminate a plan, use the `terminate()` method over it. This cancels the subscription immediately, and cuts short any grace period it may have to the moment of termination. 904 | 905 | ```php 906 | use Laragear\Subscriptions\Facades\Plan; 907 | use Laragear\Subscriptions\Models\Subscription; 908 | use Illuminate\Support\Facades\Auth; 909 | 910 | $user = Auth::user(); 911 | 912 | if ($user->cannot('terminate', $user->subscription)) { 913 | return "You don't have permissions to terminate this subscription."; 914 | } 915 | 916 | $user->subscription->terminate(); 917 | 918 | echo $user->subscription->isActive(); // false 919 | echo $user->subscription->isCancelled(); // true 920 | ``` 921 | 922 | > [!IMPORTANT] 923 | > 924 | > Terminating a Subscription doesn't detach the admin from it, but it will detach all other subscribers. This can be disabled with `terminateWithoutDetaching()` over the subscription 925 | > 926 | > ```php 927 | > $user->subscription->terminateWithoutDetaching(); 928 | > ``` 929 | > 930 | 931 | #### Cancellation mark 932 | 933 | Most of the time you will want to let the subscription expire instead of cancelling it immediately. To help to detect when users don't want to automatically renew their subscriptions, use the `cancel()` method, which sets a flag on the Subscription that later you can check with `isCancelled()`. 934 | 935 | ```php 936 | use Illuminate\Support\Facades\Auth; 937 | 938 | $user = Auth::user(); 939 | 940 | $user->subscription->cancel()->save(); 941 | 942 | echo $user->subscription->isActive(); // true 943 | echo $user->subscription->isCancelled(); // true 944 | ``` 945 | 946 | This is mostly a helper so later these subscriptions cannot be renewed automatically. 947 | 948 | ```php 949 | $user = Auth::user(); 950 | 951 | if ($user->subscription->isCancelled()) { 952 | return "Your subscription won't be renewed. You will lose access at {$user->subscription->ends_at}." 953 | } 954 | ``` 955 | 956 | ### Modifying the subscription 957 | 958 | Most of the subscription data is copied from its parent [Plan](#plans). Laragear's Subscriptions package automatically copies over the Plan data into the Subscription model, which allows you to modify a particular Subscription, isolating its data from the parent Plan itself and other similar Subscriptions. 959 | 960 | ```php 961 | $subscription->capabilities->set('deliveries', 20); 962 | 963 | echo $subscription->capability('deliveries'); // 20 964 | echo $subscription->plan->capability('deliveries'); // 10 965 | ``` 966 | 967 | You're free to edit the Subscription as you see fit. Remember calling `save()` to push the changes into the database permanently. 968 | 969 | ### Moving the cycle start 970 | 971 | Moving the subscription cycle start it's usually a big problem, but sometimes you will need to change it for legal reasons or because a particular demand. The way Laragear's Subscription package deals with moving the cycle start is by **extending the current cycle until the new date**. 972 | 973 | You only need to use `adjustStartTo()` with a new future date. The subscription will change at the date issued. 974 | 975 | ```php 976 | use Illuminate\Support\Facades\Auth; 977 | 978 | $user = Auth::user(); 979 | 980 | $user->subscription->adjustStartTo(now()->day(5)); 981 | 982 | $user->subscription->save(); 983 | 984 | return 'Your subscriptions is now billed the 5th of each month.'; 985 | ``` 986 | 987 | This method returns a `CarbonInterval` instance. You can use that, for example, to calculate how much to charge up-front or at the next billing date, based on the costs of the subscription. 988 | 989 | In the following example, the user will be charged up-front for the extended date, before persisting the cycle change. 990 | 991 | ```php 992 | use Illuminate\Support\Facades\Auth; 993 | 994 | // Today is 1st of January. We need the cycle to start from the 5th of each month. 995 | $extended = Auth::user()->subscription->adjustStartTo(now()->day(5)); 996 | 997 | // Multiply the days extended by the cost-per-day of the monthly subscription. 998 | $cost = $extended->totalDays * (29.90 / 30); // 3.98, if you're curious. 999 | 1000 | return "You will be charged $ {$cost} to accommodate the cycle change. Next cycle will be charged at the normal rate."; 1001 | ``` 1002 | 1003 | ### Shared subscriptions 1004 | 1005 | Normally, one entity will be tied to one subscription, but you can attach multiple entities to a subscription, likely to create "Family" or "Teams" based Subscriptions. You can use `attach()` over the Subscription with entity to attach. It also accepts an optional array of attributes to set in the pivot table. 1006 | 1007 | This is useful to mix with an [authorization gate](#authorization) to check if the plan allows to be shared, and there are slots free to attach the entities. 1008 | 1009 | ```php 1010 | use App\Models\User; 1011 | use Laragear\Subscriptions\Models\Subscription; 1012 | use Illuminate\Support\Facades\Auth; 1013 | 1014 | $admin = Auth::user(); 1015 | $guests = User::find(4); 1016 | 1017 | if ($admin->cannot('attachTo', [$admin->subscription, $guests])) { 1018 | return 'Only admins can attach users to this subscriptions.'; 1019 | } 1020 | 1021 | $admin->subscription->attach($guests); 1022 | ``` 1023 | 1024 | To remove an entity from the subscription, use `detach()` over the subscription and the entity. 1025 | 1026 | ```php 1027 | use App\Models\User; 1028 | use Laragear\Subscriptions\Facades\Subscription; 1029 | use Illuminate\Support\Facades\Auth; 1030 | 1031 | $admin = Auth::user(); 1032 | $guest = User::find(4); 1033 | 1034 | $subscription = Subscription::find('bc728326...'); 1035 | 1036 | if ($admin->cannot('detachFrom', [$admin->subscription, $guest])) { 1037 | return 'Only admins can attach users to this subscriptions.'; 1038 | } 1039 | 1040 | $admin->subscription->detach($guest); 1041 | ``` 1042 | 1043 | ### Used and unused time 1044 | 1045 | You can easily get how much of the cycle has been used or remains using `used()` and `unused()`, respectively. These return a `CarbonInterval` with the difference in time. 1046 | 1047 | For example, we can use the `unused()` interval to calculate how much an upgrade will cost for other Plan by subtracting the current cost of the unused days. 1048 | 1049 | ```php 1050 | $user = User::find(1); 1051 | 1052 | $newPlanCost = 49.99 1053 | 1054 | $price = $newPlanCost - $user->subscription()->unused()->days * 29.99; 1055 | 1056 | return "Upgrade now and pay a reduced price of $ $price."; 1057 | ``` 1058 | 1059 | > [!IMPORTANT] 1060 | > 1061 | > Intervals for `used()` and `unused()` return empty intervals if the subscription hasn't started or already ended. 1062 | 1063 | ### Upgrading / Downgrading 1064 | 1065 | To upgrade or downgrade a subscription, use the `changeTo()` method with the Plan you want to change to, over the entity that's subscribed. 1066 | 1067 | ```php 1068 | use App\Models\User; 1069 | use Laragear\Subscriptions\Models\Plan; 1070 | 1071 | $user = User::find(1); 1072 | 1073 | $plan = Plan::find('b6954722...'); 1074 | 1075 | if ($user->cant('upgradeTo', $plan)) { 1076 | return "You cannot upgrade to the plan $plan->name"; 1077 | } 1078 | 1079 | $upgraded = $user->changeTo($plan); 1080 | ``` 1081 | 1082 | > [!TIP] 1083 | > 1084 | > This will automatically migrate all attached entities to the new Subscription, and keep the same _admin_. 1085 | 1086 | You can also change a Subscription to another Plan manually using the `Change` facade. 1087 | 1088 | ```php 1089 | use Laragear\Subscriptions\Facades\Change; 1090 | use Laragear\Subscriptions\Models\Plan; 1091 | use Laragear\Subscriptions\Models\Subscription; 1092 | 1093 | $plan = Plan::find('b6954722...'); 1094 | 1095 | $new = Change::to(Subscription::find('bc728326...'), $plan); 1096 | ``` 1097 | 1098 | > [!IMPORTANT] 1099 | > 1100 | > Changing a Subscription to another Plan creates a new Subscription. It doesn't change the current Subscription. Ensure you [set a grace period](#grace-period) if needed to. 1101 | 1102 | ## Subscription Capabilities 1103 | 1104 | Subscriptions and Plans contain capabilities. This is copied directly from the Plan itself, and allows checking if an entity has certain power to do something in your app. 1105 | 1106 | You can check capabilities using `capability()` which returns the value of the capability. It also accepts a default value to return if the capability doesn't exist. 1107 | 1108 | ```php 1109 | use Illuminate\Support\Facades\Auth; 1110 | 1111 | $user = Auth::user(); 1112 | 1113 | $count = $user->deliveries()->forCurrentMonth()->count(); 1114 | 1115 | $max = $user->subscription->capability('deliveries', 0); 1116 | 1117 | return "You have made $count deliveries out of $max for this month."; 1118 | ``` 1119 | 1120 | You can use `capabilities` property directly to set a new value. For example, we can make a lottery to upgrade the deliveries amount for the winners. 1121 | 1122 | ```php 1123 | use Illuminate\Support\Facades\Auth; 1124 | use Illuminate\Support\Lottery; 1125 | 1126 | $subscription = Auth::user()->subscription; 1127 | 1128 | return Lottery::odds(1, 20) 1129 | ->winner(function () use ($subscription) { 1130 | $subscription->capabilities->set('deliveries', 20)->save(); 1131 | $subscription->save(); 1132 | 1133 | return 'You are the lucky winner! You now have 20 deliveries forever!'; 1134 | }) 1135 | ->loser(fn() => 'Better luck next time!') 1136 | ->choose(); 1137 | ``` 1138 | 1139 | You can also rewrite the capabilities entirely by setting the `capabilities` property using an array. 1140 | 1141 | ```php 1142 | $user->subscription->capabilities = [ 1143 | 'deliveries' => 20, 1144 | 'priority' => 'low' 1145 | ]; 1146 | 1147 | $user->subscription->save(); 1148 | ``` 1149 | 1150 | ### Checking capabilities 1151 | 1152 | To avoid using complex syntax, you can use convenient methods to check for the capabilities of a subscription. Use `check()` along the capability key in `dot.notation` and one of the self-explanatory methods to check if a condition is true or false. 1153 | 1154 | ```php 1155 | $user = Auth::user(); 1156 | 1157 | if ($user->subscription->hasDisabled('delivery.express')) { 1158 | return 'Your subscription does not support Express Deliveries'; 1159 | } 1160 | ``` 1161 | 1162 | These are the following methods you can chain to a check 1163 | 1164 | | Method | Description | 1165 | |-------------|--------------------------------------------------| 1166 | | hasEnabled | Checks the capability exists and is truthy. | 1167 | | canUse | Checks the capability exists and is truthy. | 1168 | | hasDisabled | Checks the capability doesn't exist or is falsy. | 1169 | | cannotUse | Checks the capability doesn't exist or is falsy. | 1170 | | cantUse | Checks the capability doesn't exist or is falsy. | 1171 | | isBlank | Checks the capability value is "blank". | 1172 | | isFilled | Checks the capability value is "filled". | 1173 | 1174 | You can also fluently compare values using the `check()` method: 1175 | 1176 | ```php 1177 | $user = Auth::user(); 1178 | 1179 | $count = $user->deliveries()->forCurrentMonth()->count(); 1180 | 1181 | if ($user->subscription->check($count)->exceeds('deliveries')) { 1182 | return 'You have depleted all your available deliveries for this month.'; 1183 | } 1184 | ``` 1185 | 1186 | | Method | Description | 1187 | |-------------------------------------|---------------------------------------------------------------------| 1188 | | `isGreaterThan($capability)` | Checks if the value is greater than the named capability. | 1189 | | `exceeds($capability)` | Checks if the value is greater than the named capability. | 1190 | | `isEqualOrGreaterThan($capability)` | Checks if the value is equal or greater than the named capability. | 1191 | | `is($capability)` | Checks if the value is equal to the named capability, strictly. | 1192 | | `isNot($capability)` | Checks if the value is not equal to the named capability, strictly. | 1193 | | `isSameAs($capability)` | Checks if the value is equal to the named capability. | 1194 | | `isEqualOrLessThan($capability)` | Checks if the value is equal or less than the named capability. | 1195 | | `doesntExceeds($capability)` | Checks if the value is equal or less than the named capability. | 1196 | | `isLessThan($capability)` | Checks if the value is less than the named capability. | 1197 | 1198 | ## Authorization 1199 | 1200 | This package includes a lot of helpers to authorize actions over Plans and Subscriptions. All of those are managed via the `App\Policies\SubscriptionPolicy` and `App\Policies\PlanPolicy` [policy classes](https://laravel.com/docs/11.x/authorization#creating-policies) that should be already [installed](#1-install-the-files) in `app\Policies`. 1201 | 1202 | | Method | Description | 1203 | |--------------------------------------|----------------------------------------------------------------------| 1204 | | `'subscribe', Plan::class` | Determines that the user can subscribe to any Plan. | 1205 | | `'subscribeTo', $plan` | Determines that the user can subscribe to a given Plan. | 1206 | | `'upgradeTo', $plan` | Determines that the user subscription can upgrade to a given Plan. | 1207 | | `'downgradeTo', $plan` | Determines that the user subscription can downgrade to a given Plan. | 1208 | | `'renew', $subscription` | Determines that the user can renew the Subscription. | 1209 | | `'cancel', $subscription` | Determines that the user can cancel the Subscription. | 1210 | | `'terminate', $subscription` | Determines that the user can terminate the subscription. | 1211 | | `'attachTo', $subscription, $user` | Determines that the user can attach an user to the subscription. | 1212 | | `'detachFrom', $subscription, $user` | Determines that the user can detach an user from the subscription. | 1213 | 1214 | This allows you to use authorization gates directly in your application, like inside controllers or views. 1215 | 1216 | ```blade 1217 | @can('terminate', $subscription) 1218 | Terminate subscription 1219 | @else 1220 | You are not allowed to terminate the subscription. 1221 | @endcan 1222 | ``` 1223 | 1224 | You're free to change them as you see fit for your application. For example, you can add a `hasUpgradeDiscount()` gate to check if a subscription upgrade is eligible for a discount. 1225 | 1226 | ```php 1227 | // app\Policies\SubscriptionPolicy.php 1228 | public function hasUpgradeDiscount($user, Subscription $subscription): bool 1229 | { 1230 | return $subscription->isActive() 1231 | && $subscription->isNotOnGracePeriod() 1232 | && $subscription->isNotFirstCycle(); 1233 | } 1234 | ``` 1235 | 1236 | Then, you can easily use the newly created gate in your application: 1237 | 1238 | ```blade 1239 | @can('haveUpgradeDiscount', $user->subscription) 1240 | You're eligible for an upgrade discount of $ {{ $discount }}. 1241 | @endcan 1242 | ``` 1243 | 1244 | ## Pruning subscribers 1245 | 1246 | When plans or subscription are deleted, the pivot table `subscribables` may contain orphaned records. To prune them, use `subscriptions:prune`. 1247 | 1248 | ```shell 1249 | php artisan subscriptions:prune 1250 | 1251 | # Removed 10 dangling records pointing to deleted plans or subscriptions. 1252 | ``` 1253 | 1254 | ## Events 1255 | 1256 | This package fires the following self-explanatory events: 1257 | 1258 | | Event | Description | 1259 | |--------------------------|---------------------------------------------------------------| 1260 | | `OnDemandPlanCreated` | A new on-demand Plan was created. | 1261 | | `SubscriberAttached` | An existing subscription was attached to an entity. | 1262 | | `SubscriberDetached` | An existing subscription was detached to an entity. | 1263 | | `SubscriptionCancelled` | An existing subscription was marked as cancelled. | 1264 | | `SubscriptionChanged` | An existing subscription was changed to another Plan. | 1265 | | `SubscriptionCreated` | A new subscription was created. | 1266 | | `SubscriptionDowngraded` | An existing subscription was replaced for a new "worse" one. | 1267 | | `SubscriptionRenewed` | An existing subscription was renewed for a new cycle. | 1268 | | `SubscriptionTerminated` | An existing subscription was terminated. | 1269 | | `SubscriptionUpgraded` | An existing subscription was replaced for a new "better" one. | 1270 | 1271 | ## Polymorphic relations 1272 | 1273 | To use polymorphic relations, use the `Subscription::addPolymorphicRelations()` to add the models you plan to attach to the Subscription model. You can do this in your `AppServiceProvider` or `bootstrap/app.php` file. 1274 | 1275 | ```php 1276 | use App\Models\Director; 1277 | use App\Models\Producer; 1278 | use App\Models\Actor; 1279 | use Illuminate\Foundation\Application; 1280 | use Laragear\Subscriptions\Models\Subscription; 1281 | 1282 | return Application::configure(basePath: dirname(__DIR__)) 1283 | ->booted(function () { 1284 | Subscription::macroPolymorphicRelations([ 1285 | 'producers' => Producer::class, 1286 | 'actors' => Actor::class, 1287 | 'director' => Director::class, 1288 | ]) 1289 | ) 1290 | ->create(); 1291 | ``` 1292 | 1293 | Once it's done, you can easily access these polymorphic relations like you would normally do using Eloquent Models. These new methods you register will even support [eager-loading](https://laravel.com/docs/11.x/eloquent-relationships#eager-loading). 1294 | 1295 | ```php 1296 | use Laragear\Subscriptions\Models\Subscription; 1297 | 1298 | $subscription = Subscription::with('actors')->find('bc728326...'); 1299 | 1300 | foreach ($subscription->actors as $actor) { 1301 | echo $actor->name; 1302 | }; 1303 | ``` 1304 | 1305 | If you like to have manual control on the dynamic relations, you can also use an array with both the model class name and a callback to use as a relation builder. 1306 | 1307 | ```php 1308 | use Laragear\Subscriptions\Models\Subscription; 1309 | use App\Models\Producer; 1310 | use App\Models\Actor; 1311 | 1312 | public function boot() 1313 | { 1314 | Subscription::macroPolymorphicRelations([ 1315 | 'actors' => Actor::class, 1316 | 'producers' => [ 1317 | Producer::class, 1318 | function($subscription) { 1319 | return $subscription 1320 | ->morphedByMany(Producer::class, 'subscribable') 1321 | ->credited(); 1322 | } 1323 | ]; 1324 | ]); 1325 | 1326 | // ... 1327 | } 1328 | ``` 1329 | 1330 | While you can use [dynamic relationships](https://laravel.com/docs/11.x/eloquent-relationships#dynamic-relationships) directly,these methods add more data to the relationship, and makes the Subscription model aware of the relationship nature (monomorphic or polymorphic). 1331 | 1332 | ### Retrieving polymorphic records manually 1333 | 1334 | If you're using polymorphic subscribers, the default model is `App\Models\User`, so the `subscribers` property will retrieve all models for that polymorphic relation, unless you change it to other model. 1335 | 1336 | ```php 1337 | use Laragear\Subscriptions\Models\Subscription; 1338 | 1339 | $subscription = Subscription::find('bc728326...'); 1340 | 1341 | $users = $subscription->subscribers; 1342 | ``` 1343 | 1344 | When not using the default model, you will need to use the `subscribers()` with the model name or instance to retrieve the different types of subscribers. 1345 | 1346 | ```php 1347 | use Laragear\Subscriptions\Models\Subscription; 1348 | use App\Models\Cameraman; 1349 | use App\Models\Photographer; 1350 | 1351 | $subscription = Subscription::find('bc728326...'); 1352 | 1353 | $cameramen = $subscription->subscribers(Cameraman::class)->get(); 1354 | $photographers = $subscription->subscribers(Photographer::class)->get(); 1355 | ``` 1356 | 1357 | ## Testing 1358 | 1359 | This package includes a `PlanFactory` and `SubscriptionFactory`, you can easily access from their respective models. 1360 | 1361 | ```php 1362 | use Laragear\Subscriptions\Models\Plan; 1363 | use App\Models\User; 1364 | 1365 | $plan = Plan::factory()->called('test')->monthly()->createOne(); 1366 | ``` 1367 | 1368 | For the case of the Subscription Factory, you may want to attach entities after creating them using the `attach()` method. 1369 | 1370 | ```php 1371 | use Laragear\Subscriptions\Models\Subscription; 1372 | use App\Models\Company; 1373 | 1374 | $company = Company::find(1); 1375 | 1376 | $subscription = Subscription::factory() 1377 | ->called('test') 1378 | ->monthly() 1379 | ->createOne(); 1380 | 1381 | $subscription->attach($company); 1382 | ``` 1383 | 1384 | ## Laravel Octane compatibility 1385 | 1386 | - The only singleton registered is the Plan Bag. On-demand plans removes themselves from the bag once created. 1387 | - There are no singletons using a stale config instance. 1388 | - There are no singletons using a stale request instance. 1389 | - There are no static properties written during a request. 1390 | 1391 | There should be no problems using this package with Laravel Octane. 1392 | 1393 | ## Security 1394 | 1395 | If you discover any security related issues, please email darkghosthunter@gmail.com instead of using the issue tracker. 1396 | 1397 | # License 1398 | 1399 | This specific package version is licensed under the terms of the [MIT License](LICENSE.md), at time of publishing. 1400 | 1401 | [Laravel](https://laravel.com) is a Trademark of [Taylor Otwell](https://github.com/TaylorOtwell/). Copyright © 2011-2025 Laravel LLC. 1402 | --------------------------------------------------------------------------------