Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Plans Laravel Package

lacodix/laravel-plans

Laravel package to manage SaaS plans, addons, subscriptions, and optional features. Supports countable/uncountable features with limits, resets, and consumption across plans, plus translations, ordering, and metadata—billing/invoicing not included.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require lacodix/laravel-plans
    php artisan vendor:publish --provider="Lacodix\Plans\PlansServiceProvider" --tag="migrations"
    php artisan migrate
    
  2. Configure: Publish the config file:

    php artisan vendor:publish --provider="Lacodix\Plans\PlansServiceProvider" --tag="config"
    

    Update config/plans.php with your Stripe/Paddle/etc. API keys and billing settings.

  3. First Use Case: Define a plan via a migration or Tinker:

    use Lacodix\Plans\Models\Plan;
    
    Plan::create([
        'name' => 'Basic',
        'slug' => 'basic',
        'price' => 9.99,
        'billing_interval' => 'month',
        'currency' => 'USD',
        'description' => 'Basic features for individuals',
    ]);
    
  4. Key Models:

    • Plan: Represents a subscription tier.
    • Feature: Optional features tied to plans (countable/uncountable).
    • Subscription: Tracks user subscriptions (active, canceled, etc.).
    • Addon: Optional add-ons for extra features.

Implementation Patterns

Core Workflows

1. Plan Management

  • CRUD via Eloquent:
    // Create a plan
    $plan = Plan::create([...]);
    
    // Fetch plans for checkout
    $plans = Plan::with('features')->where('is_active', true)->get();
    
  • Dynamic Pricing: Use price and billing_interval (e.g., month, year) to calculate recurring charges.
    $plan->price; // 9.99
    $plan->billing_interval; // 'month'
    

2. Feature Integration

  • Countable Features (e.g., "Storage"):
    $feature = Feature::create([
        'name' => 'Storage',
        'slug' => 'storage',
        'is_countable' => true,
        'max_count' => 10, // Optional for countable features
    ]);
    $plan->features()->attach($feature, ['count' => 5]); // Attach with a count
    
  • Uncountable Features (e.g., "API Access"):
    $feature = Feature::create([
        'name' => 'API Access',
        'slug' => 'api_access',
        'is_countable' => false,
    ]);
    $plan->features()->attach($feature); // Simple boolean attachment
    

3. Subscription Handling

  • Subscribe a User:
    use Lacodix\Plans\Facades\Plans;
    
    $subscription = Plans::subscribe($user, $planId, [
        'trial_days' => 7,
        'billing_cycle_anchor' => now()->startOfMonth(),
    ]);
    
  • Check User Features:
    if ($user->hasFeature('storage', 10)) {
        // Grant access
    }
    
  • Cancel/Resume:
    $subscription->cancel(); // Soft cancel
    $subscription->resume(); // Resume canceled subscription
    

4. Add-ons

  • Attach Add-ons to Subscription:
    $addon = Addon::create([...]);
    $subscription->addons()->attach($addon);
    
  • Check Add-on Access:
    if ($subscription->hasAddon('premium_support')) {
        // Enable premium support
    }
    

5. Webhooks (Stripe/Paddle)

  • Listen for Events: Publish the webhook config:
    php artisan vendor:publish --provider="Lacodix\Plans\PlansServiceProvider" --tag="webhook-config"
    
    Update config/plans/webhooks.php with your endpoint and secret. Handle events in app/Http/Controllers/WebhookController:
    use Lacodix\Plans\Events\SubscriptionCreated;
    
    public function handleWebhook(Request $request) {
        event(new SubscriptionCreated($request->all()));
    }
    

Integration Tips

1. Middleware for Feature Gating

Create middleware to check features before accessing routes:

namespace App\Http\Middleware;

use Closure;
use Lacodix\Plans\Facades\Plans;

class CheckFeature
{
    public function handle($request, Closure $next, $featureSlug, $minCount = null)
    {
        if (!Plans::userHasFeature($request->user(), $featureSlug, $minCount)) {
            abort(403, 'Access denied');
        }
        return $next($request);
    }
}

Register in app/Http/Kernel.php:

'check.feature' => \App\Http\Middleware\CheckFeature::class,

2. Blade Directives for Templates

Create a directive to display feature availability:

Blade::directive('feature', function ($featureSlug) {
    return "<?php if(\Lacodix\Plans\Facades\Plans::userHasFeature(auth()->user(), {$featureSlug})): ?>";
});
Blade::directive('endif', function () {
    return "<?php endif; ?>";
});

Usage in Blade:

@feature('api_access')
    <button>Enable API</button>
@endif

3. Seeding Plans

Use a seeder to populate initial plans/features:

public function run()
{
    $basicPlan = Plan::create([...]);
    $proPlan = Plan::create([...]);

    $storageFeature = Feature::create([
        'name' => 'Storage',
        'slug' => 'storage',
        'is_countable' => true,
        'max_count' => 100,
    ]);

    $basicPlan->features()->attach($storageFeature, ['count' => 10]);
    $proPlan->features()->attach($storageFeature, ['count' => 100]);
}

4. Testing Subscriptions

Use factories and mocks for testing:

$user = User::factory()->create();
$plan = Plan::factory()->create();

$subscription = Plans::subscribe($user, $plan->id);
$this->assertTrue($user->hasFeature('storage', 10));

Gotchas and Tips

Pitfalls

1. Webhook Delays

  • Issue: Webhook events may arrive out of order or with delays.
  • Fix: Use Plans::syncSubscription() to force-sync subscriptions on critical paths (e.g., feature checks).
  • Tip: Log webhook events for debugging:
    \Log::info('Webhook received', $request->all());
    

2. Feature Count Mismatches

  • Issue: Countable features may not update correctly if the subscription is canceled/resumed.
  • Fix: Always check features via Plans::userHasFeature() instead of caching counts in the session.
  • Tip: Use Plans::refreshUserFeatures($user) to manually refresh feature counts.

3. Billing Interval Confusion

  • Issue: Incorrect billing_interval (e.g., month vs. year) can lead to misaligned billing cycles.
  • Fix: Validate intervals in migrations/models:
    protected $rules = [
        'billing_interval' => 'required|in:day,week,month,year',
    ];
    

4. Add-on Overlaps

  • Issue: Add-ons may conflict with plan features (e.g., duplicate storage).
  • Fix: Use Plans::getUserFeatureCount($user, 'storage') to aggregate counts from both plans and add-ons.

5. Stripe/Paddle Sync

  • Issue: External payment provider (Stripe/Paddle) subscriptions may not sync with the local subscription table.
  • Fix: Implement a Plans::syncWithProvider() method to reconcile differences:
    $subscription = Plans::findByProviderId($providerId);
    $subscription->syncWithProvider(); // Updates local state
    

Debugging Tips

1. Log Subscription Events

Enable debug logging in config/plans.php:

'debug' => env('PLANS_DEBUG', false),

Check logs for subscription lifecycle events:

[2025-11-20] INFO  Subscription created for user #1: Plan #5 (Basic)
[2025-11-20] INFO  Subscription canceled for user #1: Plan #5 (Basic)

2. Inspect Feature Queries

Use Laravel Debugbar to inspect feature queries

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours