Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/Livewire/MobilePricing.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ public function createCheckoutSession(?string $plan, ?User $user = null)
return;
}

if ($this->hasActivePaidSubscription($user)) {
Log::warning('Blocked checkout session for a user who already has an active paid subscription.', [
'user_id' => $user->id,
]);

$message = 'This account already has an active subscription. Manage it from your dashboard.';

return Auth::id() === $user->id
? redirect(route('dashboard'))->with('message', $message)
: redirect(route('login'))->with('message', $message);
}

// Cancel any comped subscription before creating a new paid one
if ($user->hasCompedSubscription()) {
$user->subscription('default')->cancelNow();
Expand Down Expand Up @@ -220,6 +232,16 @@ public function upgradeSubscription(): mixed
return redirect(route('dashboard'))->with('success', 'Your subscription has been upgraded to Ultra!');
}

/**
* Comped subscriptions are replaced at checkout, so only paid ones block a new checkout.
*/
private function hasActivePaidSubscription(User $user): bool
{
$subscription = $user->subscription('default');

return $subscription && $subscription->active() && ! $user->hasCompedSubscription();
}

private function findOrCreateUser(string $email): User
{
Validator::validate(['email' => $email], [
Expand Down
58 changes: 58 additions & 0 deletions tests/Feature/MobilePricingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -629,4 +629,62 @@ public function preview_upgrade_sets_preview_to_null_when_stripe_has_no_upcoming
->assertSet('upgradePreview', null)
->assertOk();
}

#[Test]
public function ultra_subscriber_cannot_start_a_new_checkout_session()
{
$user = $this->createSubscribedUser(self::MAX_PRICE_ID);
Auth::login($user);

Livewire::test(MobilePricing::class)
->call('createCheckoutSession', 'max')
->assertRedirect(route('dashboard'));

$this->assertSame(1, $user->subscriptions()->count());
}

#[Test]
public function guest_cannot_start_a_checkout_session_for_an_email_with_an_active_subscription()
{
$user = $this->createSubscribedUser(self::MAX_PRICE_ID);

Livewire::test(MobilePricing::class)
->call('handlePurchaseRequest', [
'email' => $user->email,
'plan' => 'max',
])
->assertRedirect(route('login'));

$this->assertSame(1, $user->subscriptions()->count());
}

#[Test]
public function pro_subscriber_cannot_bypass_the_upgrade_flow_with_a_new_checkout_session()
{
$user = $this->createSubscribedUser(self::PRO_PRICE_ID);
Auth::login($user);

Livewire::test(MobilePricing::class)
->call('createCheckoutSession', 'max')
->assertRedirect(route('dashboard'));
}

private function createSubscribedUser(string $priceId): User
{
$user = User::factory()->create(['stripe_id' => 'cus_'.uniqid()]);

$subscription = Cashier::$subscriptionModel::factory()
->for($user)
->active()
->create([
'stripe_price' => $priceId,
'is_comped' => false,
]);

Cashier::$subscriptionItemModel::factory()
->for($subscription, 'subscription')
->create(['stripe_price' => $priceId]);

return $user;
}
}
Loading