Releases: Crumbls/subscriptions
Releases · Crumbls/subscriptions
v2.0.0 — Correctness, DX, and Tooling Polish
Major release focused on correctness, developer experience, and infrastructure. See UPGRADING.md for the migration path from 1.x.
Breaking
plan_subscriptions.slugis no longer globally unique. It is now unique per subscriber:(subscriber_type, subscriber_id, slug). This unblocks polymorphic use — two different subscriber types (e.g.UserandTeam) can now both hold amainsubscription. Existing consumers must rebuild the unique index. SeeUPGRADING.md.- Dropped unused
prorate_day,prorate_period,prorate_extend_duecolumns from theplanstable. These were never read or written by any code path. SubscriptionCreated,SubscriptionCanceled,SubscriptionRenewed, andSubscriptionPlanChangedno longer implementShouldBroadcast.broadcastOn()was returning[]so nothing was ever broadcast. Thesubscriptions.broadcast_eventsconfig key has been removed. Consumers who want broadcasting should extend these events in their own application.PlanSubscription::setNewPeriod()signature changed from(Interval|string $invoiceInterval = '', int $invoicePeriod = 0, Carbon|string $start = '')to(?Interval $invoiceInterval = null, ?int $invoicePeriod = null, ?Carbon $start = null). Only relevant if you were subclassingPlanSubscription.PlanSubscription::recordFeatureUsage()now throwsCrumbls\Subscriptions\Exceptions\UnknownFeatureExceptioninstead ofIlluminate\Database\Eloquent\ModelNotFoundExceptionwhen called with a feature slug that isn't on the subscription's plan.
Fixed
PlanSubscription::active()no longer contained an unreachable branch. Behavior is unchanged for all documented states.- Feature usage increments are now wrapped in a
DB::transaction()withlockForUpdate(). Two concurrent writers can no longer lose a usage increment. newPlanSubscription()now locks the plan row during subscriber-limit enforcement. Two concurrent subscribers to a plan withactive_subscribers_limit = Ncan no longer both squeeze past the check.PlanSubscriptionUsage::scopeByFeatureSlugnow filters via a single subquery instead of fetching the feature row first.subscriptions:prunenow prunes naturally-expired subscriptions too (previously only canceled + expired were pruned), matching the command description.
Added
- Composite index on
(subscriber_type, subscriber_id, slug)and plain index onends_atin theplan_subscriptionstable. Feature::scopeBySlug()andFeature::hasReset()helpers.HasPlanSubscriptions::subscribe()— friendly alias fornewPlanSubscription().- Model factories (
PlanFactory,FeatureFactory,PlanSubscriptionFactory,PlanSubscriptionUsageFactory) with useful state methods (free(),paid(),withTrial(),withGrace(),limitedTo(),ended(),canceled(),onTrial(),resettableMonthly(),resettableDaily()). - GitHub Actions workflows for tests (PHP 8.3/8.4 × Laravel 11/12/13) and static analysis (Pint, PHPStan, Rector).
- Dependabot config, Pint config,
CONTRIBUTING.md,SECURITY.md, issue and PR templates.
Stats
- 73 → 102 tests (227 assertions)
- Full PHP 8.3/8.4 × Laravel 11/12/13 compatibility exercised in CI
v1.1.0 — Laravel 13 support
Laravel 13 support
- Adds Laravel 13 to the supported matrix (
illuminate/*: ^13.0,orchestra/testbench: ^11.0) - Minimum PHP bumped to 8.3 (required by Laravel 13)
- Spatie dependencies widened:
eloquent-sortable ^4.4 || ^5.0,laravel-sluggable ^3.8,laravel-translatable ^6.13 - Dev tooling widened: Pest 3 or 4, pest-plugin-laravel 3 or 4, PHPUnit 11 or 12
driftingly/rector-laravelbumped to^2.3with Laravel 12/13 Rector sets; Rector PHP set switched to 8.3
Fixes
$plan->features()->create(...)test callers now pass pivotvalueas the second argument (properBelongsToMany::createsignature), fixingNOT NULL constraint failed: plan_features.valuefailures in the suiteresets usage when period expirestest freezes Carbon before creating the subscription sovalid_untilmath aligns with the simulated clock
Compatibility
Still supports Laravel 11 and 12. Consumers on PHP 8.2 will need to upgrade to 8.3.
1.0.0
Refactor: standalone features with many-to-many plan pivot - New Feature model (standalone, translatable, sortable) - PlanFeature is now a Pivot model (plan_id, feature_id, value, sort_order) - Plan::features() changed from hasMany to belongsToMany - New 'features' table, 'plan_features' is now a proper pivot table - Usage tracking references features table directly - getFeatureValue() reads from pivot - Updated config with feature model and features table - Comprehensive README with full documentation