PHP code example of develupers / laravel-plan-usage
1. Go to this page and download the library: Download develupers/laravel-plan-usage 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/ */
develupers / laravel-plan-usage example snippets
return [
'tables' => [
'billable' => 'accounts', // Your billable model's table
'plans' => 'plans',
'plan_prices' => 'plan_prices',
'features' => 'features',
'plan_features' => 'plan_features',
'usages' => 'usages',
'quotas' => 'quotas',
'subscription_plan_changes' => 'subscription_plan_changes',
'billing_webhook_events' => 'billing_webhook_events',
],
'cache' => [
'enabled' => true,
'store' => 'redis',
'ttl' => 3600,
],
'quota' => [
'throw_exception' => true,
'soft_limit' => false,
'grace_percentage' => 0,
'warning_thresholds' => [80, 100],
'trigger_once' => false, // Only fire each threshold event once per billing period
],
// Billing provider configuration
'billing' => [
// 'auto' = detect from installed package
// 'stripe' = force Stripe provider
// 'paddle' = force Paddle provider
// 'polar' = force Polar provider
'provider' => env('BILLING_PROVIDER', 'auto'),
],
// Subscription settings
'subscription' => [
// Default plan ID for new billables and cancelled subscriptions.
// Set this to your free plan ID so users always have a plan.
'default_plan_id' => env('DEFAULT_PLAN_ID', null),
],
// Paddle-specific configuration (only needed if using Paddle)
'paddle' => [
'sandbox' => env('PADDLE_SANDBOX', true),
'seller_id' => env('PADDLE_SELLER_ID'),
'api_key' => env('PADDLE_API_KEY'),
'webhook_secret' => env('PADDLE_WEBHOOK_SECRET'),
'client_side_token' => env('PADDLE_CLIENT_SIDE_TOKEN'),
],
// Polar-specific configuration (only needed if using Polar)
// Webhook signature verification is handled by danestves/laravel-polar via
// POLAR_WEBHOOK_SECRET in its own config, so there is no key for it here.
'polar' => [
'access_token' => env('POLAR_ACCESS_TOKEN'),
'organization_id' => env('POLAR_ORGANIZATION_ID'),
'server' => env('POLAR_SERVER', 'sandbox'),
],
];
// In config/plan-usage.php
'subscription' => [
'default_plan_id' => 1, // Your Free plan ID
],
// Or via .env
DEFAULT_PLAN_ID=1
// The free plan is automatically assigned!
$account = Account::create([
'name' => 'New Account',
'owner_id' => $user->id,
]);
echo $account->plan->name; // "Free"
use Develupers\PlanUsage\Traits\HasPlanFeatures;
use Laravel\Cashier\Billable;
class Account extends Model
{
use Billable, HasPlanFeatures;
}
use Develupers\PlanUsage\Traits\HasPlanFeatures;
use Laravel\Paddle\Billable;
class Account extends Model
{
use Billable, HasPlanFeatures;
}
use Danestves\LaravelPolar\Billable;
use Develupers\PlanUsage\Traits\HasPlanFeatures;
class Account extends Model
{
use Billable, HasPlanFeatures;
}
use Develupers\PlanUsage\Models\Plan;
use Develupers\PlanUsage\Models\PlanPrice;
use Develupers\PlanUsage\Models\Feature;
use Develupers\PlanUsage\Models\PlanFeature;
// Create a plan
$plan = Plan::create([
'name' => 'Professional',
'slug' => 'professional',
'display_name' => 'Professional Plan',
'description' => 'Perfect for growing businesses',
'stripe_product_id' => 'prod_123456', // Stripe Product ID (if using Stripe)
'paddle_product_id' => 'pro_01abc123', // Paddle Product ID (if using Paddle)
'trial_days' => 14,
'type' => 'public',
]);
// Create pricing options for the plan
$monthlyPrice = PlanPrice::create([
'plan_id' => $plan->id,
'stripe_price_id' => 'price_monthly123', // Stripe Price ID
'paddle_price_id' => 'pri_01xyz789', // Paddle Price ID
'polar_product_id' => 'prod_polar_monthly123', // Polar Product ID
'price' => 49.00,
'currency' => 'USD',
'interval' => 'month',
'is_default' => true, // Default pricing option
'is_active' => true,
]);
$yearlyPrice = PlanPrice::create([
'plan_id' => $plan->id,
'stripe_price_id' => 'price_yearly456',
'paddle_price_id' => 'pri_01abc456',
'polar_product_id' => 'prod_polar_yearly456',
'price' => 490.00, // Discounted yearly price
'currency' => 'USD',
'interval' => 'year',
'is_active' => true,
]);
// Create features
$apiFeature = Feature::create([
'name' => 'API Calls',
'slug' => 'api-calls',
'type' => 'quota',
'reset_period' => 'month',
]);
// Link feature to plan with a value
PlanFeature::create([
'plan_id' => $plan->id,
'feature_id' => $apiFeature->id,
'value' => '5000', // 5000 API calls per month
]);
// CheckQuota gates the request, ConsumeQuota enforces + logs on success
Route::middleware(['check-quota:api-calls,1', 'consume-quota:api-calls,1'])
->post('/api/generate', 'ApiController@generate');
// consume() does everything: checks quota, increments, and logs usage
if ($account->consume('api-calls', 1, ['endpoint' => '/api/generate'])) {
// Success -- quota was available
} else {
// Quota exceeded
}
// Check if feature is in the plan
$account->hasFeature('api-calls');
// Read-only quota check (no side effects)
$account->checkQuota('api-calls', 10);
// Log usage without quota enforcement
$account->logUsage('api-calls', 1, ['source' => 'import']);
// Check remaining quota
$remaining = $account->getRemainingQuota('api-calls');
// Get detailed usage information
$usage = $account->getFeatureUsage('api-calls');
// Returns: ['limit' => 5000, 'used' => 1250, 'remaining' => 3750]
// Returns null when there's nothing to meter — see "getFeatureUsage() can
// return null" under Usage Tracking & Analytics.
// Only show public plans on pricing page
$availablePlans = Plan::availableForPurchase()->get(); // active + public
// Get legacy plans for existing customers
$legacyPlans = Plan::legacy()->get();
// Get hidden plans (admin use only)
$hiddenPlans = Plan::hidden()->get();
// Check plan type
if ($plan->isAvailableForPurchase()) {
// Show "Subscribe" button
}
if ($plan->isHidden()) {
// Only show in admin panel
}
// Plan type lifecycle: public → legacy (when retired)
// Private plans are for gated access (access codes, invitations)
// Hidden plans are never exposed to users (lifetime deals, internal use)
$plan = Plan::create([
'name' => 'Growth Lifetime',
'slug' => 'growth-lifetime',
'display_name' => 'Growth Lifetime',
'description' => 'Growth plan with lifetime access',
'type' => 'hidden', // Not shown on public pricing pages
'is_lifetime' => true, // Exempt from subscription enforcement
'is_active' => true,
]);
// Get all lifetime plans
$lifetimePlans = Plan::lifetime()->get();
// Get plans that is lifetime
if ($plan->isLifetime()) {
// Skip subscription enforcement
}
// Get default price
$defaultPrice = $plan->defaultPrice;
// Get price by interval
$monthlyPrice = $plan->getMonthlyPrice();
$yearlyPrice = $plan->getYearlyPrice();
$customPrice = $plan->getPriceByInterval('week');
// Get all active prices
$activePrices = $plan->activePrices;
// Find plan by any provider price ID (auto-detects current provider)
$plan = Plan::findByProviderPriceId('price_monthly123');
// Calculate savings
$yearlyPrice = $plan->getYearlyPrice();
$monthlyPrice = $plan->getMonthlyPrice();
$savings = $yearlyPrice->calculateSavings($monthlyPrice);
echo "Save {$savings}% with yearly billing!";
// In bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'check-feature' => \Develupers\PlanUsage\Http\Middleware\CheckFeature::class,
'check-quota' => \Develupers\PlanUsage\Http\Middleware\CheckQuota::class,
'consume-quota' => \Develupers\PlanUsage\Http\Middleware\ConsumeQuota::class,
]);
})
// In routes/web.php
Route::get('/analytics', function () {
// Only accessible if user has 'advanced-analytics' feature
})->middleware('check-feature:advanced-analytics');
Route::post('/api/generate', function () {
// Enforces quota, increments, and logs usage on success
})->middleware('consume-quota:api-calls,1');
// Gate + consume (check first, consume after success)
Route::middleware(['check-quota:api-calls,5', 'consume-quota:api-calls,5'])
->post('/api/bulk', 'ApiController@bulk');
// Get comprehensive usage details for a feature
$usage = $account->getFeatureUsage('api-calls');
// Returns: ['limit' => 5000, 'used' => 1250, 'remaining' => 3750]
// Get usage percentage
$percentage = $account->getFeatureUsagePercentage('api-calls');
echo "You've used {$percentage}% of your API calls";
// Get all features status
$featuresStatus = $account->getFeaturesStatus();
foreach ($featuresStatus as $status) {
echo "{$status['name']}: {$status['used']}/{$status['limit']}\n";
}
$usage = $account->getFeatureUsage('api-calls');
if ($usage === null) {
// One of:
// - the feature slug doesn't exist, or it's a boolean feature
// (boolean features have no usage to report)
// - the billable has no plan, or its plan doesn't ing']} left";
}
use Develupers\PlanUsage\Facades\PlanUsage;
// Consume: enforce quota + increment + log (returns false if exceeded)
PlanUsage::consume($account, 'api-calls', 10, [
'endpoint' => '/api/generate',
'ip' => $request->ip(),
]);
// Get usage history
$history = PlanUsage::usage()->getHistory($account, 'api-calls');
// Get usage statistics
$stats = PlanUsage::usage()->getStatistics(
$account,
'api-calls',
now()->subMonth(),
now(),
'day'
);
// Get current usage
$used = PlanUsage::quotas()->getUsed($account, 'api-calls');
// Get remaining quota
$remaining = PlanUsage::quotas()->getRemaining($account, 'api-calls');
// Get usage percentage
$percentage = PlanUsage::quotas()->getUsagePercentage($account, 'api-calls');
// Enforce quota (returns false if would exceed)
$canProceed = PlanUsage::quotas()->enforce($account, 'api-calls', 10);
// In your EventServiceProvider or via Laravel auto-discovery
protected $listen = [
\Develupers\PlanUsage\Events\QuotaWarning::class => [
\App\Listeners\SendQuotaWarningNotification::class,
],
\Develupers\PlanUsage\Events\QuotaExceeded::class => [
\App\Listeners\HandleQuotaExceeded::class,
],
\Develupers\PlanUsage\Events\PlanRevoked::class => [
\App\Listeners\NotifyPlanRevoked::class,
],
];
// Get the current provider's price ID for a plan price
$priceId = $planPrice->getProviderPriceId();
// Find a plan by its provider's price/product ID
$plan = Plan::findByProviderPriceId($priceId);
// Get/set the provider's product ID
$productId = $plan->getProviderProductId();
$plan->setProviderProductId('prod_ABC123');
// Create a provider checkout for a plan price
$checkout = app(\Develupers\PlanUsage\Actions\Subscription\CreateCheckoutSessionAction::class)
->executeForPlanPrice($billable, $planPrice, [
'success_url' => route('billing.success'),
'cancel_url' => route('billing.index'),
]);
return $checkout->redirect();
use Develupers\PlanUsage\Contracts\BillingProvider;
use Develupers\PlanUsage\Contracts\SubscriptionPlanChangeProvider;
use Develupers\PlanUsage\Enums\SubscriptionChangeTiming;
$provider = app(BillingProvider::class);
if ($provider instanceof SubscriptionPlanChangeProvider
&& $provider->supportsTiming(SubscriptionChangeTiming::NextPeriod)) {
// Offer "downgrade at renewal" in the UI
}
use Develupers\PlanUsage\Actions\Subscription\CancelSubscriptionAction;
use Develupers\PlanUsage\Enums\SubscriptionChangeTiming;
// Upgrade now. The provider invoices the prorated price difference immediately,
// then the package grants the prorated quota difference after confirmation.
$change = $account->changePlan($growthMonthlyPrice);
// Downgrade on renewal (Polar only). Current plan limits and quotas remain
// unchanged until the provider reports that the pending product is now current.
$change = $account->changePlan($starterMonthlyPrice, SubscriptionChangeTiming::NextPeriod);
// Inspect or remove a scheduled downgrade before it takes effect.
$pending = $account->pendingPlanChange();
$account->cancelPendingPlanChange();
// The same operations are available through the facade:
// PlanUsage::changePlan($account, $growthMonthlyPrice);
// PlanUsage::cancelPendingPlanChange($account);
// Cancel at period end, resume during the grace period, or revoke now.
app(CancelSubscriptionAction::class)->execute($account);
app(CancelSubscriptionAction::class)->resume($account);
app(CancelSubscriptionAction::class)->execute($account, immediately: true);
use Illuminate\Support\Facades\Schedule;
// Reset expired quotas every hour
Schedule::command('plan-usage:reset-quotas --dispatch')->hourly();
use Develupers\PlanUsage\Jobs\ResetExpiredQuotasJob;
// Queued
ResetExpiredQuotasJob::dispatch();
// Synchronous
ResetExpiredQuotasJob::dispatchSync();
use Illuminate\Support\Facades\Schedule;
// Enforce plan subscriptions daily
Schedule::command('plan-usage:enforce-subscriptions --dispatch')->daily();
use Develupers\PlanUsage\Events\PlanRevoked;
// In your EventServiceProvider or via Laravel auto-discovery
protected $listen = [
PlanRevoked::class => [
\App\Listeners\NotifyPlanRevoked::class,
],
];
// Plans connect to Stripe Products
$plan->stripe_product_id = 'prod_ABC123';
// Each price connects to Stripe Prices
$price->stripe_price_id = 'price_XYZ789';
// Plans connect to Paddle Products
$plan->paddle_product_id = 'pro_01ABC123';
// Each price connects to Paddle Prices
$price->paddle_price_id = 'pri_01XYZ789';