Download the PHP package revoltify/subscriptionify without Composer
On this page you can find all versions of the php package revoltify/subscriptionify. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download revoltify/subscriptionify
More information about revoltify/subscriptionify
Files in revoltify/subscriptionify
Package subscriptionify
Short Description Feature-based subscription management for Laravel
License MIT
Homepage https://github.com/revoltify/subscriptionify
Informations about the package subscriptionify
Subscriptionify
Feature-based subscription management for Laravel. Gateway-agnostic plans, features, usage tracking, and optional overage billing.
Table of Contents
- Requirements
- Installation
- Quick Start
- Plans
- Features
- Subscriptions
- Feature Usage
- Direct Feature Grants
- Metered Billing & Overage
- DTOs
- Middleware
- Blade Directives
- Query Scopes
- Scheduled Commands
- Events
- Exceptions
- Configuration
- Customization
- Testing
Requirements
- PHP 8.2+
- Laravel 11, 12, or 13
Installation
Publish the config and migrations, then run them:
This creates six tables: plans, features, feature_plan, subscriptions, feature_usages, and feature_subscribable.
Quick Start
Add the trait and contract to your subscribable model (e.g. Team, User, or Organization):
Create a plan, add features, and subscribe:
Plans
Plans define billing cycles, trial periods, and grace periods.
Plan columns
| Column | Type | Default | Description |
|---|---|---|---|
name |
string |
— | Display name |
slug |
string |
— | Unique identifier |
description |
string\|null |
null |
Optional description |
is_free |
bool |
false |
Free plans never expire (ends_at is null) |
is_active |
bool |
true |
Whether the plan accepts new subscriptions |
billing_period |
int |
1 |
Number of intervals per billing cycle |
billing_interval |
Interval |
Month |
Day, Week, Month, or Year |
trial_days |
int |
0 |
Trial length in days (0 = no trial) |
grace_days |
int |
0 |
Days of access after cancellation |
sort_order |
int |
0 |
Display ordering |
Plan methods
Features
Four feature types model different SaaS quota patterns:
| Type | Behaviour | Resets | Releases | Charges |
|---|---|---|---|---|
| Toggle | On/off access gate | — | — | — |
| Consumable | Depletable quota | Periodically | No | On overage |
| Limit | Hard cap with release | No | Yes | On overage |
| Metered | Pay-per-use, no cap | — | No | Per unit |
Feature methods
Attaching features to plans
Features are attached to plans via a pivot table with allocation data:
Unlimited: Setting
valueto0grants unlimited usage for that feature.
Pivot data access
Pivot allocation data is accessed through the HasFeaturePivot contract on the pivot models (FeaturePlan, FeatureSubscribable):
Subscriptions
Creating a subscription
If a plan has trial_days > 0, the subscription starts in Trialing status automatically. Free plans create subscriptions with ends_at set to null (never expires).
Subscription statuses
| Status | Description |
|---|---|
Active |
Normal active subscription |
Trialing |
In trial period |
PastDue |
Payment overdue |
Cancelled |
Cancelled by user |
Expired |
Billing period ended |
Checking status on the subscribable
Checking status on the subscription
Managing subscriptions
subscriptions() vs subscription()
$team->subscriptions()— rawMorphManyrelationship (all records, any status)$team->subscription()— resolves the current active/trialing subscription, cached per request
Feature Usage
All feature operations are available directly on the subscribable model:
How consumption works per type
| Type | consume() behaviour |
|---|---|
| Toggle | No-op (access is checked via hasFeature) |
| Consumable | Increments usage, resets when valid_until expires, charges overage if HasFunds |
| Limit | Increments usage (use release() to free slots), charges overage if HasFunds |
| Metered | Increments usage and charges per unit if HasFunds |
Direct Feature Grants
Grant features directly to a subscribable, independent of their plan. Grants are additive — if a plan provides 10,000 API calls and a direct grant adds 50,000, the total quota is 60,000.
Metered Billing & Overage
Implement HasFunds alongside Subscribable to enable pay-per-use and overage billing:
Billing behaviour per feature type
| Feature Type | Without HasFunds |
With HasFunds |
|---|---|---|
| Toggle | Access check only | Access check only |
| Consumable | Hard quota limit — exceeding throws | Quota + automatic overage charging when exceeded |
| Limit | Hard cap — exceeding throws | Hard cap + automatic overage charging when exceeded |
| Metered | Free unlimited usage tracking | Charged per unit consumed, deducted from balance |
Overage kicks in when a consumable or limit feature exceeds its quota and the subscribable has both:
- A
unit_priceconfigured on the feature HasFundsimplemented with sufficient balance
Checking remaining overage capacity
Use remainingOverage() to check how many additional overage units a subscribable can afford based on their current balance:
Returns '0' when:
- The subscribable does not implement
HasFunds - The feature has no
unit_priceconfigured - The balance is zero or negative
Note: Since the balance is shared across all features, consuming overage on one feature reduces the overage capacity for all others. The value represents a point-in-time snapshot.
DTOs
FeatureInfo
Rich snapshot of a feature's current state for a subscribable:
SubscriptionInfo
Complete subscription snapshot for building UI:
ConsumptionResult
Returned internally after consuming units:
All features
Middleware
Three middleware are registered automatically via the config. They throw 403 responses on failure.
| Middleware | Purpose | Usage |
|---|---|---|
subscribed |
Requires active subscription | Route::middleware('subscribed') |
plan:{slug} |
Requires specific plan | Route::middleware('plan:pro') |
feature:{slug} |
Requires specific feature | Route::middleware('feature:api-calls') |
The subscribable is resolved via Subscriptionify::resolveSubscribable(), which defaults to auth()->user(). See Subscribable Resolver to customize.
Blade Directives
Query Scopes
Query scopes are available on models that use the InteractsWithSubscriptions trait:
Scheduled Commands
Subscriptionify ships with an artisan command to automatically expire overdue subscriptions:
This finds all Active subscriptions whose ends_at date has passed and transitions them to Expired status, firing a SubscriptionExpired event for each.
Scheduling
Add to your routes/console.php
Tip: The
expired()method on a subscription also returnstruefor active subscriptions with a pastends_at— providing a real-time safety net between scheduler runs.
Events
All lifecycle events are dispatched automatically:
| Event | Dispatched when |
|---|---|
SubscriptionCreated |
A new subscription is created |
SubscriptionRenewed |
A subscription is renewed |
SubscriptionCancelled |
A subscription is cancelled |
SubscriptionResumed |
A cancelled subscription is resumed |
SubscriptionPlanChanged |
The subscription's plan is changed |
SubscriptionExpired |
A subscription is expired |
SubscriptionExpiring |
A subscription is about to expire |
SubscriptionMarkedPastDue |
A subscription is marked as past due |
FeatureConsumed |
Feature units are consumed |
FeatureReleased |
Feature units are released (limit type) |
Exceptions
| Exception | When |
|---|---|
SubscriptionException |
Already subscribed, cannot resume ended subscription |
FeatureException |
Feature not found, quota exceeded, non-limit release |
InsufficientFundsException |
Balance too low for metered charge or overage |
Configuration
Publish the config file:
Customization
Custom models
Extend the base models and register them in the config. All internal relationships resolve from config automatically.
Subscribable resolver
By default, auth()->user() is used as the subscribable for middleware and Blade directives. Override this in your AppServiceProvider:
Custom subscription resolution
Override resolveSubscription() in your model to change which subscription is resolved. The default resolves the latest Active or Trialing subscription:
subscription()isfinal— overrideresolveSubscription()instead. The caching layer stays intact.