PHP code example of vnuswilliams / laravel-subscription

1. Go to this page and download the library: Download vnuswilliams/laravel-subscription library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

vnuswilliams / laravel-subscription example snippets


// database/seeders/PlanSeeder.php

use Vnuswilliams\Subscription\Models\Plan;
use Vnuswilliams\Subscription\Enums\FeatureType;
use Vnuswilliams\Subscription\Enums\PeriodicityType;

// Pro Plan — monthly, 7-day grace period
$pro = Plan::create([
    'name'             => 'Pro',
    'slug'             => 'pro',
    'description'      => 'For growing teams.',
    'price'            => 19.99,
    'periodicity_type' => PeriodicityType::Month->value,  // 'month'
    'periodicity'      => 1,
    'trial_days'       => 0,
    'grace_days'       => 7,
    'is_active'        => true,
]);

// Consumable feature: employee quota
$pro->features()->create([
    'slug'    => 'max-employees',
    'name'    => 'Number of employees',
    'type'    => FeatureType::Consumable->value,  // 'consumable'
    'charges' => 25,  // 25 available slots
]);

// Boolean feature: access to the employee portal
$pro->features()->create([
    'slug'    => 'employee-portal',
    'name'    => 'Employee Portal',
    'type'    => FeatureType::Boolean->value,  // 'boolean'
    'charges' => null,  // null = unlimited / no counter
]);

// Free Plan — permanent (no periodicity), 15-day trial
Plan::create([
    'name'             => 'Free',
    'slug'             => 'free',
    'price'            => 0,
    'periodicity_type' => null,   // null = permanent plan, never expires
    'periodicity'      => null,
    'trial_days'       => 15,
    'grace_days'       => 0,
    'is_active'        => true,
]);

// app/Enums/FeatureEnum.php
enum FeatureEnum: string
{
    case MAX_EMPLOYEES    = 'max-employees';
    case EMPLOYEE_PORTAL  = 'employee-portal';
    case DOCUMENTS        = 'documents';
    case ADVANCED_REPORTS = 'advanced-reports';
    case PRIORITY_SUPPORT = 'priority-support';
}

// app/Models/Company.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Vnuswilliams\Subscription\Traits\HasSubscriptions;

class Company extends Model
{
    use HasSubscriptions;
}

$company->subscribeTo('pro');
$company->hasActiveSubscription();
$company->canConsume('max-employees', 1);
$company->balance('max-employees');

use Vnuswilliams\Subscription\Facades\Subscription;

Subscription::subscribeTo($company, 'pro');
Subscription::cancel($company);
Subscription::canConsume($company, 'max-employees', 1);
Subscription::balance($company, 'max-employees');

use Vnuswilliams\Subscription\SubscriptionManager;

class SubscriptionService
{
    public function __construct(
        private readonly SubscriptionManager $subscription,
    ) {}

    public function canAddEmployee(Company $company): bool
    {
        return $this->subscription->hasActiveSubscription($company)
            && $this->subscription->canConsume($company, FeatureEnum::MAX_EMPLOYEES->value, 1);
    }
}

// By slug
$company->subscribeTo('pro');

// By instance
$plan = Plan::where('slug', 'pro')->firstOrFail();
$company->subscribeTo($plan);

// Via the Facade
use Vnuswilliams\Subscription\Facades\Subscription;
Subscription::subscribeTo($company, 'pro');

// Uses the plan price
$company->subscribeTo('pro');

// Stores 24.99 on the subscription, without changing the plan price
$company->subscribeTo('pro', price: 24.99);

// Integer-like prices are supported too
Subscription::subscribeTo($company, 'enterprise', price: 100);

// Free plan with a 15-day trial
$company->subscribeTo('free', expiration: now()->addDays(15));

// Promotional offer: 3 months free
$company->subscribeTo('pro', expiration: now()->addMonths(3));

// Immediate switch: the old subscription is removed, the new one starts
$company->switchTo('business');

// Deferred switch: the old subscription runs until its end date
$company->switchTo('starter', immediately: false);

// Via the Facade
Subscription::switchTo($company, 'business');

$company->subscription->cancel();

// Or via the Facade
Subscription::cancel($company);

if ($company->subscription->isCanceled()) {
    // The user has cancelled, but still has access until ends_at
    $expiresAt = $company->subscriptionExpiresAt();
}

$company->subscription->suppress();

// Or via the Facade
Subscription::suppress($company);

$company->renewSubscription();

// Or via the Facade
Subscription::renew($company);

// Is the subscription valid? (active, on trial, or in grace period)
$company->hasActiveSubscription(); // bool

// What is the current plan?
$plan = $company->currentPlan(); // Plan|null
echo $plan->name;  // 'Pro'
echo $plan->slug;  // 'pro'

// When does it expire?
$date = $company->subscriptionExpiresAt(); // Carbon|null

// Direct access to the Subscription model
$sub = $company->subscription;
$sub->isActive();        // bool
$sub->isOnTrial();       // bool
$sub->isOnGracePeriod(); // bool
$sub->isCanceled();      // bool
$sub->isExpired();       // bool
$sub->hasAccess();       // bool — aggregates all valid states

// Does the company have access to the employee portal?
if ($company->canConsume('employee-portal')) {
    // access granted
}

// Via the Facade
if (Subscription::canConsume($company, 'employee-portal')) {
    // access granted
}

// ✅ Recommended pattern
if ($company->canConsume('max-employees', 1)) {

    // Business action first
    $employee = Employee::create([...]);

    // Consumption afterwards
    $company->consume('max-employees', 1);

} else {
    return back()->with('error', 'Employee quota reached. Please upgrade your plan.');
}

// Deleting an employee → releases 1 slot
$employee->delete();
$company->release('max-employees', 1);

// Total slots allocated by the plan
$total = $company->totalCharges('max-employees');  // e.g. 25

// Slots consumed in the current period
$used = $company->usedCharges('max-employees');    // e.g. 17

// Remaining slots (PHP_INT_MAX if unlimited)
$remaining = $company->balance('max-employees');   // e.g. 8

Plan::create([
    'slug'       => 'pro',
    'grace_days' => 7,   // 7-day grace period after expiration
    // ...
]);

Plan::create([
    'slug'       => 'free',
    'grace_days' => 0,   // No grace period on the free plan
    // ...
]);

// routes/web.php

// Requires any valid subscription
Route::middleware(['auth', 'subscribed'])->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
    Route::get('/employees', [EmployeeController::class, 'index']);
});

// Requires a specific plan (by slug)
Route::middleware(['auth', 'subscribed:business'])->group(function () {
    Route::get('/analytics', [AnalyticsController::class, 'index']);
    Route::get('/support', [SupportController::class, 'index']);
});

// app/Listeners/SendWelcomeEmail.php

use Vnuswilliams\Subscription\Events\SubscriptionCreated;

class SendWelcomeEmail
{
    public function handle(SubscriptionCreated $event): void
    {
        $subscriber = $event->subscription->subscriber;
        // $subscriber is the Company, User, etc. instance

        Mail::to($subscriber->email)->send(new WelcomeMail($subscriber));
    }
}

// app/Listeners/NotifyQuotaExhausted.php

use Vnuswilliams\Subscription\Events\FeatureQuotaReached;

class NotifyQuotaExhausted
{
    public function handle(FeatureQuotaReached $event): void
    {
        $subscriber  = $event->subscription->subscriber;
        $featureSlug = $event->feature->slug;  // e.g. 'max-employees'

        // Send a notification suggesting an upgrade
        $subscriber->notify(new QuotaReachedNotification($featureSlug));
    }
}

// routes/console.php

use Illuminate\Support\Facades\Schedule;

Schedule::command('subscription:check-lifecycle')->daily();

protected function schedule(Schedule $schedule): void
{
    $schedule->command('subscription:check-lifecycle')->daily();
}

// app/Services/SubscriptionService.php

use Vnuswilliams\Subscription\SubscriptionManager;

final class SubscriptionService
{
    public function __construct(
        private readonly SubscriptionManager $subscription,
    ) {}

    public function subscribeTo(Company $company, PlanEnum $planEnum): Subscription
    {
        $plan = $this->subscription->resolvePlan($planEnum->value);

        // App-specific business logic:
        // the FREE plan gets a manual 15-day trial
        if ($planEnum === PlanEnum::FREE) {
            return $this->subscription->subscribeTo($company, $plan, expiration: now()->addDays(15));
        }

        return $this->subscription->subscribeTo($company, $plan);
    }

    public function canAddEmployee(Company $company): bool
    {
        return $this->subscription->hasActiveSubscription($company)
            && $this->subscription->canConsume($company, FeatureEnum::MAX_EMPLOYEES->value, 1);
    }

    public function consumeEmployeeSlot(Company $company): void
    {
        $this->subscription->consume($company, FeatureEnum::MAX_EMPLOYEES->value, 1);
    }

    public function releaseEmployeeSlot(Company $company): void
    {
        $this->subscription->release($company, FeatureEnum::MAX_EMPLOYEES->value, 1);
    }

    public function remainingEmployeeSlots(Company $company): int
    {
        return $this->subscription->balance($company, FeatureEnum::MAX_EMPLOYEES->value);
    }

    public function currentPlan(Company $company): ?PlanEnum
    {
        $plan = $this->subscription->currentPlan($company);

        return $plan !== null ? PlanEnum::tryFrom($plan->slug) : null;
    }
}

// app/Http/Controllers/EmployeeController.php

class EmployeeController extends Controller
{
    public function __construct(
        private readonly SubscriptionService $subscriptionService,
    ) {}

    public function store(StoreEmployeeRequest $request): RedirectResponse
    {
        $company = $request->user()->company;

        if (! $this->subscriptionService->canAddEmployee($company)) {
            return back()->with('error', 'Employee quota reached.');
        }

        $employee = Employee::create($request->validated());

        $this->subscriptionService->consumeEmployeeSlot($company);

        return redirect()->route('employees.index')
            ->with('success', 'Employee added successfully.');
    }

    public function destroy(Employee $employee): RedirectResponse
    {
        $company = auth()->user()->company;

        $employee->delete();

        // Release the slot so it can be reused
        $this->subscriptionService->releaseEmployeeSlot($company);

        return redirect()->route('employees.index')
            ->with('success', 'Employee deleted.');
    }
}

use Vnuswilliams\Subscription\Enums\SubscriptionStatus;
use Vnuswilliams\Subscription\Enums\FeatureType;
use Vnuswilliams\Subscription\Enums\PeriodicityType;

PeriodicityType::Day;    // 'day'
PeriodicityType::Week;   // 'week'
PeriodicityType::Month;  // 'month'
PeriodicityType::Year;   // 'year'

FeatureType::Boolean;    // 'boolean'
FeatureType::Consumable; // 'consumable'

SubscriptionStatus::Active;        // 'active'
SubscriptionStatus::OnTrial;       // 'on_trial'
SubscriptionStatus::OnGracePeriod; // 'on_grace_period'
SubscriptionStatus::Canceled;      // 'canceled'
SubscriptionStatus::Expired;       // 'expired'

use Vnuswilliams\Subscription\Exceptions\InvalidPlanException;
use Vnuswilliams\Subscription\Exceptions\SubscriptionNotFoundException;
use Vnuswilliams\Subscription\Exceptions\FeatureNotFoundException;

try {
    Subscription::subscribeTo($company, 'non-existent-plan');
} catch (InvalidPlanException $e) {
    // Plan not found or inactive
    Log::warning($e->getMessage());
}
blade
@php
    $total     = $company->totalCharges('max-employees');
    $used      = $company->usedCharges('max-employees');
    $remaining = $company->balance('max-employees');
    $percent   = $total > 0 ? round(($used / $total) * 100) : 0;
@endphp

<div class="quota-bar">
    <div class="quota-bar__fill" style="width: {{ $percent }}%"></div>
</div>
<p>{{ $used }} / {{ $total }} employees — {{ $remaining }} slots remaining</p>
bash
# Manual execution
php artisan subscription:check-lifecycle