diff --git a/app/Features/InteractiveBrokers.php b/app/Features/InteractiveBrokers.php deleted file mode 100644 index fe27d967..00000000 --- a/app/Features/InteractiveBrokers.php +++ /dev/null @@ -1,23 +0,0 @@ -validated(); $user = auth()->user(); - abort_unless(Feature::for($user)->active(InteractiveBrokers::class), 403); - if ($this->shouldBlockOpenBankingAccess($user)) { return $this->subscribeJsonResponse(); } diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index a2477c80..74fdfb1e 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -6,7 +6,6 @@ use App\Enums\BankingConnectionStatus; use App\Enums\BankingProvider; use App\Features\AiConsentSettings; use App\Features\CalculateBalancesOnImport; -use App\Features\InteractiveBrokers; use App\Models\BankingConnection; use App\Services\CurrencyOptions; use Illuminate\Foundation\Inspiring; @@ -180,21 +179,18 @@ class HandleInertiaRequests extends Middleware return [ 'cashflow' => true, 'calculateBalancesOnImport' => false, - 'interactiveBrokers' => false, 'aiConsentSettings' => false, ]; } $features = Feature::for($user)->values([ CalculateBalancesOnImport::class, - InteractiveBrokers::class, AiConsentSettings::class, ]); return [ 'cashflow' => true, 'calculateBalancesOnImport' => $features[CalculateBalancesOnImport::class] !== false, - 'interactiveBrokers' => $features[InteractiveBrokers::class] !== false, 'aiConsentSettings' => $features[AiConsentSettings::class] !== false, ]; } diff --git a/resources/js/components/open-banking/connect-account-dialog.test.tsx b/resources/js/components/open-banking/connect-account-dialog.test.tsx index 66a70ac2..77293867 100644 --- a/resources/js/components/open-banking/connect-account-dialog.test.tsx +++ b/resources/js/components/open-banking/connect-account-dialog.test.tsx @@ -9,10 +9,8 @@ globalThis.ResizeObserver ??= class { disconnect() {} }; -const mockFeatures = { interactiveBrokers: false }; - vi.mock('@inertiajs/react', () => ({ - usePage: () => ({ props: { features: mockFeatures } }), + usePage: () => ({ props: { features: {} } }), })); vi.mock('@/utils/i18n', () => ({ @@ -116,11 +114,9 @@ async function reachBankStep( describe('ConnectAccountDialog', () => { beforeEach(() => { vi.clearAllMocks(); - mockFeatures.interactiveBrokers = false; }); - it('shows Interactive Brokers only when the feature flag is enabled', async () => { - mockFeatures.interactiveBrokers = true; + it('shows Interactive Brokers as a connectable provider', async () => { await reachBankStep([]); expect( @@ -128,14 +124,6 @@ describe('ConnectAccountDialog', () => { ).toBeInTheDocument(); }); - it('hides Interactive Brokers when the feature flag is disabled', async () => { - await reachBankStep([]); - - expect( - screen.queryByRole('button', { name: /Interactive Brokers/ }), - ).not.toBeInTheDocument(); - }); - it('keeps an already-connected bank selectable and badges it', async () => { await reachBankStep([liveBbvaConnection()]); diff --git a/resources/js/hooks/use-connect-flow.ts b/resources/js/hooks/use-connect-flow.ts index f55b9786..fdc69505 100644 --- a/resources/js/hooks/use-connect-flow.ts +++ b/resources/js/hooks/use-connect-flow.ts @@ -9,13 +9,11 @@ import { isProviderComplete, } from '@/lib/connect-providers'; import { getCsrfToken } from '@/lib/csrf'; -import type { SharedData } from '@/types'; import type { BankingConnection, EnableBankingInstitution, } from '@/types/banking'; import { __ } from '@/utils/i18n'; -import { usePage } from '@inertiajs/react'; import { useCallback, useEffect, useMemo, useState } from 'react'; export const CONNECT_COUNTRIES = [ @@ -48,7 +46,6 @@ export type ConnectStep = 'country' | 'bank' | 'confirm'; * the components. */ export function useConnectFlow(connections: BankingConnection[]) { - const { features } = usePage().props; const [step, setStep] = useState('country'); const [country, setCountry] = useState(''); const [institutions, setInstitutions] = useState< @@ -144,7 +141,6 @@ export function useConnectFlow(connections: BankingConnection[]) { const extraInstitutions = CONNECT_PROVIDERS.filter( (p) => - (!p.feature || features[p.feature]) && (!p.onlyCountry || p.onlyCountry === countryCode) && !hasLiveConnectionForProvider( connections, @@ -175,7 +171,7 @@ export function useConnectFlow(connections: BankingConnection[]) { setIsLoading(false); } }, - [connections, features], + [connections], ); const handleAuthorize = useCallback(async () => { diff --git a/resources/js/lib/connect-providers.tsx b/resources/js/lib/connect-providers.tsx index 59535e70..55b341d6 100644 --- a/resources/js/lib/connect-providers.tsx +++ b/resources/js/lib/connect-providers.tsx @@ -2,7 +2,6 @@ import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { cn } from '@/lib/utils'; -import type { Features } from '@/types'; import type { EnableBankingInstitution } from '@/types/banking'; import { __ } from '@/utils/i18n'; @@ -41,8 +40,6 @@ export type ConnectProvider = { sendsCountry?: boolean; /** Only offered when connecting from this country (e.g. Indexa: ES). */ onlyCountry?: string; - /** Hidden unless this Pennant feature is active. */ - feature?: keyof Features; /** Confirm-step header copy (i18n key). */ headerDescription: string; /** Confirm-card copy (i18n key). */ @@ -215,7 +212,6 @@ export const CONNECT_PROVIDERS: ConnectProvider[] = [ maximum_consent_validity: null, }, endpoint: '/open-banking/interactive-brokers/connect', - feature: 'interactiveBrokers', headerDescription: 'Enter your Flex Web Service token and Query ID to connect your Interactive Brokers account.', cardDescription: diff --git a/resources/js/types/index.d.ts b/resources/js/types/index.d.ts index e144e6f8..dd8cda89 100644 --- a/resources/js/types/index.d.ts +++ b/resources/js/types/index.d.ts @@ -42,7 +42,6 @@ export interface NavDivider { export interface Features { cashflow: boolean; calculateBalancesOnImport: boolean; - interactiveBrokers: boolean; aiConsentSettings: boolean; } diff --git a/tests/Feature/InertiaSharedDataTest.php b/tests/Feature/InertiaSharedDataTest.php index 743ce623..b9097964 100644 --- a/tests/Feature/InertiaSharedDataTest.php +++ b/tests/Feature/InertiaSharedDataTest.php @@ -43,7 +43,6 @@ test('shared feature flags do not include coinbase flag', function () { expect($props['features'])->toBe([ 'cashflow' => true, 'calculateBalancesOnImport' => false, - 'interactiveBrokers' => false, 'aiConsentSettings' => false, ]); }); diff --git a/tests/Feature/OpenBanking/InteractiveBrokersControllerTest.php b/tests/Feature/OpenBanking/InteractiveBrokersControllerTest.php index 2d204d9b..e780e7a0 100644 --- a/tests/Feature/OpenBanking/InteractiveBrokersControllerTest.php +++ b/tests/Feature/OpenBanking/InteractiveBrokersControllerTest.php @@ -1,7 +1,6 @@ create([ @@ -41,23 +39,10 @@ function ibConnect(): array return ['token' => 'flex-token-1234567890', 'query_id' => '123456']; } -test('is blocked when the feature flag is off', function () { - $user = User::factory()->onboarded()->create(); - - $this->actingAs($user)->postJson('/open-banking/interactive-brokers/connect', ibConnect()) - ->assertForbidden(); - - $this->assertDatabaseMissing('banking_connections', [ - 'user_id' => $user->id, - 'provider' => 'interactivebrokers', - ]); -}); - test('users can connect with valid flex credentials', function () { Queue::fake(); $user = User::factory()->onboarded()->create(); - Feature::for($user)->activate(InteractiveBrokers::class); ibFakeFlex(); $response = $this->actingAs($user)->postJson('/open-banking/interactive-brokers/connect', ibConnect()); @@ -79,8 +64,6 @@ test('users can connect with valid flex credentials', function () { test('invalid flex credentials return 422', function () { $user = User::factory()->onboarded()->create(); - Feature::for($user)->activate(InteractiveBrokers::class); - Http::fake([ '*SendRequest*' => Http::response('Fail1015Invalid token.'), ]); @@ -99,8 +82,6 @@ test('free tier users cannot connect after onboarding when subscriptions are ena config(['subscriptions.enabled' => true]); $user = User::factory()->onboarded()->create(); - Feature::for($user)->activate(InteractiveBrokers::class); - $response = $this->actingAs($user)->postJson('/open-banking/interactive-brokers/connect', ibConnect()); $response->assertStatus(402); @@ -109,8 +90,6 @@ test('free tier users cannot connect after onboarding when subscriptions are ena test('token and query_id are required', function () { $user = User::factory()->onboarded()->create(); - Feature::for($user)->activate(InteractiveBrokers::class); - $this->actingAs($user)->postJson('/open-banking/interactive-brokers/connect', []) ->assertUnprocessable() ->assertJsonValidationErrors(['token', 'query_id']); @@ -121,7 +100,6 @@ test('auto-creates accounts during onboarding', function () { Queue::fake(); $user = User::factory()->notOnboarded()->create(); - Feature::for($user)->activate(InteractiveBrokers::class); ibFakeFlex(['U1111111', 'U2222222']); $response = $this->actingAs($user)->postJson('/open-banking/interactive-brokers/connect', ibConnect()); @@ -161,8 +139,6 @@ test('reports a friendly message while the statement is still generating', funct Sleep::fake(); $user = User::factory()->onboarded()->create(); - Feature::for($user)->activate(InteractiveBrokers::class); - Http::fake([ '*SendRequest*' => Http::response('Success999'), '*GetStatement*' => Http::response('Warn1019Statement generation in progress. Please try again shortly.'), @@ -180,8 +156,6 @@ test('reports a friendly message while the statement is still generating', funct test('reports a rate-limit message when IB throttles the request', function () { $user = User::factory()->onboarded()->create(); - Feature::for($user)->activate(InteractiveBrokers::class); - Http::fake([ '*SendRequest*' => Http::response('Fail1018Too many requests have been made from this token.'), ]);