bankingConnections() ->withCount('accounts') ->orderByDesc('created_at') ->get() ->each(function ($connection) { $connection->has_pending_accounts = $connection->hasPendingAccounts(); }); return Inertia::render('settings/connections', [ 'connections' => $connections, ]); } /** * Manually trigger a sync for a connection. */ public function sync(BankingConnection $connection): RedirectResponse { if ($connection->user_id !== Auth::id()) { abort(403); } if ($this->shouldBlockOpenBankingAccess(Auth::user(), false)) { return $this->subscribeRedirectResponse(); } if (! $connection->isActive() && $connection->status !== BankingConnectionStatus::Error) { return back()->with('error', 'Connection is not active.'); } $connection->update([ 'status' => BankingConnectionStatus::Active, 'error_message' => null, 'consecutive_sync_failures' => 0, ]); SyncBankingConnectionJob::dispatch($connection); return back()->with('success', 'Sync started. Transactions will be updated shortly.'); } /** * Update credentials for an API-key-based connection. */ public function updateCredentials(UpdateConnectionCredentialsRequest $request, BankingConnection $connection): RedirectResponse { if ($this->shouldBlockOpenBankingAccess($request->user(), false)) { return $this->subscribeRedirectResponse(); } $validated = $request->validated(); $validationError = $this->validateProviderCredentials($connection, $validated); if ($validationError) { return back()->withErrors(['credentials' => $validationError]); } $connection->update([ ...$connection->provider->credentialColumns($validated), 'status' => BankingConnectionStatus::Active, 'error_message' => null, 'consecutive_sync_failures' => 0, ]); SyncBankingConnectionJob::dispatch($connection); return back()->with('success', __('Credentials updated. Sync started.')); } /** * Validate credentials against the provider API. */ private function validateProviderCredentials(BankingConnection $connection, array $validated): ?string { try { match ($connection->provider) { BankingProvider::IndexaCapital => (new IndexaCapitalClient($validated['api_token']))->getUser(), BankingProvider::Binance => (new BinanceClient($validated['api_key'], $validated['api_secret']))->getAccount(), BankingProvider::Bitpanda => (new BitpandaClient($validated['api_key']))->getCryptoWallets(), BankingProvider::Coinbase => (new CoinbaseClient($validated['api_key_name'], $validated['private_key']))->getAccounts(limit: 1), BankingProvider::InteractiveBrokers => (new InteractiveBrokersClient($validated['token'], $validated['query_id']))->fetchStatement(), BankingProvider::Wise => (new WiseClient($validated['api_token']))->getProfiles(), default => throw new \InvalidArgumentException('Unsupported provider for credential update.'), }; } catch (\InvalidArgumentException $e) { return $e->getMessage(); } catch (\Throwable $e) { Log::warning('Credential validation failed during update', [ 'connection_id' => $connection->id, 'provider' => $connection->provider->value, 'error' => $e->getMessage(), ]); return __('Invalid credentials. Please check and try again.'); } return null; } /** * Revoke and delete a banking connection. */ public function destroy(DestroyConnectionRequest $request, BankingConnection $connection, DisconnectBankingConnection $disconnectBankingConnection): RedirectResponse { $disconnectBankingConnection->handle($connection, $request->boolean('delete_accounts')); return redirect()->route('settings.connections.index') ->with('success', 'Banking connection disconnected.'); } }