Download the PHP package develupers/laravel-plan-usage without Composer
On this page you can find all versions of the php package develupers/laravel-plan-usage. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download develupers/laravel-plan-usage
More information about develupers/laravel-plan-usage
Files in develupers/laravel-plan-usage
Package laravel-plan-usage
Short Description A Laravel package for managing subscription plans, features, quotas, and usage tracking across multiple billing providers
License MIT
Homepage https://github.com/develupers/laravel-plan-usage
Informations about the package laravel-plan-usage
Laravel Plan Usage
A powerful Laravel package for managing subscription plans, features, quotas, and usage tracking. Perfect for SaaS applications that need flexible plan management with feature access control and usage monitoring. Supports Stripe, Paddle, and Polar billing integrations.
β¨ Features
- π Flexible Plan Management - Create and manage subscription tiers with customizable pricing
- π― Feature Access Control - Define boolean, limit, and quota-based features
- π Usage Tracking - Monitor and track feature consumption in real-time
- π¦ Quota Enforcement - Automatic quota limits with configurable warning thresholds
- π³ Multi-Provider Billing - Support for Stripe, Paddle, and Polar billing providers
- π MoR Support - Use Paddle or Polar as Merchant of Record for simplified tax/VAT handling
- π± Multi-Currency Support - Support for different currencies per pricing option
- π Flexible Pricing Intervals - Daily, weekly, monthly, yearly, or lifetime pricing
- π Periodic Reset Options - Daily, weekly, monthly, or yearly quota resets
- πͺ Event-Driven Architecture - React to usage events and quota warnings
- π‘οΈ Middleware Protection - Route-level feature and quota enforcement
- π·οΈ Plan Types - Support for public, legacy, and private plans
- βΎοΈ Lifetime Plans - Mark plans as lifetime to exempt them from subscription enforcement
- β° Quota Reset Scheduling - Built-in command and job for resetting expired quotas
- π Usage Analytics - Built-in statistics and reporting capabilities
π Requirements
- PHP 8.3+
- Laravel 11.x, 12.x, or 13.x
- One of the following billing packages:
- Laravel Cashier 15.x or 16.x (for Stripe; installed automatically)
- Laravel Cashier Paddle 2.8+ (for Paddle Billing; install separately)
- Laravel Polar 2.13+ within 2.x (for Polar; install separately)
π Installation
Install the package from Packagist by running this command in your Laravel application's root directory:
Laravel automatically discovers the service provider and facade aliases. No custom Composer repository or manual provider registration is required.
Select Your Billing Provider
Stripe's laravel/cashier package is installed as a dependency. For Paddle or Polar, also install the matching integration:
Set your chosen provider in .env before publishing migrations, because the package publishes different migrations for each provider:
Use paddle or polar instead when appropriate. Clear any cached configuration:
Publish and Configure
For a fresh installation, publish the configuration and the selected provider's migrations:
This writes config/plan-usage.php and migration files in database/migrations; it does not run the migrations. The installer overwrites the published configuration, so use the individual vendor:publish commands when updating an existing installation:
Before migrating, configure models.billable and tables.billable in config/plan-usage.php for your billable model and table. Add the package's HasPlanFeatures trait and your provider's Billable trait to that model, and complete the provider's database and webhook setup. See the Installation Guide for model examples and provider configuration.
Then create the tables:
βοΈ Configuration
After installation, configure the package in config/plan-usage.php:
Default Plan for New Billables
When default_plan_id is configured, the package automatically assigns this plan to new billable models when they're created. This is useful for:
- Free tier assignment: New users automatically get the free plan
- Subscription cancellation fallback: When a paid subscription is cancelled, users fall back to the default plan instead of having no plan
With this configured, you don't need to manually assign plans when creating billables:
π³ Billing Provider Setup
This package supports Stripe, Paddle, and Polar as billing providers. You can only use one provider at a time.
Option 1: Stripe (Default)
Install Laravel Cashier for Stripe:
Add to your .env:
Option 2: Paddle (Merchant of Record)
Paddle acts as Merchant of Record, handling all tax/VAT compliance for you. This is ideal if you want to avoid dealing with tax regulations across different countries.
Install Laravel Cashier for Paddle:
Add to your .env:
Option 3: Polar (Merchant of Record)
Install Laravel Polar and run its installer:
Add to your .env:
Each PlanPrice maps to a distinct Polar product through
polar_product_id. Monthly and yearly subscriptions are separate Polar
products even when they grant the same application plan.
Lifetime plans are fully supported on Polar: a PlanPrice with the
lifetime interval is pushed as a one-time Polar product, the plan and
quotas are assigned when the order.paid webhook arrives (no subscription is
created), and a fully refunded order revokes them again. Mark the plan
is_lifetime = true so subscription enforcement never touches its holders.
Auto-Detection
If BILLING_PROVIDER is set to auto (or not set), the package will automatically detect which billing package is installed and use the appropriate provider. Detection priority: Paddle > Polar > Stripe.
Automatic Migration Selection
The package automatically publishes the correct migration for your billable table based on the detected billing provider:
- Stripe: Adds
stripe_id,pm_type,pm_last_four,trial_ends_atcolumns - Paddle: Adds
paddle_id,trial_ends_at,billing_emailcolumns - Polar: Uses Polar's polymorphic customer/subscription tables and adds only the plan tracking columns
All migrations also add plan tracking columns: plan_id, plan_price_id, plan_changed_at.
The plan_prices table likewise only receives the selected provider's price/product identifier column (stripe_price_id, paddle_price_id, or polar_product_id). The managed subscription-change table (subscription_plan_changes) is published for providers that support managed plan changes (Stripe, Paddle, and Polar); the durable webhook-event table (billing_webhook_events) is Polar-only.
Switching billing providers? Migrations are selected from the provider detected at publish time, so after installing the new provider package and updating
BILLING_PROVIDER, re-runphp artisan vendor:publish --tag="plan-usage-migrations"andphp artisan migrate. Only the new provider's migrations are added (already-published ones are skipped), and every provider-specific migration is guarded withhasColumn/hasTablechecks so re-running is safe. Skipping this step leaves the new provider's column/tables missing β e.g. Polar webhooks will fail without thebilling_webhook_eventstable.The Paddle stub also adds a
billing_emailoverride (falls back to the owner/user email). Paddle allows only one customer per email, so each billable needs its own billing identity to subscribe independently;PaddleProvider::updateCustomerEmail()pushes changes to Paddle.Important: You still need to publish and run the billing provider's own migrations separately. The package only adds the billable columns to your model's table.
π Quick Start
1. Add Traits to Your Billable Model
For Stripe:
For Paddle:
For Polar:
2. Create Plans and Features
3. Assign Plan to Billable
4. Use Features in Your Application
There are two approaches -- pick whichever fits your use case:
Middleware approach (automatic, on routes):
Manual approach (in your code):
Other useful methods:
π Understanding Feature Checks
The package provides two main methods for checking features:
| Method | Purpose | Mutates quota? |
|---|---|---|
hasFeature('api-calls') |
Check if feature is included in the plan | No |
checkQuota('api-calls', 10) |
Check if quota is available for amount | No |
consume('api-calls', 10) |
Enforce + increment + log (the full operation) | Yes |
logUsage('api-calls', 10) |
Log usage only (no enforcement) | No |
Key difference: hasFeature() checks plan inclusion, checkQuota() checks current quota availability, and consume() actually uses the quota.
π·οΈ Plan Types
Plans can be categorized by visibility and distribution type:
| Type | Description | Purchasable | Visible to |
|---|---|---|---|
| public | Current plans on pricing page | Yes, self-service checkout | Everyone |
| private | Gated plans requiring access code, invite, or membership | Yes, with access | Invited/authorized users |
| legacy | Discontinued plans, grandfathered for existing subscribers | No, existing subscribers only | Current holders only |
| hidden | Internal/admin-only plans (lifetime deals, staff plans) | No, manually assigned | Admin only |
βΎοΈ Lifetime Plans
Lifetime plans are plans that don't require an active billing subscription. They're ideal for one-time purchase deals, promotional offers, or manually assigned plans that should never be revoked by subscription enforcement.
Creating a Lifetime Plan
Querying Lifetime Plans
How Lifetime Plans Work
| Aspect | Regular Plan | Lifetime Plan |
|---|---|---|
| Requires subscription | Yes | No |
| Quotas reset periodically | Yes | Yes |
| Subject to plan enforcement | Yes | No |
| Shown on pricing page | If type = public |
Typically type = hidden |
| Assigned via | Stripe/Paddle/Polar checkout | Admin panel, seeder, or manual assignment |
Lifetime plans still benefit from quota resets β a lifetime plan with 8,000 monthly credits will reset to 0 used credits each month. The only difference is that the plan is never removed due to a missing subscription.
Lifetime Purchases on Polar
Polar is the only provider with true one-time purchases: a LIFETIME-interval PlanPrice maps to a one-time Polar product. The order.paid webhook assigns the plan and quotas (orders belonging to a subscription are ignored), and a fully refunded order is the only thing that revokes a lifetime plan β subscription lifecycle events, reconciliation, and enforcement all skip lifetime holders, so a historical (cancelled) subscription can never wipe a lifetime plan bought afterwards.
Checkout blocks buying the same lifetime plan price twice (one-time purchases create no subscription row, so the usual subscribed() guard cannot catch a repeat purchase). Known limitation: purchases made outside the package's checkout (e.g. Polar dashboard payment links) are not deduplicated β sell lifetime products through CreateCheckoutSessionAction to keep refund semantics safe.
Migration
If upgrading from a previous version, publish and run the migration:
This adds the is_lifetime boolean column (default: false) to the plans table.
π° Pricing Structure
Plans support multiple pricing options with different intervals and currencies:
Creating Multiple Prices
Working with Prices
Supported Intervals
| Interval | Description |
|---|---|
day |
Daily billing |
week |
Weekly billing |
month |
Monthly billing |
year |
Annual billing |
lifetime |
One-time payment |
π― Feature Types
The package supports three types of features:
| Type | Description | Example |
|---|---|---|
| Boolean | On/off features | Advanced Analytics, Priority Support |
| Limit | Maximum allowed quantity | Max Projects, Team Members |
| Quota | Usage-based with periodic reset | API Calls, Storage, Bandwidth |
π‘οΈ Middleware
Protect your routes with built-in middleware:
π Usage Tracking & Analytics
Feature Usage Details
β οΈ getFeatureUsage() can return null β always guard for it
getFeatureUsage() returns ?array. It gives back null whenever there is
nothing to meter, which is deliberately different from a feature that is
metered but exhausted (['limit' => 0, ...]). Treat null as "not applicable"
rather than rendering it as a maxed-out progress bar.
| Scenario | Return value |
|---|---|
| Feature not found, or boolean type | null |
| Billable has no plan | null |
| Plan doesn't include the feature | null |
| Granted, unlimited | ['limit' => null, 'used' => N, 'remaining' => null] |
| Granted with a limit | ['limit' => 5000, 'used' => 1250, 'remaining' => 3750] |
Track Usage
Quota Management
πͺ Events
The package dispatches events you can listen to:
UsageRecorded- When usage is recordedQuotaWarning- When usage reaches warning threshold (80%, 100%)QuotaExceeded- When quota limit is exceededPlanRevoked- When a plan is revoked due to no active subscription (see Enforcing Plan Subscriptions)SubscriptionPlanChangeScheduled- When a future plan change is accepted by the providerSubscriptionPlanChanged- When a confirmed plan change is applied locallySubscriptionPlanChangeCancelled- When a pending plan change is cancelled
Event Deduplication (trigger_once)
By default, QuotaWarning and QuotaExceeded events fire on every consume() call where usage exceeds a threshold. This can result in duplicate notifications (e.g., an email on every API request after 80% usage).
Set trigger_once to true to fire each event only once per billing period:
When enabled, the package uses a cache key scoped to the billable, feature, and threshold. The key expires at the quota's reset_at timestamp (or 24 hours if no reset period is configured). Each threshold fires independently β crossing 80% sends one event, and later crossing 100% sends another.
This is handled at the package level, so your listeners don't need any deduplication logic.
π³ Billing Provider Integration
The package integrates with Cashier Stripe, Cashier Paddle, and Laravel Polar through one billing provider contract.
Design boundary: the provider libraries own the money β payments, invoices, taxes, payment methods, and the billing clock. This package owns the entitlements β what a customer may use inside your application, and how much of it is left. It only reaches into a provider where entitlement correctness requires owning the call path (plan changes, cancellation), and it never replicates provider machinery it can consume instead.
Webhook Behavior & Entitlement Policy
Providers do not guarantee webhook delivery order, so payloads are treated only as triggers: listeners re-fetch the subscription's current state from the provider API inside a per-billable lock and converge the local plan to that authoritative response β out-of-order deliveries are harmless. Failed deliveries return a non-2xx response (and release their dedupe key) so the provider redelivers.
One shared status policy governs listeners, subscriptions:reconcile, and subscription enforcement:
| Remote status | Outcome |
|---|---|
active, trialing |
Plan granted / synced |
past_due |
Kept by default; set plan-usage.{stripe,paddle,polar}.past_due_keeps_entitlements to false to revoke |
canceled (Polar) |
Grace period β kept until the effective end passes |
Everything else (incomplete, unpaid, paused, canceled, unknown) |
Plan revoked |
Revocation is fail-closed (the plan-clear commits before quota cleanup, so a cleanup failure still denies access) and idempotent (a billable already on the configured subscription.default_plan_id is never re-processed, preserving free-tier usage).
Provider-Agnostic Methods
Managed Plan Changes
Use the managed plan-change API instead of calling the provider's subscription model directly. This keeps the remote subscription, local plan, quotas, and pending-change audit record consistent.
Provider support:
| Timing | Stripe | Paddle | Polar |
|---|---|---|---|
Immediate (swap + prorate now) |
β | β | β |
NextPeriod (scheduled change at renewal) |
β | β | β |
Only the configured default-type subscription (subscription.default_name, 'default' by default) controls the billable's plan: changePlan() rejects other subscription names, webhook events for add-on subscriptions are ignored, and immediate cancellation only revokes the plan when the default subscription itself was cancelled.
A timing is exposed only where the provider supports it natively β the package never emulates provider-side scheduling with local timers, because the provider owns the billing clock and only it can bill the new price correctly at renewal. (Stripe could gain NextPeriod later via its native Subscription Schedules; Paddle has no native equivalent.) Feature-detect instead of catching exceptions:
The package applies these entitlement rules:
- Immediate quota upgrades add only the prorated difference for the remaining entitlement period.
- Scheduled changes do not alter current entitlements early.
- When a scheduled change becomes effective, quota usage resets and the target allowance is applied.
- Period-end cancellation keeps access through Polar's paid grace period; revocation removes the plan and quotas.
- Webhook deliveries are durably deduplicated and ordered per subscription.
subscriptions:reconcilerepairs state after missed webhooks.
For asset limits such as active projects, this package updates the numeric plan limit but does not delete or lock application records. Your application should enforce the new limit after the scheduled change is appliedβfor example, by asking the user which projects remain active and making excess projects read-only.
Syncing Plans to Billing Provider
Use the unified plans:push command to sync your local plans to the billing provider:
Resetting Expired Quotas
Quotas with a reset period (daily, weekly, monthly, yearly) need to be periodically reset. The package provides a command and a queued job for this.
Command:
Schedule it in your routes/console.php:
Dispatch the job directly from your code:
The job finds all quotas where reset_at has passed and used > 0, resets the usage to 0, and sets the next reset_at date based on the feature's reset period.
Enforcing Plan Subscriptions
Plan enforcement ensures that billables with a paid plan but no active subscription get their plan revoked. Lifetime plans are automatically exempt.
Command:
Schedule it in your routes/console.php:
What it does:
- Finds all billables with a
plan_idwhere the plan is NOT lifetime (is_lifetime = false) - Checks if they have an active subscription or are on a grace period
- If not, clears
plan_id(or sets todefault_plan_idfrom config) - Dispatches a
PlanRevokedevent for each revoked plan
Listen for revocations:
The PlanRevoked event contains:
$event->billableβ the account/user that lost their plan$event->previousPlanβ the plan that was removed$event->reasonβ why it was revoked (e.g.no_active_subscription)
Reconciling Subscriptions
If webhooks are missed, you can reconcile local subscription status with the billing provider. Every billable with subscription rows is checked against the provider's current state: expired subscriptions are revoked, active subscriptions have plan/price drift corrected, and a billable whose initial checkout webhook was lost gets its plan recovered. Billables with a plan but no subscription rows (lifetime purchases, manually granted plans) are never touched. Only the configured default-type subscription controls the plan β custom-typed subscriptions are deliberately ignored.
Stripe-Specific
Paddle-Specific
Polar-Specific
Polar sells products rather than a separate product/price pair. Each local PlanPrice therefore maps to one Polar product:
Use plans:push --provider=polar to create those products automatically. Laravel Polar owns customer, checkout, portal, order, subscription, and webhook transport; this package owns the mapping from the confirmed Polar subscription to plans, features, quotas, and usage.
β° Recommended Schedule
Add both commands to your routes/console.php to keep quotas and plans in sync:
π Plan Comparison
Compare plans to show upgrade benefits:
π§ͺ Testing
Run the test suite:
Run tests with coverage:
Format code:
Static analysis:
π Documentation
For detailed documentation, see:
- Installation Guide - Complete setup instructions
- User Guide - Comprehensive usage documentation
- Quick Reference - Common operations cheatsheet
π€ Contributing
Please see CONTRIBUTING for details on how to contribute to this project.
Development Setup
- Clone the repository
- Install dependencies:
composer install - Run tests:
composer test - Check code style:
composer format
π Security
If you discover any security-related issues, please email [email protected] instead of using the issue tracker.
π Changelog
Please see CHANGELOG for more information on what has changed recently.
π₯ Credits
- Omar Robinson
- All Contributors
π License
The MIT License (MIT). Please see License File for more information.
πͺ Support
Need help or have questions? Feel free to:
- π Check the documentation
- π Report bugs
- π‘ Request features
- β Star the repository if you find it useful!
Built with β€οΈ by Develupers
All versions of laravel-plan-usage with dependencies
spatie/laravel-package-tools Version ^1.18
illuminate/contracts Version ^11.0||^12.0||^13.0
illuminate/support Version ^11.0||^12.0||^13.0
illuminate/database Version ^11.0||^12.0||^13.0
laravel/cashier Version ^15.0||^16.0