create([ 'legacy_portal' => 'businessportal24', 'legacy_id' => fake()->unique()->numberBetween(1, 1_000_000), 'user_id' => $user->id, 'legacy_user_id' => $user->id, 'number' => 'LEG-'.fake()->unique()->numberBetween(1, 1_000_000), 'total_cents' => 4900, 'status' => $status, 'invoice_date' => $invoiceDate, 'paid_at' => $status === 'paid' ? $invoiceDate : null, 'imported_at' => now(), ]); } test('latestLegacyInvoice returns newest by invoice_date then id', function () { $user = User::factory()->create(); makeLegacyInvoice($user, 'paid', '2024-01-01'); $newest = makeLegacyInvoice($user, 'reminder', '2024-06-01'); makeLegacyInvoice($user, 'paid', '2024-03-01'); expect($user->latestLegacyInvoice()->id)->toBe($newest->id); }); test('token gate grants access when latest legacy invoice is paid', function () { $user = User::factory()->create(['is_active' => true]); makeLegacyInvoice($user, 'paid', '2024-01-01'); expect((new ApiAccessEligibilityService)->canCreateToken($user))->toBeTrue(); }); test('token gate denies when latest legacy invoice is unpaid even if an older one was paid', function () { $user = User::factory()->create(['is_active' => true]); makeLegacyInvoice($user, 'paid', '2024-01-01'); makeLegacyInvoice($user, 'reminder', '2024-06-01'); expect((new ApiAccessEligibilityService)->canCreateToken($user))->toBeFalse(); }); test('token gate grants access for an active stripe subscription', function () { $user = User::factory()->create(['is_active' => true]); $user->subscriptions()->create([ 'type' => 'default', 'stripe_id' => 'sub_test_api_gate', 'stripe_status' => 'active', 'stripe_price' => 'price_starter', 'quantity' => 1, ]); expect((new ApiAccessEligibilityService)->canCreateToken($user))->toBeTrue(); }); test('token gate denies for a stripe subscription that has ended', function () { $user = User::factory()->create(['is_active' => true]); $user->subscriptions()->create([ 'type' => 'default', 'stripe_id' => 'sub_test_api_gate_ended', 'stripe_status' => 'canceled', 'stripe_price' => 'price_starter', 'quantity' => 1, 'ends_at' => now()->subDay(), ]); expect((new ApiAccessEligibilityService)->canCreateToken($user))->toBeFalse(); }); test('token gate denies when there is no legacy invoice and no active payment option', function () { $user = User::factory()->create(['is_active' => true]); expect((new ApiAccessEligibilityService)->canCreateToken($user))->toBeFalse(); }); test('hasActivePaymentOption is period-aware for active options', function () { $user = User::factory()->create(['is_active' => true]); UserPaymentOption::factory()->for($user)->create([ 'status' => UserPaymentOptionStatus::Active->value, 'current_period_start' => now()->subDay(), 'current_period_end' => now()->addDay(), ]); expect($user->hasActivePaymentOption())->toBeTrue(); $user->userPaymentOptions()->update([ 'current_period_start' => now()->subMonths(2), 'current_period_end' => now()->subMonth(), ]); expect($user->fresh()->hasActivePaymentOption())->toBeFalse(); }); test('hasActivePaymentOption is period-aware for grandfathering', function () { $user = User::factory()->create(['is_active' => true]); $option = UserPaymentOption::factory()->for($user)->create([ 'status' => UserPaymentOptionStatus::Grandfathered->value, 'grandfathered_until' => now()->addMonth(), ]); expect($user->hasActivePaymentOption())->toBeTrue(); $option->update(['grandfathered_until' => now()->subDay()]); expect($user->fresh()->hasActivePaymentOption())->toBeFalse(); }); test('booking and quota gates are period-aware when enforcement is on', function () { config()->set('billing.enforce_booking', true); $user = User::factory()->create(['is_active' => true]); UserPaymentOption::factory()->for($user)->create([ 'status' => UserPaymentOptionStatus::Active->value, 'current_period_start' => now()->subMonths(2), 'current_period_end' => now()->subMonth(), ]); expect($user->hasActiveBooking())->toBeFalse() ->and($user->hasUnlimitedPressReleaseQuota())->toBeFalse(); });