Skip to content

Commit 0275604

Browse files
Merge pull request #107 from trypostit/chore/remove-legacy-plans
chore: remove legacy plan tiers (starter/plus/pro/max)
2 parents 11319cb + 21e45b4 commit 0275604

9 files changed

Lines changed: 50 additions & 150 deletions

File tree

app/Enums/Plan/Slug.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,11 @@
66

77
enum Slug: string
88
{
9-
case Starter = 'starter';
10-
case Plus = 'plus';
11-
case Pro = 'pro';
12-
case Max = 'max';
139
case Workspace = 'workspace';
1410

1511
public function label(): string
1612
{
1713
return match ($this) {
18-
self::Starter => 'Starter',
19-
self::Plus => 'Plus',
20-
self::Pro => 'Pro',
21-
self::Max => 'Max',
2214
self::Workspace => 'Workspace',
2315
};
2416
}

database/factories/PlanFactory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ class PlanFactory extends Factory
2121
public function definition(): array
2222
{
2323
return [
24-
'slug' => fake()->randomElement(Slug::cases()),
25-
'name' => fake()->word(),
24+
'slug' => Slug::Workspace,
25+
'name' => 'Workspace',
2626
'stripe_monthly_price_id' => null,
2727
'stripe_yearly_price_id' => null,
2828
'monthly_credits_limit' => 2500,

tests/Feature/Jobs/PostHog/SyncAccountUsageTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test_key']);
2020

2121
$this->account = Account::factory()->create([
22-
'plan_id' => Plan::query()->where('slug', 'starter')->first()?->id,
22+
'plan_id' => Plan::query()->where('slug', 'workspace')->first()?->id,
2323
]);
2424
$this->user = User::factory()->create(['account_id' => $this->account->id]);
2525
$this->account->update(['owner_id' => $this->user->id]);

tests/Feature/Jobs/PostHog/SyncUserTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test_key']);
1818

1919
$this->account = Account::factory()->create([
20-
'plan_id' => Plan::query()->where('slug', 'starter')->first()?->id,
20+
'plan_id' => Plan::query()->where('slug', 'workspace')->first()?->id,
2121
]);
2222
$this->user = User::factory()->create(['account_id' => $this->account->id]);
2323
$this->account->update(['owner_id' => $this->user->id]);

tests/Feature/Jobs/PostHog/TrackBillingTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test_key']);
1919

2020
$this->account = Account::factory()->create([
21-
'plan_id' => Plan::query()->where('slug', 'starter')->first()?->id,
21+
'plan_id' => Plan::query()->where('slug', 'workspace')->first()?->id,
2222
]);
2323
$this->user = User::factory()->create(['account_id' => $this->account->id]);
2424
$this->account->update(['owner_id' => $this->user->id]);

tests/Feature/Listeners/StripeEventListenerTest.php

Lines changed: 38 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,12 @@
1515
beforeEach(function () {
1616
config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test_key']);
1717

18-
Plan::factory()->create([
19-
'slug' => Slug::Starter,
20-
'name' => 'Starter',
21-
'stripe_monthly_price_id' => 'price_starter_monthly',
22-
'stripe_yearly_price_id' => 'price_starter_yearly',
23-
]);
24-
Plan::factory()->create([
25-
'slug' => Slug::Pro,
26-
'name' => 'Pro',
27-
'stripe_monthly_price_id' => 'price_pro_monthly',
28-
'stripe_yearly_price_id' => 'price_pro_yearly',
18+
// Use the seeded Workspace plan; set deterministic price ids so the
19+
// assertions don't depend on `.env.testing`.
20+
$this->plan = Plan::where('slug', Slug::Workspace)->firstOrFail();
21+
$this->plan->update([
22+
'stripe_monthly_price_id' => 'price_workspace_monthly',
23+
'stripe_yearly_price_id' => 'price_workspace_yearly',
2924
]);
3025

3126
$this->account = Account::factory()->create(['stripe_id' => 'cus_test123']);
@@ -51,7 +46,6 @@
5146
});
5247

5348
test('subscription created clears the generic trial_ends_at on the account', function () {
54-
$starter = Plan::query()->where('slug', 'starter')->firstOrFail();
5549
$this->account->update(['trial_ends_at' => now()->addDays(3)]);
5650

5751
$this->listener->handle(new WebhookReceived([
@@ -60,7 +54,7 @@
6054
'customer' => 'cus_test123',
6155
'id' => 'sub_123',
6256
'status' => 'active',
63-
'items' => ['data' => [['price' => ['id' => $starter->stripe_monthly_price_id]]]],
57+
'items' => ['data' => [['price' => ['id' => 'price_workspace_monthly']]]],
6458
]],
6559
]));
6660

@@ -184,8 +178,7 @@
184178
});
185179

186180
test('subscription deleted dispatches TrackBilling with subscription.cancelled event', function () {
187-
$starter = Plan::query()->where('slug', 'starter')->firstOrFail();
188-
$this->account->update(['plan_id' => $starter->id]);
181+
$this->account->update(['plan_id' => $this->plan->id]);
189182

190183
Bus::fake([TrackBilling::class]);
191184

@@ -236,116 +229,81 @@
236229
});
237230

238231
// ========================================
239-
// Plan sync — domain logic
232+
// Plan mapping by Stripe price id
240233
// ========================================
241-
//
242-
// Tests below override the seeded plans' Stripe price ids with deterministic
243-
// values so the assertions don't depend on `.env.testing` having
244-
// `STRIPE_*_MONTHLY/YEARLY` set.
245-
246-
beforeEach(function () {
247-
Plan::query()->where('slug', 'starter')->update([
248-
'stripe_monthly_price_id' => 'price_starter_monthly',
249-
'stripe_yearly_price_id' => 'price_starter_yearly',
250-
]);
251-
Plan::query()->where('slug', 'pro')->update([
252-
'stripe_monthly_price_id' => 'price_pro_monthly',
253-
'stripe_yearly_price_id' => 'price_pro_yearly',
254-
]);
255-
});
256234

257-
test('subscription updated swaps account plan_id when price matches a different plan', function () {
258-
$starter = Plan::query()->where('slug', 'starter')->firstOrFail();
259-
$pro = Plan::query()->where('slug', 'pro')->firstOrFail();
260-
261-
$this->account->update(['plan_id' => $starter->id]);
235+
test('subscription created syncs the plan from the price id on first activation', function () {
236+
$this->account->update(['plan_id' => null]);
262237

263238
$this->listener->handle(new WebhookReceived([
264-
'type' => 'customer.subscription.updated',
239+
'type' => 'customer.subscription.created',
265240
'data' => ['object' => [
266241
'customer' => 'cus_test123',
267-
'items' => ['data' => [
268-
['price' => ['id' => 'price_pro_monthly']],
269-
]],
242+
'items' => ['data' => [['price' => ['id' => 'price_workspace_monthly']]]],
270243
]],
271244
]));
272245

273-
expect($this->account->fresh()->plan_id)->toBe($pro->id);
246+
expect($this->account->fresh()->plan_id)->toBe($this->plan->id);
274247
});
275248

276-
test('subscription updated leaves plan_id alone when price already matches current plan', function () {
277-
$starter = Plan::query()->where('slug', 'starter')->firstOrFail();
278-
$this->account->update(['plan_id' => $starter->id]);
249+
test('subscription updated maps the plan by its monthly price id', function () {
250+
$this->account->update(['plan_id' => null]);
279251

280252
$this->listener->handle(new WebhookReceived([
281253
'type' => 'customer.subscription.updated',
282254
'data' => ['object' => [
283255
'customer' => 'cus_test123',
284-
'items' => ['data' => [
285-
['price' => ['id' => 'price_starter_monthly']],
286-
]],
256+
'items' => ['data' => [['price' => ['id' => 'price_workspace_monthly']]]],
287257
]],
288258
]));
289259

290-
expect($this->account->fresh()->plan_id)->toBe($starter->id);
260+
expect($this->account->fresh()->plan_id)->toBe($this->plan->id);
291261
});
292262

293-
test('subscription updated ignores unknown price ids without erroring', function () {
294-
$starter = Plan::query()->where('slug', 'starter')->firstOrFail();
295-
$this->account->update(['plan_id' => $starter->id]);
263+
test('subscription updated maps the plan by its yearly price id too', function () {
264+
$this->account->update(['plan_id' => null]);
296265

297266
$this->listener->handle(new WebhookReceived([
298267
'type' => 'customer.subscription.updated',
299268
'data' => ['object' => [
300269
'customer' => 'cus_test123',
301-
'items' => ['data' => [
302-
['price' => ['id' => 'price_unknown_xyz']],
303-
]],
270+
'items' => ['data' => [['price' => ['id' => 'price_workspace_yearly']]]],
304271
]],
305272
]));
306273

307-
expect($this->account->fresh()->plan_id)->toBe($starter->id);
274+
expect($this->account->fresh()->plan_id)->toBe($this->plan->id);
308275
});
309276

310-
test('subscription updated matches yearly price ids too', function () {
311-
$starter = Plan::query()->where('slug', 'starter')->firstOrFail();
312-
$pro = Plan::query()->where('slug', 'pro')->firstOrFail();
313-
314-
$this->account->update(['plan_id' => $starter->id]);
277+
test('subscription updated leaves plan_id alone when the price already matches', function () {
278+
$this->account->update(['plan_id' => $this->plan->id]);
315279

316280
$this->listener->handle(new WebhookReceived([
317281
'type' => 'customer.subscription.updated',
318282
'data' => ['object' => [
319283
'customer' => 'cus_test123',
320-
'items' => ['data' => [
321-
['price' => ['id' => 'price_pro_yearly']],
322-
]],
284+
'items' => ['data' => [['price' => ['id' => 'price_workspace_monthly']]]],
323285
]],
324286
]));
325287

326-
expect($this->account->fresh()->plan_id)->toBe($pro->id);
288+
expect($this->account->fresh()->plan_id)->toBe($this->plan->id);
327289
});
328290

329-
test('subscription created syncs plan from price ids on first activation', function () {
330-
$pro = Plan::query()->where('slug', 'pro')->firstOrFail();
331-
$this->account->update(['plan_id' => null]);
291+
test('subscription updated ignores unknown price ids without erroring', function () {
292+
$this->account->update(['plan_id' => $this->plan->id]);
332293

333294
$this->listener->handle(new WebhookReceived([
334-
'type' => 'customer.subscription.created',
295+
'type' => 'customer.subscription.updated',
335296
'data' => ['object' => [
336297
'customer' => 'cus_test123',
337-
'items' => ['data' => [
338-
['price' => ['id' => 'price_pro_monthly']],
339-
]],
298+
'items' => ['data' => [['price' => ['id' => 'price_unknown_xyz']]]],
340299
]],
341300
]));
342301

343-
expect($this->account->fresh()->plan_id)->toBe($pro->id);
302+
expect($this->account->fresh()->plan_id)->toBe($this->plan->id);
344303
});
345304

346305
test('subscription deleted clears the account plan_id', function () {
347-
$starter = Plan::query()->where('slug', 'starter')->firstOrFail();
348-
$this->account->update(['plan_id' => $starter->id]);
306+
$this->account->update(['plan_id' => $this->plan->id]);
349307

350308
$this->listener->handle(new WebhookReceived([
351309
'type' => 'customer.subscription.deleted',
@@ -377,28 +335,26 @@
377335
// ========================================
378336

379337
test('subscription updated forwards the previous plan name to TrackBilling', function () {
380-
$starter = Plan::query()->where('slug', 'starter')->firstOrFail();
381-
$this->account->update(['plan_id' => $starter->id]);
338+
$this->account->update(['plan_id' => $this->plan->id]);
382339

383340
Bus::fake([TrackBilling::class]);
384341

385342
$this->listener->handle(new WebhookReceived([
386343
'type' => 'customer.subscription.updated',
387344
'data' => ['object' => [
388345
'customer' => 'cus_test123',
389-
'items' => ['data' => [['price' => ['id' => 'price_pro_monthly']]]],
346+
'items' => ['data' => [['price' => ['id' => 'price_workspace_monthly']]]],
390347
]],
391348
]));
392349

393350
Bus::assertDispatched(
394351
TrackBilling::class,
395-
fn ($job) => $job->event === BillingEvent::Updated && $job->previousPlan === $starter->name,
352+
fn ($job) => $job->event === BillingEvent::Updated && $job->previousPlan === $this->plan->name,
396353
);
397354
});
398355

399356
test('subscription deleted forwards the previous plan name to TrackBilling', function () {
400-
$starter = Plan::query()->where('slug', 'starter')->firstOrFail();
401-
$this->account->update(['plan_id' => $starter->id]);
357+
$this->account->update(['plan_id' => $this->plan->id]);
402358

403359
Bus::fake([TrackBilling::class]);
404360

@@ -409,7 +365,7 @@
409365

410366
Bus::assertDispatched(
411367
TrackBilling::class,
412-
fn ($job) => $job->event === BillingEvent::Cancelled && $job->previousPlan === $starter->name,
368+
fn ($job) => $job->event === BillingEvent::Cancelled && $job->previousPlan === $this->plan->name,
413369
);
414370
});
415371

@@ -422,7 +378,7 @@
422378
'type' => 'customer.subscription.created',
423379
'data' => ['object' => [
424380
'customer' => 'cus_test123',
425-
'items' => ['data' => [['price' => ['id' => 'price_starter_monthly']]]],
381+
'items' => ['data' => [['price' => ['id' => 'price_workspace_monthly']]]],
426382
]],
427383
]));
428384

@@ -431,51 +387,3 @@
431387
fn ($job) => $job->event === BillingEvent::Created && $job->previousPlan === null,
432388
);
433389
});
434-
435-
// ========================================
436-
// Plan mapping by Stripe price id
437-
// ========================================
438-
439-
test('subscription updated maps the plan by price id', function () {
440-
$starter = Plan::query()->where('slug', 'starter')->firstOrFail();
441-
$pro = Plan::query()->where('slug', 'pro')->firstOrFail();
442-
443-
$this->account->update(['plan_id' => $starter->id]);
444-
445-
$this->listener->handle(new WebhookReceived([
446-
'type' => 'customer.subscription.updated',
447-
'data' => ['object' => [
448-
'customer' => 'cus_test123',
449-
'items' => ['data' => [['price' => ['id' => 'price_pro_monthly']]]],
450-
]],
451-
]));
452-
453-
expect($this->account->fresh()->plan_id)->toBe($pro->id);
454-
});
455-
456-
test('subscription updated maps an archived legacy plan by price id', function () {
457-
$pro = Plan::query()->where('slug', 'pro')->firstOrFail();
458-
$pro->update(['is_archived' => true]);
459-
460-
$this->listener->handle(new WebhookReceived([
461-
'type' => 'customer.subscription.updated',
462-
'data' => ['object' => [
463-
'customer' => 'cus_test123',
464-
'items' => ['data' => [['price' => ['id' => 'price_pro_monthly']]]],
465-
]],
466-
]));
467-
468-
expect($this->account->fresh()->plan_id)->toBe($pro->id);
469-
});
470-
471-
test('subscription deleted clears the plan_id', function () {
472-
$starter = Plan::query()->where('slug', 'starter')->firstOrFail();
473-
$this->account->update(['plan_id' => $starter->id]);
474-
475-
$this->listener->handle(new WebhookReceived([
476-
'type' => 'customer.subscription.deleted',
477-
'data' => ['object' => ['customer' => 'cus_test123']],
478-
]));
479-
480-
expect($this->account->fresh()->plan_id)->toBeNull();
481-
});

tests/Feature/PlanTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@
66
use App\Models\Plan;
77

88
test('plan can be created with factory', function () {
9-
Plan::where('slug', Slug::Pro)->delete();
9+
Plan::where('slug', Slug::Workspace)->delete();
1010

1111
$plan = Plan::factory()->create([
12-
'slug' => Slug::Pro,
13-
'name' => 'Pro',
12+
'slug' => Slug::Workspace,
13+
'name' => 'Workspace',
1414
]);
1515

1616
expect($plan)->toBeInstanceOf(Plan::class)
17-
->and($plan->slug)->toBe(Slug::Pro)
18-
->and($plan->name)->toBe('Pro')
17+
->and($plan->slug)->toBe(Slug::Workspace)
18+
->and($plan->name)->toBe('Workspace')
1919
->and($plan->is_archived)->toBeFalse();
2020
});
2121

tests/Unit/Models/AccountTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@
174174
$account = Account::factory()->create([
175175
'trial_ends_at' => null,
176176
'stripe_id' => 'cus_test_'.fake()->uuid(),
177-
'plan_id' => Plan::where('slug', Slug::Starter)->value('id'),
177+
'plan_id' => Plan::where('slug', Slug::Workspace)->value('id'),
178178
]);
179179
$account->subscriptions()->create([
180180
'type' => Account::SUBSCRIPTION_NAME,

tests/Unit/PostHogServiceTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
Queue::fake();
8787
config(['services.posthog.enabled' => true, 'services.posthog.api_key' => 'phc_test_key']);
8888

89-
$plan = Plan::query()->where('slug', 'starter')->first();
89+
$plan = Plan::query()->where('slug', 'workspace')->first();
9090
$account = Account::factory()->create(['plan_id' => $plan?->id]);
9191

9292
$service = new PostHogService;

0 commit comments

Comments
 (0)