user = User::factory()->create(); $this->workspace = Workspace::factory()->create([ 'stripe_customer_id' => 'cus_test_123', ]); $this->workspace->users()->attach($this->user->id, [ 'role' => 'owner', 'is_default' => true, ]); // Mock the StripeGateway $this->mockStripe = Mockery::mock(StripeGateway::class); $this->service = new PaymentMethodService($this->mockStripe); }); afterEach(function () { Mockery::close(); }); describe('PaymentMethodService', function () { describe('getPaymentMethods()', function () { it('returns only active payment methods for a workspace', function () { PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_active_1', 'type' => 'card', 'brand' => 'visa', 'last_four' => '4242', 'is_active' => true, 'is_default' => true, ]); PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_active_2', 'type' => 'card', 'brand' => 'mastercard', 'last_four' => '5555', 'is_active' => true, 'is_default' => false, ]); PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_inactive', 'type' => 'card', 'brand' => 'amex', 'last_four' => '0001', 'is_active' => false, 'is_default' => false, ]); $methods = $this->service->getPaymentMethods($this->workspace); expect($methods)->toHaveCount(2) ->and($methods->pluck('gateway_payment_method_id')->toArray()) ->not->toContain('pm_inactive'); }); it('orders default payment method first', function () { PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_non_default', 'type' => 'card', 'is_active' => true, 'is_default' => false, ]); PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_default', 'type' => 'card', 'is_active' => true, 'is_default' => true, ]); $methods = $this->service->getPaymentMethods($this->workspace); expect($methods->first()->gateway_payment_method_id)->toBe('pm_default'); }); it('returns empty collection when no active methods exist', function () { $methods = $this->service->getPaymentMethods($this->workspace); expect($methods)->toBeEmpty(); }); }); describe('getDefaultPaymentMethod()', function () { it('returns the default active payment method', function () { PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_non_default', 'type' => 'card', 'is_active' => true, 'is_default' => false, ]); $default = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_default', 'type' => 'card', 'is_active' => true, 'is_default' => true, ]); $result = $this->service->getDefaultPaymentMethod($this->workspace); expect($result)->not->toBeNull() ->and($result->id)->toBe($default->id); }); it('returns null when no default method exists', function () { PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_no_default', 'type' => 'card', 'is_active' => true, 'is_default' => false, ]); $result = $this->service->getDefaultPaymentMethod($this->workspace); expect($result)->toBeNull(); }); it('ignores inactive default methods', function () { PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_inactive_default', 'type' => 'card', 'is_active' => false, 'is_default' => true, ]); $result = $this->service->getDefaultPaymentMethod($this->workspace); expect($result)->toBeNull(); }); }); describe('addPaymentMethod()', function () { it('adds a new Stripe payment method via gateway', function () { $createdMethod = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_new_123', 'type' => 'card', 'brand' => 'visa', 'last_four' => '4242', 'exp_month' => 12, 'exp_year' => 2028, 'is_default' => false, 'is_active' => true, ]); $this->mockStripe->shouldReceive('attachPaymentMethod') ->once() ->with(Mockery::on(fn ($ws) => $ws->id === $this->workspace->id), 'pm_new_123') ->andReturn($createdMethod); $this->mockStripe->shouldReceive('setDefaultPaymentMethod') ->once(); $result = $this->service->addPaymentMethod( $this->workspace, 'pm_new_123', $this->user ); expect($result)->toBeInstanceOf(PaymentMethod::class) ->and($result->gateway_payment_method_id)->toBe('pm_new_123') ->and($result->brand)->toBe('visa') ->and($result->last_four)->toBe('4242'); }); it('reactivates an existing inactive payment method', function () { $existing = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_existing_123', 'type' => 'card', 'brand' => 'visa', 'last_four' => '4242', 'is_active' => false, 'is_default' => false, ]); $result = $this->service->addPaymentMethod( $this->workspace, 'pm_existing_123' ); expect($result->id)->toBe($existing->id) ->and($result->fresh()->is_active)->toBeTrue(); }); it('returns existing active payment method without duplicating', function () { $existing = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_already_active', 'type' => 'card', 'brand' => 'mastercard', 'last_four' => '5555', 'is_active' => true, 'is_default' => false, ]); $result = $this->service->addPaymentMethod( $this->workspace, 'pm_already_active' ); expect($result->id)->toBe($existing->id); expect(PaymentMethod::where('gateway_payment_method_id', 'pm_already_active')->count())->toBe(1); }); it('sets first payment method as default automatically', function () { $createdMethod = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_first', 'type' => 'card', 'brand' => 'visa', 'last_four' => '1234', 'is_default' => false, 'is_active' => true, ]); $this->mockStripe->shouldReceive('attachPaymentMethod') ->once() ->andReturn($createdMethod); $this->mockStripe->shouldReceive('setDefaultPaymentMethod') ->once() ->with(Mockery::on(fn ($pm) => $pm->id === $createdMethod->id)); $result = $this->service->addPaymentMethod( $this->workspace, 'pm_first' ); expect($result->id)->toBe($createdMethod->id); }); it('does not override default when other methods exist', function () { // Pre-existing active method PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_existing_default', 'type' => 'card', 'is_active' => true, 'is_default' => true, ]); $newMethod = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_second', 'type' => 'card', 'brand' => 'amex', 'last_four' => '0005', 'is_default' => false, 'is_active' => true, ]); $this->mockStripe->shouldReceive('attachPaymentMethod') ->once() ->andReturn($newMethod); // setDefaultPaymentMethod should NOT be called $this->mockStripe->shouldNotReceive('setDefaultPaymentMethod'); $this->service->addPaymentMethod($this->workspace, 'pm_second'); }); it('assigns user to payment method when provided', function () { $createdMethod = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_with_user', 'type' => 'card', 'is_default' => false, 'is_active' => true, ]); $this->mockStripe->shouldReceive('attachPaymentMethod') ->once() ->andReturn($createdMethod); $this->mockStripe->shouldReceive('setDefaultPaymentMethod') ->once(); $result = $this->service->addPaymentMethod( $this->workspace, 'pm_with_user', $this->user ); expect($result->fresh()->user_id)->toBe($this->user->id); }); it('throws exception for unsupported gateway', function () { expect(fn () => $this->service->addPaymentMethod( $this->workspace, 'btc_method_123', null, 'unsupported_gateway' ))->toThrow(InvalidArgumentException::class, 'Unsupported payment gateway'); }); }); describe('removePaymentMethod()', function () { it('deactivates a payment method', function () { $method = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_to_remove', 'type' => 'card', 'is_active' => true, 'is_default' => false, ]); $this->mockStripe->shouldReceive('detachPaymentMethod') ->once() ->with(Mockery::on(fn ($pm) => $pm->id === $method->id)); $this->service->removePaymentMethod($this->workspace, $method); expect($method->fresh()->is_active)->toBeFalse(); }); it('throws exception when method does not belong to workspace', function () { $otherWorkspace = Workspace::factory()->create(); $method = PaymentMethod::create([ 'workspace_id' => $otherWorkspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_other_ws', 'type' => 'card', 'is_active' => true, 'is_default' => false, ]); expect(fn () => $this->service->removePaymentMethod($this->workspace, $method)) ->toThrow(RuntimeException::class, 'does not belong to this workspace'); }); it('throws exception when removing last method with active subscription', function () { $package = Package::where('code', 'creator')->first(); $workspacePackage = WorkspacePackage::create([ 'workspace_id' => $this->workspace->id, 'package_id' => $package->id, 'status' => 'active', ]); Subscription::create([ 'workspace_id' => $this->workspace->id, 'workspace_package_id' => $workspacePackage->id, 'status' => 'active', 'gateway' => 'stripe', 'billing_cycle' => 'monthly', 'current_period_start' => now(), 'current_period_end' => now()->addDays(30), ]); $method = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_last_one', 'type' => 'card', 'is_active' => true, 'is_default' => true, ]); expect(fn () => $this->service->removePaymentMethod($this->workspace, $method)) ->toThrow(RuntimeException::class, 'Cannot remove the last payment method'); }); it('allows removing last method when no active subscriptions', function () { $method = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_last_no_sub', 'type' => 'card', 'is_active' => true, 'is_default' => false, ]); $this->mockStripe->shouldReceive('detachPaymentMethod')->once(); $this->service->removePaymentMethod($this->workspace, $method); expect($method->fresh()->is_active)->toBeFalse(); }); it('promotes another method to default when removing the default', function () { $defaultMethod = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_default_remove', 'type' => 'card', 'is_active' => true, 'is_default' => true, ]); $otherMethod = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_becomes_default', 'type' => 'card', 'is_active' => true, 'is_default' => false, ]); $this->mockStripe->shouldReceive('detachPaymentMethod')->once(); $this->mockStripe->shouldReceive('setDefaultPaymentMethod') ->once() ->with(Mockery::on(fn ($pm) => $pm->id === $otherMethod->id)); $this->service->removePaymentMethod($this->workspace, $defaultMethod); expect($defaultMethod->fresh()->is_active)->toBeFalse() ->and($otherMethod->fresh()->is_default)->toBeTrue(); }); it('continues removal even if Stripe detach fails', function () { Log::shouldReceive('warning')->once(); Log::shouldReceive('info')->once(); $method = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_stripe_fail', 'type' => 'card', 'is_active' => true, 'is_default' => false, ]); $this->mockStripe->shouldReceive('detachPaymentMethod') ->once() ->andThrow(new Exception('Stripe API error')); $this->service->removePaymentMethod($this->workspace, $method); expect($method->fresh()->is_active)->toBeFalse(); }); }); describe('setDefaultPaymentMethod()', function () { it('sets a payment method as default', function () { $method1 = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_old_default', 'type' => 'card', 'is_active' => true, 'is_default' => true, ]); $method2 = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_new_default', 'type' => 'card', 'is_active' => true, 'is_default' => false, ]); $this->mockStripe->shouldReceive('setDefaultPaymentMethod') ->once() ->with(Mockery::on(fn ($pm) => $pm->id === $method2->id)); $this->service->setDefaultPaymentMethod($this->workspace, $method2); expect($method1->fresh()->is_default)->toBeFalse() ->and($method2->fresh()->is_default)->toBeTrue(); }); it('throws exception when method does not belong to workspace', function () { $otherWorkspace = Workspace::factory()->create(); $method = PaymentMethod::create([ 'workspace_id' => $otherWorkspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_wrong_ws', 'type' => 'card', 'is_active' => true, 'is_default' => false, ]); expect(fn () => $this->service->setDefaultPaymentMethod($this->workspace, $method)) ->toThrow(RuntimeException::class, 'does not belong to this workspace'); }); it('clears default from all other methods in same workspace', function () { $method1 = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_was_default_1', 'type' => 'card', 'is_active' => true, 'is_default' => true, ]); $method2 = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_was_default_2', 'type' => 'card', 'is_active' => true, 'is_default' => true, ]); $method3 = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_make_default', 'type' => 'card', 'is_active' => true, 'is_default' => false, ]); $this->mockStripe->shouldReceive('setDefaultPaymentMethod')->once(); $this->service->setDefaultPaymentMethod($this->workspace, $method3); expect($method1->fresh()->is_default)->toBeFalse() ->and($method2->fresh()->is_default)->toBeFalse() ->and($method3->fresh()->is_default)->toBeTrue(); }); it('continues setting default locally even if Stripe call fails', function () { Log::shouldReceive('warning')->once(); Log::shouldReceive('info')->once(); $method = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_stripe_default_fail', 'type' => 'card', 'is_active' => true, 'is_default' => false, ]); $this->mockStripe->shouldReceive('setDefaultPaymentMethod') ->once() ->andThrow(new Exception('Stripe API error')); $this->service->setDefaultPaymentMethod($this->workspace, $method); expect($method->fresh()->is_default)->toBeTrue(); }); }); describe('syncPaymentMethodsFromStripe()', function () { it('returns empty collection when workspace has no Stripe customer ID', function () { $workspace = Workspace::factory()->create(['stripe_customer_id' => null]); $result = $this->service->syncPaymentMethodsFromStripe($workspace); expect($result)->toBeEmpty(); }); it('returns existing payment methods for workspace with Stripe customer', function () { PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_synced', 'type' => 'card', 'is_active' => true, 'is_default' => true, ]); $result = $this->service->syncPaymentMethodsFromStripe($this->workspace); expect($result)->toHaveCount(1) ->and($result->first()->gateway_payment_method_id)->toBe('pm_synced'); }); }); describe('isExpiringSoon()', function () { it('returns true when card expires within threshold', function () { $method = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_expiring', 'type' => 'card', 'exp_month' => (int) now()->addMonth()->format('m'), 'exp_year' => (int) now()->addMonth()->format('Y'), 'is_active' => true, 'is_default' => true, ]); expect($this->service->isExpiringSoon($method, 2))->toBeTrue(); }); it('returns false when card is not expiring soon', function () { $method = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_not_expiring', 'type' => 'card', 'exp_month' => 12, 'exp_year' => (int) now()->addYears(3)->format('Y'), 'is_active' => true, 'is_default' => true, ]); expect($this->service->isExpiringSoon($method))->toBeFalse(); }); it('returns false when expiry data is missing', function () { $method = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_no_expiry', 'type' => 'crypto_wallet', 'exp_month' => null, 'exp_year' => null, 'is_active' => true, 'is_default' => false, ]); expect($this->service->isExpiringSoon($method))->toBeFalse(); }); it('respects custom month threshold', function () { // Card expiring in 5 months $futureDate = now()->addMonths(5); $method = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_custom_threshold', 'type' => 'card', 'exp_month' => (int) $futureDate->format('m'), 'exp_year' => (int) $futureDate->format('Y'), 'is_active' => true, 'is_default' => false, ]); expect($this->service->isExpiringSoon($method, 2))->toBeFalse() ->and($this->service->isExpiringSoon($method, 6))->toBeTrue(); }); }); describe('updateFromGateway()', function () { it('updates card details from gateway data', function () { $method = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_update_card', 'type' => 'card', 'brand' => 'visa', 'last_four' => '4242', 'exp_month' => 1, 'exp_year' => 2025, 'is_active' => true, 'is_default' => true, ]); $result = $this->service->updateFromGateway($method, [ 'card' => [ 'brand' => 'visa', 'last4' => '4242', 'exp_month' => 6, 'exp_year' => 2029, ], ]); expect($result->exp_month)->toBe(6) ->and($result->exp_year)->toBe(2029) ->and($result->brand)->toBe('visa') ->and($result->last_four)->toBe('4242'); }); it('preserves existing values for missing gateway fields', function () { $method = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_partial_update', 'type' => 'card', 'brand' => 'mastercard', 'last_four' => '5555', 'exp_month' => 3, 'exp_year' => 2027, 'is_active' => true, 'is_default' => false, ]); $result = $this->service->updateFromGateway($method, [ 'card' => [ 'exp_month' => 9, 'exp_year' => 2028, ], ]); expect($result->brand)->toBe('mastercard') ->and($result->last_four)->toBe('5555') ->and($result->exp_month)->toBe(9) ->and($result->exp_year)->toBe(2028); }); it('does nothing when no card data is provided', function () { $method = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_no_card_data', 'type' => 'card', 'brand' => 'amex', 'last_four' => '0001', 'exp_month' => 12, 'exp_year' => 2026, 'is_active' => true, 'is_default' => false, ]); $result = $this->service->updateFromGateway($method, []); expect($result->brand)->toBe('amex') ->and($result->exp_month)->toBe(12) ->and($result->exp_year)->toBe(2026); }); }); describe('createSetupSession()', function () { it('creates a setup session via Stripe gateway', function () { $this->mockStripe->shouldReceive('isEnabled') ->once() ->andReturn(true); $this->mockStripe->shouldReceive('createSetupSession') ->once() ->with( Mockery::on(fn ($ws) => $ws->id === $this->workspace->id), 'https://example.com/return' ) ->andReturn([ 'session_id' => 'seti_test_123', 'setup_url' => 'https://checkout.stripe.com/setup/test', ]); $result = $this->service->createSetupSession( $this->workspace, 'https://example.com/return' ); expect($result)->toHaveKeys(['session_id', 'setup_url']) ->and($result['session_id'])->toBe('seti_test_123'); }); it('throws exception when Stripe is not enabled', function () { $this->mockStripe->shouldReceive('isEnabled') ->once() ->andReturn(false); expect(fn () => $this->service->createSetupSession( $this->workspace, 'https://example.com/return' ))->toThrow(RuntimeException::class, 'not currently available'); }); }); describe('getBillingPortalUrl()', function () { it('returns portal URL when Stripe is enabled', function () { $this->mockStripe->shouldReceive('isEnabled') ->once() ->andReturn(true); $this->mockStripe->shouldReceive('getPortalUrl') ->once() ->with( Mockery::on(fn ($ws) => $ws->id === $this->workspace->id), 'https://example.com/billing' ) ->andReturn('https://billing.stripe.com/session/test_portal'); $result = $this->service->getBillingPortalUrl( $this->workspace, 'https://example.com/billing' ); expect($result)->toBe('https://billing.stripe.com/session/test_portal'); }); it('returns null when Stripe is not enabled', function () { $this->mockStripe->shouldReceive('isEnabled') ->once() ->andReturn(false); $result = $this->service->getBillingPortalUrl( $this->workspace, 'https://example.com/billing' ); expect($result)->toBeNull(); }); }); }); describe('PaymentMethod model', function () { beforeEach(function () { $this->method = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_model_test', 'type' => 'card', 'brand' => 'visa', 'last_four' => '4242', 'exp_month' => 12, 'exp_year' => 2028, 'is_active' => true, 'is_default' => false, ]); }); it('identifies card type correctly', function () { expect($this->method->isCard())->toBeTrue() ->and($this->method->isCrypto())->toBeFalse() ->and($this->method->isBankAccount())->toBeFalse(); }); it('identifies crypto wallet type correctly', function () { $this->method->update(['type' => 'crypto_wallet']); expect($this->method->isCrypto())->toBeTrue() ->and($this->method->isCard())->toBeFalse(); }); it('identifies bank account type correctly', function () { $this->method->update(['type' => 'bank_account']); expect($this->method->isBankAccount())->toBeTrue() ->and($this->method->isCard())->toBeFalse(); }); it('detects expired cards', function () { $this->method->update([ 'exp_month' => 1, 'exp_year' => 2020, ]); expect($this->method->isExpired())->toBeTrue(); }); it('detects non-expired cards', function () { expect($this->method->isExpired())->toBeFalse(); }); it('returns false for expiry check when no expiry data', function () { $this->method->update([ 'exp_month' => null, 'exp_year' => null, ]); expect($this->method->isExpired())->toBeFalse(); }); it('generates display name for card', function () { expect($this->method->getDisplayName())->toBe('Visa **** 4242'); }); it('generates display name for crypto wallet', function () { $this->method->update(['type' => 'crypto_wallet']); expect($this->method->getDisplayName())->toBe('Crypto Wallet'); }); it('generates display name for bank account', function () { $this->method->update(['type' => 'bank_account']); expect($this->method->getDisplayName())->toBe('Bank Account'); }); it('generates display name for card without brand', function () { $this->method->update(['brand' => null]); expect($this->method->getDisplayName())->toBe('Card **** 4242'); }); it('sets itself as default and clears others', function () { $other = PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_other_default', 'type' => 'card', 'is_active' => true, 'is_default' => true, ]); $this->method->setAsDefault(); expect($this->method->fresh()->is_default)->toBeTrue() ->and($other->fresh()->is_default)->toBeFalse(); }); it('deactivates itself', function () { $this->method->deactivate(); expect($this->method->fresh()->is_active)->toBeFalse(); }); it('uses active scope correctly', function () { PaymentMethod::create([ 'workspace_id' => $this->workspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_inactive_scope', 'type' => 'card', 'is_active' => false, 'is_default' => false, ]); $active = PaymentMethod::active()->get(); expect($active->pluck('is_active')->unique()->toArray())->toBe([true]); }); it('uses default scope correctly', function () { $this->method->update(['is_default' => true]); $defaults = PaymentMethod::default()->get(); expect($defaults)->toHaveCount(1) ->and($defaults->first()->id)->toBe($this->method->id); }); it('uses forWorkspace scope correctly', function () { $otherWorkspace = Workspace::factory()->create(); PaymentMethod::create([ 'workspace_id' => $otherWorkspace->id, 'gateway' => 'stripe', 'gateway_payment_method_id' => 'pm_other_ws_scope', 'type' => 'card', 'is_active' => true, 'is_default' => false, ]); $methods = PaymentMethod::forWorkspace($this->workspace->id)->get(); expect($methods->pluck('workspace_id')->unique()->toArray())->toBe([$this->workspace->id]); }); });