diff --git a/app/Actions/User/CreateUser.php b/app/Actions/User/CreateUser.php index e7001e12..ae81ae07 100644 --- a/app/Actions/User/CreateUser.php +++ b/app/Actions/User/CreateUser.php @@ -4,10 +4,8 @@ namespace App\Actions\User; -use App\Enums\Plan\Slug; use App\Jobs\PostHog\SyncUser; use App\Models\Account; -use App\Models\Plan; use App\Models\User; use App\Services\PostHogService; use Illuminate\Support\Facades\DB; @@ -26,8 +24,6 @@ public static function execute(array $data, array $utmParameters = []): User $account = Account::create([ 'name' => data_get($data, 'name')."'s Account", 'billing_email' => data_get($data, 'email'), - 'plan_id' => Plan::where('slug', Slug::Starter)->value('id'), - 'trial_ends_at' => now()->addDays(config('cashier.trial_days')), ]); $user = User::create(array_merge([ diff --git a/app/Http/Controllers/App/BillingController.php b/app/Http/Controllers/App/BillingController.php index 2bbb3ad4..92e00744 100644 --- a/app/Http/Controllers/App/BillingController.php +++ b/app/Http/Controllers/App/BillingController.php @@ -30,6 +30,7 @@ public function subscribe(Request $request): Response|RedirectResponse return Inertia::render('billing/Subscribe', [ 'plans' => Plan::active()->orderBy('sort')->get(), + 'trialDays' => config('cashier.trial_days'), ]); } @@ -62,7 +63,8 @@ public function checkout(Request $request, Plan $plan): SymfonyResponse|Redirect ]); $subscription = $account->newSubscription(Account::SUBSCRIPTION_NAME, $priceId) - ->allowPromotionCodes(); + ->allowPromotionCodes() + ->trialDays(config('cashier.trial_days')); $checkoutSession = $subscription->checkout([ 'success_url' => route('app.billing.processing').'?session_id={CHECKOUT_SESSION_ID}', diff --git a/app/Http/Middleware/App/EnsureAccountReady.php b/app/Http/Middleware/App/EnsureAccountReady.php index 4123ee71..cdb8d4b9 100644 --- a/app/Http/Middleware/App/EnsureAccountReady.php +++ b/app/Http/Middleware/App/EnsureAccountReady.php @@ -24,15 +24,8 @@ public function handle(Request $request, Closure $next): Response $account = $user->account; - if (! config('trypost.self_hosted')) { - $hasAccess = $account && ( - $account->subscribed(Account::SUBSCRIPTION_NAME) - || $account->isOnTrial() - ); - - if (! $hasAccess) { - return redirect()->route('app.subscribe'); - } + if (! config('trypost.self_hosted') && (! $account || ! $account->subscribed(Account::SUBSCRIPTION_NAME))) { + return redirect()->route('app.subscribe'); } if (! $user->workspaces()->exists()) { diff --git a/app/Models/Account.php b/app/Models/Account.php index 632b9682..13716450 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -93,10 +93,6 @@ public function hasActiveSubscription(): bool public function isOnTrial(): bool { - if ($this->onGenericTrial()) { - return true; - } - return (bool) $this->subscription(self::SUBSCRIPTION_NAME)?->onTrial(); } @@ -104,11 +100,11 @@ public function activeTrialEndsAt(): ?CarbonInterface { $subscription = $this->subscription(self::SUBSCRIPTION_NAME); - return match (true) { - (bool) $subscription?->onTrial() => $subscription->trial_ends_at, - $this->onGenericTrial() => $this->trial_ends_at, - default => null, - }; + if (! $subscription?->onTrial()) { + return null; + } + + return $subscription->trial_ends_at; } /** diff --git a/tests/Feature/Auth/TrialOnSignupTest.php b/tests/Feature/Auth/TrialOnSignupTest.php index f47434a0..e3d9066f 100644 --- a/tests/Feature/Auth/TrialOnSignupTest.php +++ b/tests/Feature/Auth/TrialOnSignupTest.php @@ -3,9 +3,6 @@ declare(strict_types=1); use App\Actions\User\CreateUser; -use App\Enums\Plan\Slug; -use App\Models\Plan; -use Carbon\Carbon; use Database\Seeders\PlanSeeder; beforeEach(function () { @@ -13,9 +10,7 @@ $this->seed(PlanSeeder::class); }); -test('new signup gets a 7-day trial without card', function () { - Carbon::setTestNow('2026-05-14 12:00:00'); - +test('new signup does not create a trial before checkout', function () { $user = CreateUser::execute([ 'name' => 'Alice', 'email' => 'alice@example.com', @@ -24,40 +19,7 @@ 'registration_ip' => '127.0.0.1', ]); - $starterPlan = Plan::where('slug', Slug::Starter)->firstOrFail(); - - expect($user->account->plan_id)->toBe($starterPlan->id); - expect($user->account->trial_ends_at?->toDateTimeString())->toBe('2026-05-21 12:00:00'); + expect($user->account->plan_id)->toBeNull(); + expect($user->account->trial_ends_at)->toBeNull(); expect($user->account->stripe_id)->toBeNull(); }); - -test('account during generic trial is recognized as on trial', function () { - Carbon::setTestNow('2026-05-14 12:00:00'); - - $user = CreateUser::execute([ - 'name' => 'Alice', - 'email' => 'alice2@example.com', - 'password' => 'password123', - 'timezone' => 'UTC', - 'registration_ip' => '127.0.0.1', - ]); - - expect($user->account->isOnTrial())->toBeTrue(); - expect($user->account->onGenericTrial())->toBeTrue(); -}); - -test('account whose generic trial expired is not on trial', function () { - Carbon::setTestNow('2026-05-14 12:00:00'); - - $user = CreateUser::execute([ - 'name' => 'Alice', - 'email' => 'alice3@example.com', - 'password' => 'password123', - 'timezone' => 'UTC', - 'registration_ip' => '127.0.0.1', - ]); - - Carbon::setTestNow('2026-05-21 12:01:00'); - - expect($user->account->fresh()->isOnTrial())->toBeFalse(); -}); diff --git a/tests/Feature/BillingControllerTest.php b/tests/Feature/BillingControllerTest.php index ebb212ad..efe90d7b 100644 --- a/tests/Feature/BillingControllerTest.php +++ b/tests/Feature/BillingControllerTest.php @@ -92,22 +92,6 @@ ); }); -test('billing index exposes onTrial=true and trialEndsAt for generic-trial-only account', function () { - config(['trypost.self_hosted' => false]); - - $endsAt = now()->addDays(7)->startOfSecond(); - $this->account->update(['trial_ends_at' => $endsAt]); - - $response = $this->actingAs($this->user->fresh())->get(route('app.billing.index')); - - $response->assertInertia(fn ($page) => $page - ->component('settings/account/Billing', false) - ->where('hasSubscription', false) - ->where('onTrial', true) - ->where('trialEndsAt', $endsAt->toIso8601ZuluString('microsecond')) - ); -}); - test('billing index exposes onTrial=true and trialEndsAt for subscription-trial account', function () { config(['trypost.self_hosted' => false]); @@ -148,14 +132,14 @@ ); }); -test('subscribe page does not expose trialDays prop anymore', function () { +test('subscribe page exposes trialDays prop', function () { config(['trypost.self_hosted' => false]); $response = $this->actingAs($this->user)->get(route('app.subscribe')); $response->assertInertia(fn ($page) => $page ->component('billing/Subscribe', false) - ->missing('trialDays') + ->where('trialDays', config('cashier.trial_days')) ); }); diff --git a/tests/Feature/Middleware/TrialMiddlewareAccessTest.php b/tests/Feature/Middleware/TrialMiddlewareAccessTest.php index 3c80d220..1723f806 100644 --- a/tests/Feature/Middleware/TrialMiddlewareAccessTest.php +++ b/tests/Feature/Middleware/TrialMiddlewareAccessTest.php @@ -7,7 +7,6 @@ use App\Models\Account; use App\Models\User; use App\Models\Workspace; -use Carbon\Carbon; use Database\Seeders\PlanSeeder; beforeEach(function () { @@ -15,9 +14,7 @@ $this->seed(PlanSeeder::class); }); -test('user on generic trial can access the app', function () { - Carbon::setTestNow('2026-05-14 12:00:00'); - +test('user without subscription is redirected to subscribe', function () { $user = CreateUser::execute([ 'name' => 'Alice', 'email' => 'alice@example.com', @@ -35,12 +32,10 @@ $response = $this->actingAs($user->fresh())->get(route('app.accounts')); - $response->assertOk(); + $response->assertRedirect(route('app.subscribe')); }); -test('user whose trial expired is redirected to subscribe', function () { - Carbon::setTestNow('2026-05-14 12:00:00'); - +test('user with active subscription can access the app', function () { $user = CreateUser::execute([ 'name' => 'Alice', 'email' => 'alice2@example.com', @@ -56,11 +51,16 @@ $workspace->members()->attach($user->id, ['role' => Role::Member->value]); $user->update(['current_workspace_id' => $workspace->id]); - Carbon::setTestNow('2026-05-21 12:01:00'); + $user->account->subscriptions()->create([ + 'type' => Account::SUBSCRIPTION_NAME, + 'stripe_id' => 'sub_test_'.fake()->uuid(), + 'stripe_status' => 'active', + 'stripe_price' => 'price_123', + ]); $response = $this->actingAs($user->fresh())->get(route('app.accounts')); - $response->assertRedirect(route('app.subscribe')); + $response->assertOk(); }); test('user on trialing subscription (legacy trial-with-card) can access the app', function () { diff --git a/tests/Unit/Models/AccountTest.php b/tests/Unit/Models/AccountTest.php index 84147a8c..a62a867c 100644 --- a/tests/Unit/Models/AccountTest.php +++ b/tests/Unit/Models/AccountTest.php @@ -13,18 +13,8 @@ Carbon::setTestNow('2026-05-14 12:00:00'); }); -test('isOnTrial returns true for account on generic trial', function () { - $account = Account::factory()->create([ - 'trial_ends_at' => now()->addDays(7), - ]); - - expect($account->isOnTrial())->toBeTrue(); -}); - -test('isOnTrial returns false when generic trial has expired and there is no subscription', function () { - $account = Account::factory()->create([ - 'trial_ends_at' => now()->subDay(), - ]); +test('isOnTrial ignores generic trial when there is no subscription', function () { + $account = Account::factory()->create(['trial_ends_at' => now()->addDays(7)]); expect($account->isOnTrial())->toBeFalse(); }); @@ -61,8 +51,7 @@ $endsAt = now()->addDays(7); $account = Account::factory()->create(['trial_ends_at' => $endsAt]); - expect($account->activeTrialEndsAt()?->toDateTimeString()) - ->toBe($endsAt->toDateTimeString()); + expect($account->activeTrialEndsAt())->toBeNull(); }); test('activeTrialEndsAt returns subscription date when only subscription trial is active', function () { @@ -83,7 +72,7 @@ ->toBe($subscriptionEndsAt->toDateTimeString()); }); -test('activeTrialEndsAt prefers subscription date over generic when both active', function () { +test('activeTrialEndsAt returns subscription date when both generic and subscription trials are present', function () { $genericEndsAt = now()->addDays(7); $subscriptionEndsAt = now()->addDays(14);