withoutMiddleware(ConditionalValidateCsrfToken::class); $this->configureBilling(); } public function test_success_page_does_not_grant_access_by_itself(): void { $prompt = AcademyPromptTemplate::query()->create([ 'title' => 'Creator Prompt', 'slug' => 'billing-success-does-not-unlock', 'excerpt' => 'Locked creator prompt.', 'prompt' => 'SECRET CREATOR PROMPT', 'difficulty' => 'beginner', 'access_level' => 'creator', 'active' => true, 'published_at' => now()->subMinute(), ]); $user = User::factory()->create(['email_verified_at' => now()]); $this->actingAs($user) ->get(route('academy.billing.success', ['session_id' => 'cs_test_only'])) ->assertOk() ->assertInertia(fn (AssertableInertia $page) => $page ->where('currentTier', 'free') ->where('isSubscribed', false)); $this->actingAs($user) ->get(route('academy.prompts.show', ['slug' => $prompt->slug])) ->assertOk() ->assertDontSee('SECRET CREATOR PROMPT') ->assertInertia(fn (AssertableInertia $page) => $page ->where('item.locked', true) ->where('item.prompt', null)); } public function test_canceled_subscription_on_grace_period_still_has_access(): void { $lesson = AcademyLesson::query()->create([ 'title' => 'Creator Grace Lesson', 'slug' => 'creator-grace-lesson', 'excerpt' => 'Should remain available in grace period.', 'content' => 'VISIBLE DURING GRACE PERIOD', 'difficulty' => 'beginner', 'access_level' => 'creator', 'lesson_type' => 'article', 'active' => true, 'published_at' => now()->subMinute(), ]); $user = User::factory()->create(['email_verified_at' => now()]); $this->attachSubscription($user, 'sub_grace', 'price_creator_month', now()->addDay()); $this->actingAs($user) ->get(route('academy.lessons.show', ['slug' => $lesson->slug])) ->assertOk() ->assertSee('VISIBLE DURING GRACE PERIOD') ->assertInertia(fn (AssertableInertia $page) => $page ->where('item.locked', false) ->where('item.content', 'VISIBLE DURING GRACE PERIOD')); } public function test_ended_subscription_loses_paid_access(): void { $lesson = AcademyLesson::query()->create([ 'title' => 'Creator Ended Lesson', 'slug' => 'creator-ended-lesson', 'excerpt' => 'Should lock after grace period.', 'content' => 'NO LONGER VISIBLE', 'difficulty' => 'beginner', 'access_level' => 'creator', 'lesson_type' => 'article', 'active' => true, 'published_at' => now()->subMinute(), ]); $user = User::factory()->create(['email_verified_at' => now()]); $this->attachSubscription($user, 'sub_ended', 'price_creator_month', now()->subMinute()); $this->actingAs($user) ->get(route('academy.lessons.show', ['slug' => $lesson->slug])) ->assertOk() ->assertDontSee('NO LONGER VISIBLE') ->assertInertia(fn (AssertableInertia $page) => $page ->where('item.locked', true) ->where('item.content', null)); } public function test_billing_portal_route_requires_authentication(): void { $this->get(route('academy.billing.portal')) ->assertRedirect(route('login')); } private function attachSubscription(User $user, string $subscriptionId, string $priceId, ?\Illuminate\Support\Carbon $endsAt = null): Subscription { $subscription = $user->subscriptions()->create([ 'type' => 'academy', 'stripe_id' => $subscriptionId, 'stripe_status' => 'active', 'stripe_price' => $priceId, 'quantity' => 1, 'ends_at' => $endsAt, ]); $subscription->items()->create([ 'stripe_id' => 'si_'.$subscriptionId, 'stripe_product' => 'prod_'.($priceId === 'price_pro_month' ? 'pro' : 'creator'), 'stripe_price' => $priceId, 'quantity' => 1, ]); return $subscription; } private function configureBilling(): void { config()->set('academy.enabled', true); config()->set('academy.payments_enabled', true); config()->set('academy_billing.enabled', true); config()->set('academy_billing.subscription_name', 'academy'); config()->set('academy_billing.plans', [ 'creator_monthly' => [ 'label' => 'Creator Monthly', 'tier' => 'creator', 'interval' => 'monthly', 'stripe_price_id' => 'price_creator_month', 'featured' => false, ], 'pro_monthly' => [ 'label' => 'Pro Monthly', 'tier' => 'pro', 'interval' => 'monthly', 'stripe_price_id' => 'price_pro_month', 'featured' => true, ], ]); } }