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

Laravel Soulbscription Laravel Package

lucasdotdev/laravel-soulbscription

Soulbscription is a Laravel package for subscription and feature management. Define plans, subscribe models, track feature quotas and consumption, and handle recurring or prepaid access through a clean, expressive API.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require lucasdotvin/laravel-soulbscription
php artisan vendor:publish --tag="soulbscription"
php artisan migrate

Decide your model strategy before writing feature code. The package ships Plan, Feature, Subscription and Consumption models, but config/soulbscription.php lets you point at your own subclasses and rename tables. Swapping models after you already have production rows means rewriting foreign keys, so make this call on day one.

Wire the traits. Add the subscription trait and the feature-consumption trait to your billable model (usually User) and confirm the subscriber relation resolves — every package table keys off the subscriber, not the user table.

First use case — quota-limited feature. Model a "10 posts per month" plan:

  1. Create a Feature with a stable slug (posts).
  2. Create a Plan with periodicity_type (month) and periodicity (1), then attach the feature to the plan with a charges value on the pivot (10).
  3. Subscribe a user: $user->subscribeTo($plan).
  4. Gate the action: check canConsume('posts', 1), do the work, then consume('posts', 1).

Where to look first: the published migrations (they show exactly how periods, consumptions and the plan/feature pivot are shaped), then the config file (models, tables, any listeners), then the traits the package adds to your user model — the trait is the real API surface and the fastest way to see the exact method signatures.

Implementation Patterns

Treat plans as seed data, not user-editable records. Define them in a seeder with a stable lookup key, attach features and charges there, and keep admin UI to "choose a plan" rather than "compose a plan". Charges live on the plan↔feature pivot, so the same feature can cost 10 on one plan and unlimited on another — that flexibility is great until support has to reason about half-edited plans.

Gate with policies, consume after success. Put canConsume() in a Gate/FormRequest/policy so the 402/403 response is consistent, but only call consume() after the side effect actually succeeded:

DB::transaction(function () use ($user, $post) {
    $user->consume('posts', 1);
    $post->publish();
});

Wrapping both in one transaction means a failed publish doesn't burn quota.

Distinguish increment from sync. Use consume() for "the user did a thing" and setConsumedQuantity() for mirroring an external number (seats used, storage bytes, metered billing). Idempotent syncs keep you out of the "why does this user have 47 consumptions today" hole.

Build a small read model for balance. balance() / featureBalance('posts') are fine per-request, but pricing pages and dashboards that iterate features for every row produce N+1s. Cache a per-user balance snapshot, invalidate it on the package's consumption and subscription events.

Hook lifecycle via events, not model observers. Listen for subscription started / renewed / cancelled / switched to fire welcome mail, provision resources, or push usage to an external billing system. Observers on the package models will also fire for internal package writes, which is rarely what you want.

Schedule renewals explicitly. Renewal is not automatic magic you can ignore — schedule the package's renewal command (check php artisan list for the exact command in your version) and make sure the scheduler runs in the timezone your periods mean. Add a monitoring check that no subscription sits past expired_at without being renewed or marked expired.

Model the cancel UX up front. "Cancel at period end" vs "cancel now" is a product decision the package won't make for you: keep the subscription record until expired_at and gate on active status rather than deleting anything.

Test with time travel. Carbon::setTestNow() plus travel through a period boundary is the only reliable way to assert renewal, expiration and consumption-reset behaviour. Add a test for a plan with no periodicity (see gotchas).

Gotchas and Tips

4.3.2 is relevant if you sell lifetime/one-off plans. The hotfix ("preset expiration in renewals for plans with no periodicity") addresses renewals writing an expiration onto plans that have no periodicity. If you upgraded to 4.3.0/4.3.1 and sell non-expiring plans, audit any subscription that went through renewal in that window — expired_at may be set where it should be null — and pin >=4.3.2.

Null periodicity means "never expires", not "expired". Any of your own status helpers must treat expired_at === null as active. This is the single easiest way to accidentally lock out lifetime customers, and it's the class of bug 4.3.2 fixes.

Consumption is scoped to the subscription/period, not the user forever. When debugging "quota didn't reset", look at the subscription boundary first (period start/end, timezone) before suspecting the consumption records. Renewing creates/rolls the window the balance is computed against.

Concurrent consumption overshoots. Two parallel requests both pass canConsume() and both consume. For hard quotas, wrap check+consume in a transaction with a row lock on the subscription (or a unique/length constraint at the DB level), and accept that a soft quota is fine for most SaaS gating.

Charges are configuration, not data. Editing the pivot charges changes the computed balance immediately for the current period. If you need historical pricing, snapshot the charges onto the subscription/consumption at consume time rather than reading the pivot later.

Changing models or table names in config post-launch breaks relations silently — queries start hitting an empty/new table rather than erroring loudly. Do it in one migration with data copy, or not at all.

Debugging recipe: inspect subscriptions (started/expired, plan id), consumptions (feature id, quantity, subscription id) and the plan↔feature pivot (charges) in that order. Most "balance is wrong" reports are a missing pivot row or an expired subscription, not a package bug.

Extension points worth knowing: config for models/tables, the lifecycle events for integrations, and your own Plan/Feature subclasses for domain-specific accessors. Avoid editing vendor migrations — republish and adjust at install time if you need extra columns, and prefer additive migrations afterwards.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core