From 5b9ae2a5259ecf1e55e4074295c52dcc0429ef71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Mon, 16 Mar 2026 10:59:46 +0000 Subject: [PATCH] fix(banking): treat 429 rate limit as transient, skip error status on sync (#224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - A `429 ASPSP_RATE_LIMIT_EXCEEDED` response from the bank's API was incorrectly marking connections as `status=error`, blocking all future syncs. - Rate limit errors are transient — the connection is still valid and should be retried on the next scheduled sync. - Added `isRateLimitError()` check in the `catch` block of `SyncBankingConnectionJob`: on 429, the job returns early without updating the connection status or error message. --- app/Jobs/SyncBankingConnectionJob.php | 9 ++++++ .../SyncBankingConnectionJobTest.php | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/app/Jobs/SyncBankingConnectionJob.php b/app/Jobs/SyncBankingConnectionJob.php index c22566ec..e66cef6a 100644 --- a/app/Jobs/SyncBankingConnectionJob.php +++ b/app/Jobs/SyncBankingConnectionJob.php @@ -80,6 +80,10 @@ class SyncBankingConnectionJob implements ShouldBeUnique, ShouldQueue 'error' => $e->getMessage(), ]); + if ($this->isRateLimitError($e)) { + return; + } + $connection->update([ 'status' => BankingConnectionStatus::Error, 'error_message' => $this->friendlyErrorMessage($e), @@ -203,6 +207,11 @@ class SyncBankingConnectionJob implements ShouldBeUnique, ShouldQueue return __('An unexpected error occurred during sync. Please try again later.'); } + private function isRateLimitError(\Throwable $e): bool + { + return $e instanceof RequestException && $e->response->status() === 429; + } + private function isAuthError(\Throwable $e): bool { return $e instanceof RequestException diff --git a/tests/Feature/OpenBanking/SyncBankingConnectionJobTest.php b/tests/Feature/OpenBanking/SyncBankingConnectionJobTest.php index 2013a5c6..44386f35 100644 --- a/tests/Feature/OpenBanking/SyncBankingConnectionJobTest.php +++ b/tests/Feature/OpenBanking/SyncBankingConnectionJobTest.php @@ -828,3 +828,34 @@ test('sends auth failed email for binance 403 error on final attempt', function && $mail->bankingConnection->id === $connection->id; }); }); + +test('rate limit error does not set connection status to error', function () { + $user = User::factory()->onboarded()->create(); + $connection = BankingConnection::factory()->create([ + 'user_id' => $user->id, + 'last_synced_at' => now()->subDay(), + ]); + Account::factory()->connected()->create([ + 'user_id' => $user->id, + 'banking_connection_id' => $connection->id, + 'external_account_id' => 'ext-123', + ]); + + $transactionSync = Mockery::mock(TransactionSyncService::class); + $transactionSync->shouldReceive('sync')->andThrow( + new \Illuminate\Http\Client\RequestException( + new \Illuminate\Http\Client\Response( + new \GuzzleHttp\Psr7\Response(429) + ) + ) + ); + + $balanceSync = Mockery::mock(BalanceSyncService::class); + + $job = new SyncBankingConnectionJob($connection); + $job->handle($transactionSync, $balanceSync); + + $connection->refresh(); + expect($connection->status)->toBe(BankingConnectionStatus::Active); + expect($connection->error_message)->toBeNull(); +});