diff --git a/app/Services/Banking/IndexaCapitalClient.php b/app/Services/Banking/IndexaCapitalClient.php index de87115c..69f04274 100644 --- a/app/Services/Banking/IndexaCapitalClient.php +++ b/app/Services/Banking/IndexaCapitalClient.php @@ -64,27 +64,46 @@ class IndexaCapitalClient /** * Get performance data for an account, including current portfolio value. * + * Returns an empty array when Indexa Capital responds with 404 (no + * performance data available yet for the account, e.g. brand-new or + * inactive accounts) instead of throwing, so the sync can no-op cleanly. + * * @return array{total_amount?: float, return?: float, return_percentage?: float, portfolios?: array, net_amounts?: array} */ public function getPerformance(string $accountNumber): array { - $response = $this->client()->get("/accounts/{$accountNumber}/performance"); + $response = $this->client(throwOnError: false)->get("/accounts/{$accountNumber}/performance"); + + if ($response->status() === 404) { + Log::info('No Indexa Capital performance data available', [ + 'account_number' => $accountNumber, + ]); + + return []; + } $response->throw(); - return $response->json(); + $json = $response->json(); + + return is_array($json) ? $json : []; } - private function client(): PendingRequest + private function client(bool $throwOnError = true): PendingRequest { - return Http::baseUrl(self::BASE_URL) + $client = Http::baseUrl(self::BASE_URL) ->withHeaders(['X-AUTH-TOKEN' => $this->apiToken]) - ->acceptJson() - ->throw(function ($response, $exception) { - Log::error('Indexa Capital API error', [ - 'status' => $response->status(), - 'body' => $response->json(), - ]); - }); + ->acceptJson(); + + if (! $throwOnError) { + return $client; + } + + return $client->throw(function ($response, $exception) { + Log::error('Indexa Capital API error', [ + 'status' => $response->status(), + 'body' => $response->json(), + ]); + }); } } diff --git a/tests/Feature/OpenBanking/IndexaCapitalBalanceSyncTest.php b/tests/Feature/OpenBanking/IndexaCapitalBalanceSyncTest.php index e924b36d..600fa1f1 100644 --- a/tests/Feature/OpenBanking/IndexaCapitalBalanceSyncTest.php +++ b/tests/Feature/OpenBanking/IndexaCapitalBalanceSyncTest.php @@ -116,6 +116,29 @@ test('skips account without external_account_id', function () { expect($account->balances()->count())->toBe(0); }); +test('returns empty performance when indexa capital responds 404', function () { + $user = User::factory()->onboarded()->create(); + $connection = BankingConnection::factory()->indexaCapital()->create([ + 'user_id' => $user->id, + ]); + $account = Account::factory()->connected()->create([ + 'user_id' => $user->id, + 'banking_connection_id' => $connection->id, + 'external_account_id' => 'IC-404', + ]); + + Http::fake([ + 'api.indexacapital.com/accounts/IC-404/performance' => Http::response('not found', 404), + ]); + + $client = new IndexaCapitalClient('test-token'); + $service = app(IndexaCapitalBalanceSyncService::class); + + $service->sync($account, $client); + + expect($account->balances()->count())->toBe(0); +}); + test('handles missing portfolios gracefully', function () { $user = User::factory()->onboarded()->create(); $connection = BankingConnection::factory()->indexaCapital()->create([