Weave 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

Plans Laravel Package

interactively/plans

A Laravel package for creating and managing subscription plans and features. Define pricing tiers, billing intervals, and feature limits, then attach plans to models to power SaaS-style subscriptions and access control.

View on GitHub
Deep Wiki
Context7

Getting Started

interactively/plans is a Laravel subscription/entitlement package: you define plans, attach features and limits, subscribe users (or other billable models), check feature access, and generate invoices. Treat it as the catalog and entitlement layer; payment collection usually stays with Stripe/Cashier/Paddle or your own billing flow.

Minimal setup:

  1. Install and publish assets:

    composer require interactively/plans
    php artisan vendor:publish --provider="Interactively\Plans\PlansServiceProvider"
    php artisan migrate
    

    Publish both config and migrations; check the published config/plans.php for table names and model bindings before migrating.

  2. Add the billable trait to your user model:

    use Interactively\Plans\Traits\HasPlans;
    
    class User extends Authenticatable
    {
        use HasPlans;
    }
    
  3. Seed a first plan and feature:

    use Interactively\Plans\Models\Plan;
    use Interactively\Plans\Models\Feature;
    
    $pro = Plan::create([
        'name' => 'Pro',
        'slug' => 'pro',
        'price' => 1900,
        'interval' => 'month',
    ]);
    
    $apiCalls = Feature::create([
        'name' => 'API calls',
        'slug' => 'api-calls',
        'type' => 'limit', // limit / feature / consumable, depending on version
    ]);
    
    $pro->features()->attach($apiCalls, ['value' => 10000]);
    
  4. First use case — subscribe and gate a feature:

    $user->subscribeTo($pro);
    
    if ($user->subscription()->canUseFeature('api-calls')) {
        // allow access
    }
    

Where to look first: the published config, the HasPlans trait, the Plan/Feature/Subscription models, and the migrations. Those define the real API surface faster than the README.

Implementation Patterns

Model plans as a catalog, not as business logic. Keep plan definitions in seeders or an admin CRUD, and keep feature slugs stable. Renaming a slug breaks every canUseFeature() call in the codebase.

Use feature types deliberately. Typical patterns:

  • feature / boolean: on/off entitlements such as “SSO” or “priority support”.
  • limit: numeric caps such as seats or projects.
  • consumable: metered usage that resets each billing period.

Store the per-plan value on the pivot (plan_feature.value) rather than hardcoding limits in plan columns.

Centralize entitlement checks. Wrap the package calls in a policy, middleware, or dedicated service so controllers do not scatter canUseFeature() everywhere:

class EnsurePlanFeature
{
    public function handle($request, Closure $next, string $feature)
    {
        abort_unless(
            $request->user()->subscription()?->canUseFeature($feature),
            403
        );

        return $next($request);
    }
}

Meter consumption inside transactions. For consumable features, record usage in a DB transaction and re-read the subscription state before writing:

DB::transaction(function () use ($user) {
    $subscription = $user->subscription()->lockForUpdate();

    abort_unless($subscription->canUseFeature('api-calls'), 429);

    $subscription->recordFeatureUsage('api-calls', 1);
});

This avoids the classic read-check-write race where two requests both see one unit remaining.

Handle plan changes as explicit workflows. Upgrades, downgrades, and cancellations usually need more than swapping a foreign key: decide whether usage resets, whether the period end is preserved, and whether invoices are prorated. Put that behind a ChangePlan action class so the policy is testable.

Use events for side effects. Listen for subscription created/renewed/canceled/expired events to provision resources, send receipts, or revoke access. Do not put mail sending or external API calls directly in controllers around subscribeTo().

Generate invoices from a listener, not from HTTP requests. On renewal or subscription creation, create the invoice in a queued listener, assign a sequential number, persist line items, and store the PDF on a private disk. Send the invoice notification separately so a mail failure does not roll back billing state.

Integrate with a payment provider at the edges. Keep provider IDs on your subscription/invoice records if the package exposes metadata or a JSON column. Webhooks should be idempotent: look up by provider event ID or subscription ID before applying changes.

Test with time control and factories. Use Carbon::setTestNow() to test period boundaries, renewals, and expirations. Create plans/features in test setup rather than relying on seeded production data.

Gotchas and Tips

  • Fork due diligence matters. With 4 stars and 0 dependents, this fork has a small ecosystem. Diff it against the upstream package before adopting it, check whether the invoice functionality is fork-specific, and pin a commit or exact version in composer.json. Review the issue tracker and recent commits.
  • Do not edit vendor files. Publish config and migrations, then extend models via the config’s model bindings. If you need custom behavior, subclass the package model and point the config at your class.
  • Check migration order and table prefixes. Forks often add invoice tables. Publish migrations before running migrate, and confirm the table names in config match what your code queries.
  • Define what “unlimited” means. A missing pivot row, a null value, and 0 can mean different things across versions. Normalize this in one helper so “not included” does not silently become “unlimited”.
  • Consumable usage is the main correctness risk. Always use a transaction and row lock for metered features. Never rely on an in-memory model instance that was loaded before another request consumed the last unit.
  • Period boundaries and timezones. Subscription renewals depend on the app timezone and the plan interval. Test month-end and DST transitions, and make renewal jobs idempotent so a retried queued job does not double-charge or double-reset usage.
  • Invoices are not an accounting system. Sequential numbering, tax, currency rounding, credit notes, and legal retention are your responsibility. If you need VAT/GST compliance, integrate a dedicated invoicing service rather than extending this package indefinitely.
  • Protect active plans from deletion. Prevent hard-deleting a plan that has active subscriptions, or use soft deletes and filter them out of the catalog. Foreign key constraints will otherwise surprise you in production.
  • Cache entitlement checks carefully. Caching canUseFeature() is tempting for hot paths, but invalidate on usage consumption, plan change, cancellation, and renewal. A stale “allowed” result after downgrade is a business risk.
  • Debug with tinker first. Inspect $user->subscription(), its features, and the pivot value directly. Most “feature not working” reports are a missing pivot row, a slug typo, or an expired subscription rather than a package bug.
  • Watch for abandoned-fork risk. Because the fork has almost no dependents, keep an upgrade path in mind: isolate package calls behind your own service/policy layer so swapping the entitlement implementation later is a contained change rather than a rewrite.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource