withoutMiddleware(EnsurePlatformSession::class); config(['billing.api_url' => 'https://billing.test']); $this->owner = User::create([ 'public_id' => 'queue-owner-001', 'name' => 'Owner', 'email' => 'owner@example.com', 'password' => bcrypt('password'), ]); $this->organization = app(OrganizationResolver::class)->completeOnboarding($this->owner, [ 'organization_name' => 'Queue Org', 'industry' => 'retail', 'appointment_mode' => 'hybrid', 'branch_name' => 'Main', 'timezone' => 'UTC', ]); } public function test_pro_page_renders_three_tier_pricing(): void { $proPerBranch = number_format((int) config('qms.plans.pro.price_minor_per_branch') / 100, 0); $enterprisePerBranch = number_format((int) config('qms.plans.enterprise.price_minor_per_branch') / 100, 0); $this->actingAs($this->owner) ->get(route('qms.pro.index')) ->assertOk() ->assertSee('Choose your Queue plan') ->assertSee('GHS '.$proPerBranch) ->assertSee('GHS '.$enterprisePerBranch) ->assertSee('/branch/mo') ->assertSee('Branches') ->assertSee('Minimum is your current active branch count') ->assertSee('Upgrade to Pro') ->assertSee('Enterprise'); } public function test_sidebar_shows_upgrade_to_pro_on_dashboard(): void { $this->actingAs($this->owner) ->get(route('qms.dashboard')) ->assertOk() ->assertSee('Upgrade to Pro') ->assertSee('Unlock Queue Pro or Enterprise') ->assertSee('View plans'); } public function test_sidebar_hides_upgrade_for_operator_without_billing_access(): void { $operator = User::create([ 'public_id' => 'queue-operator-001', 'name' => 'Operator', 'email' => 'operator@example.com', 'password' => bcrypt('password'), ]); Member::create([ 'owner_ref' => $this->organization->owner_ref, 'organization_id' => $this->organization->id, 'user_ref' => $operator->public_id, 'role' => 'queue_operator', ]); $this->actingAs($operator); \App\Support\StaffUx::remember([ 'full_access' => false, 'apps' => ['queue'], 'show_hub' => false, 'show_billing' => false, ]); $this->get(route('qms.dashboard')) ->assertOk() ->assertDontSee('Upgrade to Pro') ->assertDontSee('Unlock Queue Pro or Enterprise') ->assertDontSee('View plans') ->assertSee('Report Issue') ->assertDontSee(route('qms.settings.edit')); } public function test_subscribe_upgrades_organization_to_pro(): void { Http::fake([ 'billing.test/can-afford*' => Http::response(['affordable' => true]), 'billing.test/debit' => Http::response(['ok' => true]), ]); $this->actingAs($this->owner) ->post(route('qms.pro.subscribe'), ['branches' => 1]) ->assertRedirect(route('qms.pro.index')) ->assertSessionHas('success'); $settings = $this->organization->fresh()->settings; $this->assertSame('pro', $settings['plan']); $this->assertSame(1, $settings['pro_billed_branches']); } public function test_subscribe_pro_uses_selected_branch_count(): void { Http::fake([ 'billing.test/can-afford*' => Http::response(['affordable' => true]), 'billing.test/debit' => function ($request) { $expected = 3 * (int) config('qms.plans.pro.price_minor_per_branch'); $this->assertSame($expected, (int) $request['amount_minor']); return Http::response(['ok' => true]); }, ]); $this->actingAs($this->owner) ->post(route('qms.pro.subscribe'), ['branches' => 3]) ->assertRedirect(route('qms.pro.index')) ->assertSessionHas('success'); $this->assertSame(3, $this->organization->fresh()->settings['pro_billed_branches']); } public function test_subscribe_enterprise_requires_multiple_branches(): void { Http::fake([ 'billing.test/can-afford*' => Http::response(['affordable' => true]), 'billing.test/debit' => Http::response(['ok' => true]), ]); $this->actingAs($this->owner) ->post(route('qms.pro.subscribe-enterprise'), ['branches' => 1]) ->assertRedirect(route('qms.pro.index')) ->assertSessionHas('error'); Branch::create([ 'owner_ref' => $this->organization->owner_ref, 'organization_id' => $this->organization->id, 'name' => 'Second', 'is_active' => true, ]); $this->actingAs($this->owner) ->post(route('qms.pro.subscribe-enterprise'), ['branches' => 2]) ->assertRedirect(route('qms.pro.index')) ->assertSessionHas('success'); $settings = $this->organization->fresh()->settings; $this->assertSame('enterprise', $settings['plan']); $this->assertSame(2, $settings['enterprise_billed_branches']); } public function test_pro_renew_extends_subscription_when_wallet_charges(): void { $this->organization->update([ 'settings' => array_merge($this->organization->settings ?? [], [ 'plan' => 'pro', 'auto_renew' => true, 'pro_billed_branches' => 1, 'plan_expires_at' => now()->subDay()->toIso8601String(), ]), ]); $expected = (int) config('qms.plans.pro.price_minor_per_branch'); Http::fake([ 'billing.test/debit' => function ($request) use ($expected) { $this->assertSame($expected, (int) $request['amount_minor']); return Http::response(['ok' => true]); }, ]); $this->artisan('qms:pro-renew')->assertSuccessful(); $settings = $this->organization->fresh()->settings; $this->assertSame('pro', $settings['plan']); $this->assertSame(1, $settings['pro_billed_branches']); $this->assertTrue(now()->parse($settings['plan_expires_at'])->isFuture()); } public function test_pro_renew_charges_per_active_branch(): void { Branch::create([ 'owner_ref' => $this->organization->owner_ref, 'organization_id' => $this->organization->id, 'name' => 'Second', 'is_active' => true, ]); $this->organization->update([ 'settings' => array_merge($this->organization->settings ?? [], [ 'plan' => 'pro', 'auto_renew' => true, 'pro_billed_branches' => 1, 'plan_expires_at' => now()->subDay()->toIso8601String(), ]), ]); $expected = 2 * (int) config('qms.plans.pro.price_minor_per_branch'); Http::fake([ 'billing.test/debit' => function ($request) use ($expected) { $this->assertSame($expected, (int) $request['amount_minor']); return Http::response(['ok' => true]); }, ]); $this->artisan('qms:pro-renew')->assertSuccessful(); $settings = $this->organization->fresh()->settings; $this->assertSame('pro', $settings['plan']); $this->assertSame(2, $settings['pro_billed_branches']); } public function test_enterprise_renew_charges_per_active_branch(): void { Branch::create([ 'owner_ref' => $this->organization->owner_ref, 'organization_id' => $this->organization->id, 'name' => 'Second', 'is_active' => true, ]); $this->organization->update([ 'settings' => array_merge($this->organization->settings ?? [], [ 'plan' => 'enterprise', 'auto_renew' => true, 'enterprise_billed_branches' => 2, 'plan_expires_at' => now()->subDay()->toIso8601String(), ]), ]); $expected = 2 * (int) config('qms.plans.enterprise.price_minor_per_branch'); Http::fake([ 'billing.test/debit' => function ($request) use ($expected) { $this->assertSame($expected, (int) $request['amount_minor']); return Http::response(['ok' => true]); }, ]); $this->artisan('qms:pro-renew')->assertSuccessful(); $settings = $this->organization->fresh()->settings; $this->assertSame('enterprise', $settings['plan']); $this->assertSame(2, $settings['enterprise_billed_branches']); } public function test_prepaid_checkout_redirects_to_paystack(): void { Http::fake([ 'billing.test/plan-checkout' => Http::response([ 'checkout_url' => 'https://checkout.paystack.test/pay', 'reference' => 'SAP-QUEUE-TEST', ], 201), ]); $this->actingAs($this->owner) ->post(route('qms.pro.subscribe-prepaid'), [ 'plan' => 'pro', 'months' => 12, 'branches' => 1, ]) ->assertRedirect('https://checkout.paystack.test/pay'); } }