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 Steps

  1. Installation:

    composer require lacodix/laravel-plans
    php artisan vendor:publish --provider="Lacodix\LaravelPlans\LaravelPlansServiceProvider"
    

    Publish the config and migrations, then run:

    php artisan migrate
    
  2. Add Trait to User Model:

    use Lacodix\LaravelPlans\Models\Traits\HasSubscriptions;
    
    class User extends Authenticatable {
        use HasSubscriptions;
    }
    
  3. Create a Basic Plan:

    use Lacodix\LaravelPlans\Enums\Interval;
    use Lacodix\LaravelPlans\Models\Plan;
    
    Plan::create([
        'slug' => 'basic-plan',
        'name' => 'Basic Plan',
        'price' => 9.99,
        'active' => true,
        'billing_interval' => Interval::MONTH,
        'billing_period' => 1,
    ]);
    
  4. Subscribe a User:

    $user = User::first();
    $user->subscribe(Plan::where('slug', 'basic-plan')->first());
    

First Use Case: Feature-Based Access Control

// Create a feature
$feature = Feature::create([
    'slug' => 'api-access',
    'name' => ['en' => 'API Access'],
]);

// Attach to a plan
$plan->features()->attach($feature, ['value' => 100]);

// Check if user has access
if ($user->hasFeature('api-access', 1)) {
    // Grant access
}

Implementation Patterns

Core Workflows

Plan Management

  1. Plan Creation with Features:

    $plan = Plan::create([
        'slug' => 'premium',
        'name' => ['en' => 'Premium Plan'],
        'price' => 29.99,
        'billing_interval' => Interval::YEAR,
        'billing_period' => 1,
    ]);
    
    $feature = Feature::create([
        'slug' => 'storage',
        'name' => ['en' => 'Storage'],
        'countable' => true,
    ]);
    
    $plan->features()->attach($feature, [
        'value' => 1024, // GB
        'resettable_period' => 1,
        'resettable_interval' => Interval::MONTH,
    ]);
    
  2. Localized Plan Names:

    $plan->name = ['en' => 'Premium', 'de' => 'Premium Plan'];
    $plan->save();
    

Subscription Workflows

  1. Multi-Subscription with Slugs:

    $user->subscribe($plan1, 'main');       // Primary subscription
    $user->subscribe($addonPlan, 'addon');  // Secondary subscription
    
  2. Renewal and Cancellation:

    $subscription = $user->subscriptions()->first();
    $subscription->renew();       // Auto-calculates next period
    $subscription->renew(force: true); // Force renewal from current end date
    $subscription->cancel();      // Soft cancellation (configurable)
    
  3. Feature Consumption:

    // Consume 50 units of 'storage' feature
    $user->consumeFeature('storage', 50);
    
    // Check remaining units
    $remaining = $user->getRemainingFeature('storage');
    

Billing Integration

  1. Listen for Subscription Events:

    // In EventServiceProvider
    protected $listen = [
        \Lacodix\LaravelPlans\Events\SubscriptionRenewed::class => [
            \App\Listeners\CreateInvoice::class,
        ],
    ];
    
  2. Calculate Period Price:

    $price = $subscription->calculatePeriodPrice(); // Handles partial periods
    
  3. Custom Price Calculation:

    $percentage = $subscription->calculatePeriodLengthInPercent();
    $customPrice = $subscription->plan->meta['price_per_user'] * $userCount * ($percentage / 100);
    

Sorting and Ordering

  1. Plan Ordering:

    $plan->moveToStart();  // Move to top of list
    $plan->moveToEnd();    // Move to bottom
    
  2. Subscription Ordering:

    $subscription->moveUp();
    $subscription->moveDown();
    

Integration Tips

  1. Use Meta Data for Flexibility: Store billing-specific data (e.g., currency, tax_rate) in the meta field of plans/subscriptions:

    $plan->meta = ['currency' => 'EUR', 'tax_rate' => 0.2];
    
  2. Combine with Billing Providers: Trigger Stripe/PayPal subscriptions on SubscriptionCreated events:

    public function handle(SubscriptionCreated $event) {
        \Stripe\Subscription::create([
            'customer' => $event->subscription->user->stripe_id,
            'items' => [['price' => $event->subscription->plan->stripe_price_id]],
        ]);
    }
    
  3. Feature-Based UI Logic: Dynamically show/hide UI elements based on feature availability:

    @if($user->hasFeature('advanced-analytics'))
        <button>Enable Analytics</button>
    @endif
    
  4. Grace Period Handling: Use hasGracePeriod() to extend access after cancellation:

    if ($subscription->hasGracePeriod()) {
        // Grant access until grace period ends
    }
    
  5. Testing Subscriptions: Use Subscription::fake() in tests to mock subscriptions:

    $user->subscribe($plan);
    Subscription::fake()->assertSubscribed($user, $plan);
    

Gotchas and Tips

Pitfalls

  1. Slug Conflicts:

    • Subscriptions use slugs to distinguish between multiple subscriptions (e.g., main, addon). Ensure slugs are unique per user.
    • Fix: Validate slugs before attaching subscriptions.
  2. Feature Reset Timing:

    • Countable features reset based on resettable_period and resettable_interval. Misconfiguration can lead to unexpected resets.
    • Fix: Test with php artisan plans:reset-features to verify reset logic.
  3. Partial Period Pricing:

    • calculatePeriodPrice() accounts for trial periods and partial months. Overriding this logic may break billing.
    • Fix: Use calculatePeriodLengthInPercent() for custom calculations.
  4. Translation Dependencies:

    • Plan/feature names rely on spatie/laravel-translatable. Missing translations may cause errors.
    • Fix: Set default locales in config or use fallback values:
      $plan->name = ['en' => 'Default Name'];
      
  5. Sortable Package Conflicts:

    • The package uses spatie/eloquent-sortable. Conflicts may arise with other sortable implementations.
    • Fix: Ensure no duplicate position columns exist in plans or subscriptions tables.
  6. Event Listener Order:

    • Subscription events (e.g., SubscriptionRenewed) may fire out of order if multiple listeners are registered.
    • Fix: Use priority in listeners or queue delayed jobs.
  7. Meta Data Serialization:

    • Meta data is stored as JSON. Large or complex data may cause serialization issues.
    • Fix: Limit meta data size or use a separate table for critical data.

Debugging Tips

  1. Log Subscription Events: Add a listener to log events for debugging:

    public function handle($event) {
        \Log::info('Subscription Event', ['event' => $event::class, 'data' => $event->subscription->toArray()]);
    }
    
  2. Check Feature Consumption: Use the plans:check-features artisan command to audit feature usage:

    php artisan plans:check-features --user=1
    
  3. Validate Plan/Feature Data: Ensure required fields are set (e.g., slug, billing_interval):

    $plan->validate([
        'slug' => 'required|unique:plans',
        'billing_interval' => 'required',
    ]);
    
  4. Test Renewal Logic: Manually trigger renewals in tests to verify period calculations:

    $subscription->forceRenew();
    $this->assertEquals($expectedPrice, $subscription->calculatePeriodPrice());
    
  5. Inspect Sort Order: Use dd($plan->subscriptions()->orderBy('position')->get()) to debug subscription ordering.


Extension Points

  1. Custom Feature Logic: Extend the Feature model to add domain-specific behavior:
    class CustomFeature extends Feature {
        public function isActive
    
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.
calmfox/watch-sylius
damienfern/grpc-symfony-bundle
atoolo/index-bundle
atoolo/genai-bundle
coprotoai/laravel-ticket
davidjln/llm-carbon-bundle
cryonighter/valid-request-bundle
coolms/taxonomy-bundle
coolms/field-bundle
articulate-orm/symfony
aaix/laravel-tall-architect
ephoto/akeneo-connector
emmanuelballery/eb-plantumlbundle
emielburgman/symfony-visitor-beacon
emielburgman/symfony-visit-storage
emielburgman/symfony-security-headers
emielburgman/symfony-log-viewer
emarref/xdebug-bundle
emarref/pubnub-bundle
elriseio/finance-money-bundle