Skip to content

Commit 0c10e15

Browse files
committed
fix: scope subscription slug uniqueness per subscriber
1 parent fd71351 commit 0c10e15

4 files changed

Lines changed: 86 additions & 134 deletions

File tree

src/Models/Feature.php

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
namespace Laravelcm\Subscriptions\Models;
66

77
use Carbon\Carbon;
8+
use Carbon\CarbonInterface;
9+
use Illuminate\Database\Eloquent\Collection;
810
use Illuminate\Database\Eloquent\Factories\HasFactory;
911
use Illuminate\Database\Eloquent\Model;
1012
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -20,33 +22,19 @@
2022

2123
/**
2224
* @property-read int|string $id
23-
* @property string $slug
24-
* @property array $title
25-
* @property array $description
26-
* @property string $value
27-
* @property int $resettable_period
28-
* @property string $resettable_interval
29-
* @property int $sort_order
30-
* @property Carbon|null $created_at
31-
* @property Carbon|null $updated_at
32-
* @property Carbon|null $deleted_at
25+
* @property-read string $slug
26+
* @property-read array $title
27+
* @property-read array $description
28+
* @property-read string $value
29+
* @property-read int|string $plan_id
30+
* @property-read int $resettable_period
31+
* @property-read string $resettable_interval
32+
* @property-read int $sort_order
33+
* @property-read CarbonInterface $created_at
34+
* @property-read CarbonInterface $updated_at
35+
* @property-read ?CarbonInterface $deleted_at
3336
* @property-read Plan $plan
34-
* @property-read \Illuminate\Database\Eloquent\Collection|SubscriptionUsage[] $usages
35-
*
36-
* @method static \Illuminate\Database\Eloquent\Builder|Feature byPlanId($planId)
37-
* @method static \Illuminate\Database\Eloquent\Builder|Feature ordered($direction = 'asc')
38-
* @method static \Illuminate\Database\Eloquent\Builder|Feature whereCreatedAt($value)
39-
* @method static \Illuminate\Database\Eloquent\Builder|Feature whereDeletedAt($value)
40-
* @method static \Illuminate\Database\Eloquent\Builder|Feature whereDescription($value)
41-
* @method static \Illuminate\Database\Eloquent\Builder|Feature whereId($value)
42-
* @method static \Illuminate\Database\Eloquent\Builder|Feature whereTitle($value)
43-
* @method static \Illuminate\Database\Eloquent\Builder|Feature wherePlanId($value)
44-
* @method static \Illuminate\Database\Eloquent\Builder|Feature whereResettableInterval($value)
45-
* @method static \Illuminate\Database\Eloquent\Builder|Feature whereResettablePeriod($value)
46-
* @method static \Illuminate\Database\Eloquent\Builder|Feature whereSlug($value)
47-
* @method static \Illuminate\Database\Eloquent\Builder|Feature whereSortOrder($value)
48-
* @method static \Illuminate\Database\Eloquent\Builder|Feature whereUpdatedAt($value)
49-
* @method static \Illuminate\Database\Eloquent\Builder|Feature whereValue($value)
37+
* @property-read Collection<int, SubscriptionUsage> $usages
5038
*/
5139
class Feature extends Model implements Sortable
5240
{

src/Models/Plan.php

Lines changed: 25 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Laravelcm\Subscriptions\Models;
66

7+
use Carbon\CarbonInterface;
8+
use Illuminate\Database\Eloquent\Collection;
79
use Illuminate\Database\Eloquent\Factories\HasFactory;
810
use Illuminate\Database\Eloquent\Model;
911
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -16,53 +18,29 @@
1618

1719
/**
1820
* @property-read int|string $id
19-
* @property string $slug
20-
* @property array $name
21-
* @property array $description
22-
* @property bool $is_active
23-
* @property float $price
24-
* @property float $signup_fee
25-
* @property string $currency
26-
* @property int $trial_period
27-
* @property string $trial_interval
28-
* @property int $invoice_period
29-
* @property string $invoice_interval
30-
* @property int $grace_period
31-
* @property string $grace_interval
32-
* @property int $prorate_day
33-
* @property int $prorate_period
34-
* @property int $prorate_extend_due
35-
* @property int $active_subscribers_limit
36-
* @property int $sort_order
37-
* @property \Carbon\Carbon|null $created_at
38-
* @property \Carbon\Carbon|null $updated_at
39-
* @property \Carbon\Carbon|null $deleted_at
40-
* @property-read \Illuminate\Database\Eloquent\Collection|\Laravelcm\Subscriptions\Models\Feature[] $features
41-
* @property-read \Illuminate\Database\Eloquent\Collection|\Laravelcm\Subscriptions\Models\Subscription[] $subscriptions
42-
*
43-
* @method static \Illuminate\Database\Eloquent\Builder|Plan ordered($direction = 'asc')
44-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereActiveSubscribersLimit($value)
45-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereCreatedAt($value)
46-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereCurrency($value)
47-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereDeletedAt($value)
48-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereDescription($value)
49-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereGraceInterval($value)
50-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereGracePeriod($value)
51-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereId($value)
52-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereInvoiceInterval($value)
53-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereInvoicePeriod($value)
54-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereIsActive($value)
55-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereName($value)
56-
* @method static \Illuminate\Database\Eloquent\Builder|Plan wherePrice($value)
57-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereProrateDay($value)
58-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereProrateExtendDue($value)
59-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereProratePeriod($value)
60-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereSignupFee($value)
61-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereSlug($value)
62-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereSortOrder($value)
63-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereTrialInterval($value)
64-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereTrialPeriod($value)
65-
* @method static \Illuminate\Database\Eloquent\Builder|Plan whereUpdatedAt($value)
21+
* @property-read string $slug
22+
* @property-read array $name
23+
* @property-read array $description
24+
* @property-read bool $is_active
25+
* @property-read float $price
26+
* @property-read float $signup_fee
27+
* @property-read string $currency
28+
* @property-read int $trial_period
29+
* @property-read string $trial_interval
30+
* @property-read int $invoice_period
31+
* @property-read string $invoice_interval
32+
* @property-read int $grace_period
33+
* @property-read string $grace_interval
34+
* @property-read int $prorate_day
35+
* @property-read int $prorate_period
36+
* @property-read int $prorate_extend_due
37+
* @property-read int $active_subscribers_limit
38+
* @property-read int $sort_order
39+
* @property-read CarbonInterface $created_at
40+
* @property-read CarbonInterface $updated_at
41+
* @property-read ?CarbonInterface $deleted_at
42+
* @property-read Collection<int, Feature> $features
43+
* @property-read Collection<int, Subscription> $subscriptions
6644
*/
6745
class Plan extends Model implements Sortable
6846
{

src/Models/Subscription.php

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
namespace Laravelcm\Subscriptions\Models;
66

77
use Carbon\Carbon;
8+
use Carbon\CarbonInterface;
89
use Illuminate\Database\Eloquent\Builder;
10+
use Illuminate\Database\Eloquent\Collection;
911
use Illuminate\Database\Eloquent\Factories\HasFactory;
1012
use Illuminate\Database\Eloquent\Model;
1113
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -21,43 +23,23 @@
2123

2224
/**
2325
* @property-read int|string $id
24-
* @property string $subscriber_type
25-
* @property string $slug
26-
* @property array $title
27-
* @property array $description
28-
* @property Carbon|null $trial_ends_at
29-
* @property Carbon|null $starts_at
30-
* @property Carbon|null $ends_at
31-
* @property Carbon|null $cancels_at
32-
* @property Carbon|null $canceled_at
33-
* @property Carbon|null $created_at
34-
* @property Carbon|null $updated_at
35-
* @property Carbon|null $deleted_at
26+
* @property-read string $subscriber_type
27+
* @property-read int|string $subscriber_id
28+
* @property-read string $slug
29+
* @property-read array $name
30+
* @property-read array $description
31+
* @property-read int|string $plan_id
32+
* @property-read ?CarbonInterface $trial_ends_at
33+
* @property-read ?CarbonInterface $starts_at
34+
* @property-read ?CarbonInterface $ends_at
35+
* @property-read ?CarbonInterface $cancels_at
36+
* @property-read ?CarbonInterface $canceled_at
37+
* @property-read CarbonInterface $created_at
38+
* @property-read CarbonInterface $updated_at
39+
* @property-read ?CarbonInterface $deleted_at
3640
* @property-read Plan $plan
37-
* @property-read \Illuminate\Database\Eloquent\Collection|\Laravelcm\Subscriptions\Models\SubscriptionUsage[] $usage
41+
* @property-read Collection<int, SubscriptionUsage> $usage
3842
* @property-read Model $subscriber
39-
*
40-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription byPlanId($planId)
41-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription findEndedPeriod()
42-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription findEndedTrial()
43-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription findEndingPeriod($dayRange = 3)
44-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription findEndingTrial($dayRange = 3)
45-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription ofSubscriber(\Illuminate\Database\Eloquent\Model $subscriber)
46-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription whereCanceledAt($value)
47-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription whereCancelsAt($value)
48-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription whereCreatedAt($value)
49-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription whereDeletedAt($value)
50-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription whereDescription($value)
51-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription whereEndsAt($value)
52-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription whereId($value)
53-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription whereTitle($value)
54-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription wherePlanId($value)
55-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription whereSlug($value)
56-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription whereStartsAt($value)
57-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription whereTrialEndsAt($value)
58-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription whereUpdatedAt($value)
59-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription whereSubscriberId($value)
60-
* @method static \Illuminate\Database\Eloquent\Builder|Subscription whereSubscriberType($value)
6143
*/
6244
class Subscription extends Model
6345
{
@@ -127,7 +109,11 @@ public function getSlugOptions(): SlugOptions
127109
return SlugOptions::create()
128110
->doNotGenerateSlugsOnUpdate()
129111
->generateSlugsFrom('name')
130-
->saveSlugsTo('slug');
112+
->saveSlugsTo('slug')
113+
->extraScope(fn ($builder) => $builder
114+
->where('subscriber_type', $this->subscriber_type)
115+
->where('subscriber_id', $this->subscriber_id)
116+
);
131117
}
132118

133119
public function subscriber(): MorphTo
@@ -167,10 +153,10 @@ public function ended(): bool
167153

168154
public function cancel(bool $immediately = false): self
169155
{
170-
$this->canceled_at = Carbon::now();
156+
$this->fill(['canceled_at' => Carbon::now()]);
171157

172158
if ($immediately) {
173-
$this->ends_at = $this->canceled_at;
159+
$this->fill(['ends_at' => $this->canceled_at]);
174160
}
175161

176162
$this->save();
@@ -190,7 +176,7 @@ public function changePlan(Plan $plan): self
190176
}
191177

192178
// Attach new plan to subscription
193-
$this->plan_id = $plan->getKey();
179+
$this->fill(['plan_id' => $plan->getKey()]);
194180
$this->save();
195181

196182
return $this;
@@ -217,7 +203,7 @@ public function renew(): self
217203

218204
// Renew period
219205
$subscription->setNewPeriod();
220-
$subscription->canceled_at = null;
206+
$subscription->fill(['canceled_at' => null]);
221207
$subscription->save();
222208
});
223209

@@ -300,8 +286,10 @@ protected function setNewPeriod(string $invoice_interval = '', ?int $invoice_per
300286
start: $start ?? Carbon::now()
301287
);
302288

303-
$this->starts_at = $period->getStartDate();
304-
$this->ends_at = $period->getEndDate();
289+
$this->fill([
290+
'starts_at' => $period->getStartDate(),
291+
'ends_at' => $period->getEndDate(),
292+
]);
305293

306294
return $this;
307295
}
@@ -310,6 +298,7 @@ public function recordFeatureUsage(string $featureSlug, int $uses = 1, bool $inc
310298
{
311299
$feature = $this->plan->features()->where('slug', $featureSlug)->first();
312300

301+
/** @var SubscriptionUsage $usage */
313302
$usage = $this->usage()->firstOrNew([
314303
'subscription_id' => $this->getKey(),
315304
'feature_id' => $feature->getKey(),
@@ -320,16 +309,18 @@ public function recordFeatureUsage(string $featureSlug, int $uses = 1, bool $inc
320309
if ($usage->valid_until === null) {
321310
// Set date from subscription creation date so the reset
322311
// period match the period specified by the subscription's plan.
323-
$usage->valid_until = $feature->getResetDate($this->created_at);
312+
$usage->fill(['valid_until' => $feature->getResetDate($this->created_at)]);
324313
} elseif ($usage->expired()) {
325314
// If the usage record has been expired, let's assign
326315
// a new expiration date and reset the uses to zero.
327-
$usage->valid_until = $feature->getResetDate($usage->valid_until);
328-
$usage->used = 0;
316+
$usage->fill([
317+
'valid_until' => $feature->getResetDate($usage->valid_until),
318+
'used' => 0,
319+
]);
329320
}
330321
}
331322

332-
$usage->used = $incremental ? $usage->used + $uses : $uses;
323+
$usage->fill(['used' => $incremental ? $usage->used + $uses : $uses]);
333324

334325
$usage->save();
335326

src/Models/SubscriptionUsage.php

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Laravelcm\Subscriptions\Models;
66

77
use Carbon\Carbon;
8+
use Carbon\CarbonInterface;
89
use Illuminate\Database\Eloquent\Builder;
910
use Illuminate\Database\Eloquent\Factories\HasFactory;
1011
use Illuminate\Database\Eloquent\Model;
@@ -13,23 +14,16 @@
1314

1415
/**
1516
* @property-read int|string $id
16-
* @property int $used
17-
* @property Carbon|null $valid_until
18-
* @property Carbon|null $created_at
19-
* @property Carbon|null $updated_at
20-
* @property Carbon|null $deleted_at
17+
* @property-read int $feature_id
18+
* @property-read int $subscription_id
19+
* @property-read int $used
20+
* @property-read string $timezone
21+
* @property-read ?CarbonInterface $valid_until
22+
* @property-read CarbonInterface $created_at
23+
* @property-read CarbonInterface $updated_at
24+
* @property-read ?CarbonInterface $deleted_at
2125
* @property-read Feature $feature
2226
* @property-read Subscription $subscription
23-
*
24-
* @method static \Illuminate\Database\Eloquent\Builder|SubscriptionUsage byFeatureSlug($featureSlug)
25-
* @method static \Illuminate\Database\Eloquent\Builder|SubscriptionUsage whereCreatedAt($value)
26-
* @method static \Illuminate\Database\Eloquent\Builder|SubscriptionUsage whereDeletedAt($value)
27-
* @method static \Illuminate\Database\Eloquent\Builder|SubscriptionUsage whereFeatureId($value)
28-
* @method static \Illuminate\Database\Eloquent\Builder|SubscriptionUsage whereId($value)
29-
* @method static \Illuminate\Database\Eloquent\Builder|SubscriptionUsage whereSubscriptionId($value)
30-
* @method static \Illuminate\Database\Eloquent\Builder|SubscriptionUsage whereUpdatedAt($value)
31-
* @method static \Illuminate\Database\Eloquent\Builder|SubscriptionUsage whereUsed($value)
32-
* @method static \Illuminate\Database\Eloquent\Builder|SubscriptionUsage whereValidUntil($value)
3327
*/
3428
class SubscriptionUsage extends Model
3529
{
@@ -41,6 +35,7 @@ class SubscriptionUsage extends Model
4135
'feature_id',
4236
'used',
4337
'valid_until',
38+
'timezone',
4439
];
4540

4641
protected $casts = [

0 commit comments

Comments
 (0)