From 0f1ce81b4e3f6bdfbc1d803e2f6d0e81ba62209b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Wed, 6 May 2026 14:50:02 +0100 Subject: [PATCH] Fix onboarding return after bank auth errors (#361) ## Summary - Keep non-onboarded users on the accounts step after bank authorization errors - Add feature and browser coverage for the callback error path ## Tests - vendor/bin/pint --dirty --format agent - php artisan test --compact tests/Browser/OnboardingFlowTest.php --filter='returns to the accounts step' - php artisan test --compact tests/Feature/OpenBanking/AuthorizationControllerTest.php --filter='callback with error' --- .../OpenBanking/AuthorizationController.php | 11 +++--- tests/Browser/OnboardingFlowTest.php | 35 +++++++++++++++++++ .../AuthorizationControllerTest.php | 17 +++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/OpenBanking/AuthorizationController.php b/app/Http/Controllers/OpenBanking/AuthorizationController.php index 6733ba72..e428e678 100644 --- a/app/Http/Controllers/OpenBanking/AuthorizationController.php +++ b/app/Http/Controllers/OpenBanking/AuthorizationController.php @@ -103,7 +103,8 @@ class AuthorizationController extends Controller public function callback(Request $request, BankingProviderInterface $provider): RedirectResponse { $user = auth()->user(); - $errorRedirect = $user->isOnboarded() ? 'settings.connections.index' : 'onboarding'; + $errorRedirectRoute = $user->isOnboarded() ? 'settings.connections.index' : 'onboarding'; + $errorRedirectParams = $user->isOnboarded() ? [] : ['step' => 'create-account']; if ($request->has('error')) { Log::warning('EnableBanking authorization error', [ @@ -117,14 +118,14 @@ class AuthorizationController extends Controller ->first() ?->delete(); - return redirect()->route($errorRedirect) + return redirect()->route($errorRedirectRoute, $errorRedirectParams) ->with('error', $request->query('error_description', 'Authorization was denied or cancelled.')); } $code = $request->query('code'); if (! $code) { - return redirect()->route($errorRedirect) + return redirect()->route($errorRedirectRoute, $errorRedirectParams) ->with('error', 'No authorization code received.'); } @@ -133,7 +134,7 @@ class AuthorizationController extends Controller } catch (\Throwable $e) { Log::error('EnableBanking session creation failed', ['error' => $e->getMessage()]); - return redirect()->route($errorRedirect) + return redirect()->route($errorRedirectRoute, $errorRedirectParams) ->with('error', 'Failed to connect to your bank. Please try again.'); } @@ -143,7 +144,7 @@ class AuthorizationController extends Controller ->first(); if (! $connection) { - return redirect()->route($errorRedirect) + return redirect()->route($errorRedirectRoute, $errorRedirectParams) ->with('error', 'No pending connection found.'); } diff --git a/tests/Browser/OnboardingFlowTest.php b/tests/Browser/OnboardingFlowTest.php index 3b2a746f..9eb91f65 100644 --- a/tests/Browser/OnboardingFlowTest.php +++ b/tests/Browser/OnboardingFlowTest.php @@ -2,6 +2,7 @@ use App\Models\Account; use App\Models\Bank; +use App\Models\BankingConnection; use App\Models\User; // ============================================================================= @@ -168,6 +169,40 @@ it('allows continuing with existing accounts', function () { ->assertNoJavascriptErrors(); }); +it('returns to the accounts step when bank authorization fails during onboarding', function () { + $user = User::factory()->create([ + 'onboarded_at' => null, + ]); + + $bank = Bank::factory()->create(['name' => 'Connected Bank']); + Account::factory()->create([ + 'user_id' => $user->id, + 'bank_id' => $bank->id, + 'type' => 'checking', + 'currency_code' => 'EUR', + ]); + $connection = BankingConnection::factory()->pending()->create([ + 'user_id' => $user->id, + 'aspsp_name' => 'Failing Bank', + 'aspsp_country' => 'ES', + ]); + + $this->actingAs($user); + + $page = visit('/open-banking/callback?error=access_denied&error_description=Authentication+failed'); + + $page->wait(1) + ->assertPathIs('/onboarding') + ->assertQueryStringHas('step', 'create-account') + ->assertSee('Your Accounts') + ->assertSee('Connected Bank') + ->assertDontSee('Welcome to') + ->assertNoJavascriptErrors(); + + $connection->refresh(); + expect($connection->trashed())->toBeTrue(); +}); + // ============================================================================= // More Accounts Flow Tests // ============================================================================= diff --git a/tests/Feature/OpenBanking/AuthorizationControllerTest.php b/tests/Feature/OpenBanking/AuthorizationControllerTest.php index bc8ff283..4cac3415 100644 --- a/tests/Feature/OpenBanking/AuthorizationControllerTest.php +++ b/tests/Feature/OpenBanking/AuthorizationControllerTest.php @@ -146,6 +146,23 @@ test('callback with error redirects with error message and deletes pending conne expect($connection->trashed())->toBeTrue(); }); +test('callback with error during onboarding redirects to the accounts step', function () { + $user = User::factory()->notOnboarded()->create(); + Account::factory()->create(['user_id' => $user->id]); + $connection = BankingConnection::factory()->pending()->create([ + 'user_id' => $user->id, + ]); + + $response = $this->actingAs($user) + ->get('/open-banking/callback?error=access_denied&error_description=User+denied+access'); + + $response->assertRedirect(route('onboarding', ['step' => 'create-account'])); + $response->assertSessionHas('error', 'User denied access'); + + $connection->refresh(); + expect($connection->trashed())->toBeTrue(); +}); + test('callback without code redirects with error', function () { $user = User::factory()->onboarded()->create(); $response = $this->actingAs($user)->get('/open-banking/callback');