Skip to content

Releases: Crumbls/subscriptions

v2.0.0 — Correctness, DX, and Tooling Polish

24 Apr 03:27

Choose a tag to compare

Major release focused on correctness, developer experience, and infrastructure. See UPGRADING.md for the migration path from 1.x.

Breaking

  • plan_subscriptions.slug is 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. User and Team) can now both hold a main subscription. Existing consumers must rebuild the unique index. See UPGRADING.md.
  • Dropped unused prorate_day, prorate_period, prorate_extend_due columns from the plans table. These were never read or written by any code path.
  • SubscriptionCreated, SubscriptionCanceled, SubscriptionRenewed, and SubscriptionPlanChanged no longer implement ShouldBroadcast. broadcastOn() was returning [] so nothing was ever broadcast. The subscriptions.broadcast_events config 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 subclassing PlanSubscription.
  • PlanSubscription::recordFeatureUsage() now throws Crumbls\Subscriptions\Exceptions\UnknownFeatureException instead of Illuminate\Database\Eloquent\ModelNotFoundException when 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() with lockForUpdate(). 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 with active_subscribers_limit = N can no longer both squeeze past the check.
  • PlanSubscriptionUsage::scopeByFeatureSlug now filters via a single subquery instead of fetching the feature row first.
  • subscriptions:prune now 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 on ends_at in the plan_subscriptions table.
  • Feature::scopeBySlug() and Feature::hasReset() helpers.
  • HasPlanSubscriptions::subscribe() — friendly alias for newPlanSubscription().
  • 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

22 Apr 06:33

Choose a tag to compare

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-laravel bumped to ^2.3 with Laravel 12/13 Rector sets; Rector PHP set switched to 8.3

Fixes

  • $plan->features()->create(...) test callers now pass pivot value as the second argument (proper BelongsToMany::create signature), fixing NOT NULL constraint failed: plan_features.value failures in the suite
  • resets usage when period expires test freezes Carbon before creating the subscription so valid_until math 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

21 Feb 18:26

Choose a tag to compare

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