refactor(open-banking): extract shared connect flow into base controller

The Binance, Bitpanda, Coinbase, Indexa Capital, and Interactive Brokers connect
controllers each carried a ~60-line store() that was near-identical: subscription
gate, credential validation, Bank + BankingConnection creation, pending-account
mapping, and the onboarding/redirect branch. Any behavior change or bug fix had
to be repeated five times.

Introduce an abstract OpenBankingConnectController that owns the shared flow via a
template method (connect()), with per-provider extension points: provider(),
providerName(), bankLogo(), aspspCountry(), fetchProviderData(),
validationFailureLogMessage(), credentialErrorMessage(), buildPendingAccounts(),
and an optional emptyProviderDataMessage() guard (used by Interactive Brokers).
Each controller now declares only what varies.

Behavior is unchanged; the existing OpenBanking feature tests pass (304/304).

Wise is intentionally left as-is: it builds pending accounts before creating the
connection and has its own empty-result guard, so it does not fit this flow.
This commit is contained in:
Víctor Falcón 2026-07-04 19:59:19 +02:00
parent 79b8d27ece
commit a73adddb3b
6 changed files with 326 additions and 306 deletions

View File

@ -2,88 +2,69 @@
namespace App\Http\Controllers\OpenBanking;
use App\Enums\BankingConnectionStatus;
use App\Enums\BankingProvider;
use App\Http\Controllers\Controller;
use App\Http\Controllers\OpenBanking\Concerns\CreatesAccountsFromPending;
use App\Http\Controllers\OpenBanking\Concerns\HandlesSubscriptionGate;
use App\Http\Requests\OpenBanking\ConnectBinanceRequest;
use App\Jobs\SyncBankingConnectionJob;
use App\Models\Bank;
use App\Models\User;
use App\Services\AccountUserCurrencyService;
use App\Services\Banking\BinanceClient;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
class BinanceController extends Controller
class BinanceController extends OpenBankingConnectController
{
use CreatesAccountsFromPending;
use HandlesSubscriptionGate;
/**
* Validate Binance API credentials and create a connection.
*/
public function store(ConnectBinanceRequest $request, AccountUserCurrencyService $accountUserCurrencyService): JsonResponse
{
$validated = $request->validated();
$user = auth()->user();
return $this->connect($request->validated(), $accountUserCurrencyService);
}
if ($this->shouldBlockOpenBankingAccess($user)) {
return $this->subscribeJsonResponse();
}
protected function provider(): BankingProvider
{
return BankingProvider::Binance;
}
protected function providerName(): string
{
return 'Binance';
}
protected function bankLogo(): ?string
{
return 'https://whisper.money/storage/banks/logos/t1h5rqi19dJTPl6ZadziPjNwm0lrcdTFBRzB3iCy.png';
}
protected function aspspCountry(array $validated): string
{
return $validated['country'];
}
protected function fetchProviderData(array $validated): mixed
{
$client = new BinanceClient($validated['api_key'], $validated['api_secret']);
$client->getAccount();
try {
$client->getAccount();
} catch (\Throwable $e) {
Log::warning('Binance credential validation failed', ['error' => $e->getMessage()]);
return null;
}
return response()->json([
'message' => 'Invalid API credentials or failed to connect to Binance.',
], 422);
}
protected function validationFailureLogMessage(): string
{
return 'Binance credential validation failed';
}
$bank = Bank::firstOrCreate(
['name' => 'Binance', 'user_id' => null],
['name' => 'Binance', 'logo' => 'https://whisper.money/storage/banks/logos/t1h5rqi19dJTPl6ZadziPjNwm0lrcdTFBRzB3iCy.png'],
);
protected function credentialErrorMessage(\Throwable $e): string
{
return 'Invalid API credentials or failed to connect to Binance.';
}
$connection = $user->bankingConnections()->create([
'provider' => BankingProvider::Binance,
...BankingProvider::Binance->credentialColumns($validated),
'aspsp_name' => 'Binance',
'aspsp_country' => $validated['country'],
'aspsp_logo' => $bank->logo,
'status' => BankingConnectionStatus::Pending,
]);
$pendingAccounts = [
protected function buildPendingAccounts(mixed $providerData, User $user): array
{
return [
[
'uid' => 'binance-portfolio',
'currency' => $user->currency_code,
'name' => 'Crypto Portfolio',
],
];
$connection->update([
'status' => BankingConnectionStatus::AwaitingMapping,
'pending_accounts_data' => $pendingAccounts,
]);
if (! $user->isOnboarded()) {
$this->createAccountsFromPending($user, $connection, $accountUserCurrencyService);
SyncBankingConnectionJob::dispatch($connection);
return response()->json([
'redirect_url' => route('onboarding', ['step' => 'create-account']),
'connection_id' => $connection->id,
]);
}
return response()->json([
'redirect_url' => route('open-banking.map-accounts', $connection),
'connection_id' => $connection->id,
]);
}
}

View File

@ -2,88 +2,69 @@
namespace App\Http\Controllers\OpenBanking;
use App\Enums\BankingConnectionStatus;
use App\Enums\BankingProvider;
use App\Http\Controllers\Controller;
use App\Http\Controllers\OpenBanking\Concerns\CreatesAccountsFromPending;
use App\Http\Controllers\OpenBanking\Concerns\HandlesSubscriptionGate;
use App\Http\Requests\OpenBanking\ConnectBitpandaRequest;
use App\Jobs\SyncBankingConnectionJob;
use App\Models\Bank;
use App\Models\User;
use App\Services\AccountUserCurrencyService;
use App\Services\Banking\BitpandaClient;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
class BitpandaController extends Controller
class BitpandaController extends OpenBankingConnectController
{
use CreatesAccountsFromPending;
use HandlesSubscriptionGate;
/**
* Validate Bitpanda API key and create a connection.
*/
public function store(ConnectBitpandaRequest $request, AccountUserCurrencyService $accountUserCurrencyService): JsonResponse
{
$validated = $request->validated();
$user = auth()->user();
return $this->connect($request->validated(), $accountUserCurrencyService);
}
if ($this->shouldBlockOpenBankingAccess($user)) {
return $this->subscribeJsonResponse();
}
protected function provider(): BankingProvider
{
return BankingProvider::Bitpanda;
}
protected function providerName(): string
{
return 'Bitpanda';
}
protected function bankLogo(): ?string
{
return 'https://whisper.money/storage/banks/logos/7Y6gl0gaFH1mStJMcUQ9VpgzX1kduyumm0dDhGlf.png';
}
protected function aspspCountry(array $validated): string
{
return $validated['country'];
}
protected function fetchProviderData(array $validated): mixed
{
$client = new BitpandaClient($validated['api_key']);
$client->getCryptoWallets();
try {
$client->getCryptoWallets();
} catch (\Throwable $e) {
Log::warning('Bitpanda credential validation failed', ['error' => $e->getMessage()]);
return null;
}
return response()->json([
'message' => 'Invalid API key or failed to connect to Bitpanda.',
], 422);
}
protected function validationFailureLogMessage(): string
{
return 'Bitpanda credential validation failed';
}
$bank = Bank::firstOrCreate(
['name' => 'Bitpanda', 'user_id' => null],
['name' => 'Bitpanda', 'logo' => 'https://whisper.money/storage/banks/logos/7Y6gl0gaFH1mStJMcUQ9VpgzX1kduyumm0dDhGlf.png'],
);
protected function credentialErrorMessage(\Throwable $e): string
{
return 'Invalid API key or failed to connect to Bitpanda.';
}
$connection = $user->bankingConnections()->create([
'provider' => BankingProvider::Bitpanda,
...BankingProvider::Bitpanda->credentialColumns($validated),
'aspsp_name' => 'Bitpanda',
'aspsp_country' => $validated['country'],
'aspsp_logo' => $bank->logo,
'status' => BankingConnectionStatus::Pending,
]);
$pendingAccounts = [
protected function buildPendingAccounts(mixed $providerData, User $user): array
{
return [
[
'uid' => 'bitpanda-portfolio',
'currency' => $user->currency_code,
'name' => 'Crypto Portfolio',
],
];
$connection->update([
'status' => BankingConnectionStatus::AwaitingMapping,
'pending_accounts_data' => $pendingAccounts,
]);
if (! $user->isOnboarded()) {
$this->createAccountsFromPending($user, $connection, $accountUserCurrencyService);
SyncBankingConnectionJob::dispatch($connection);
return response()->json([
'redirect_url' => route('onboarding', ['step' => 'create-account']),
'connection_id' => $connection->id,
]);
}
return response()->json([
'redirect_url' => route('open-banking.map-accounts', $connection),
'connection_id' => $connection->id,
]);
}
}

View File

@ -2,88 +2,69 @@
namespace App\Http\Controllers\OpenBanking;
use App\Enums\BankingConnectionStatus;
use App\Enums\BankingProvider;
use App\Http\Controllers\Controller;
use App\Http\Controllers\OpenBanking\Concerns\CreatesAccountsFromPending;
use App\Http\Controllers\OpenBanking\Concerns\HandlesSubscriptionGate;
use App\Http\Requests\OpenBanking\ConnectCoinbaseRequest;
use App\Jobs\SyncBankingConnectionJob;
use App\Models\Bank;
use App\Models\User;
use App\Services\AccountUserCurrencyService;
use App\Services\Banking\CoinbaseClient;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
class CoinbaseController extends Controller
class CoinbaseController extends OpenBankingConnectController
{
use CreatesAccountsFromPending;
use HandlesSubscriptionGate;
/**
* Validate Coinbase CDP API credentials and create a connection.
*/
public function store(ConnectCoinbaseRequest $request, AccountUserCurrencyService $accountUserCurrencyService): JsonResponse
{
$validated = $request->validated();
$user = auth()->user();
return $this->connect($request->validated(), $accountUserCurrencyService);
}
if ($this->shouldBlockOpenBankingAccess($user)) {
return $this->subscribeJsonResponse();
}
protected function provider(): BankingProvider
{
return BankingProvider::Coinbase;
}
protected function providerName(): string
{
return 'Coinbase';
}
protected function bankLogo(): ?string
{
return 'https://whisper.money/storage/banks/logos/coinbase.png';
}
protected function aspspCountry(array $validated): string
{
return $validated['country'];
}
protected function fetchProviderData(array $validated): mixed
{
$client = new CoinbaseClient($validated['api_key_name'], $validated['private_key']);
$client->getAccounts(limit: 1);
try {
$client->getAccounts(limit: 1);
} catch (\Throwable $e) {
Log::warning('Coinbase credential validation failed', ['error' => $e->getMessage()]);
return null;
}
return response()->json([
'message' => 'Invalid API credentials or failed to connect to Coinbase.',
], 422);
}
protected function validationFailureLogMessage(): string
{
return 'Coinbase credential validation failed';
}
$bank = Bank::firstOrCreate(
['name' => 'Coinbase', 'user_id' => null],
['name' => 'Coinbase', 'logo' => 'https://whisper.money/storage/banks/logos/coinbase.png'],
);
protected function credentialErrorMessage(\Throwable $e): string
{
return 'Invalid API credentials or failed to connect to Coinbase.';
}
$connection = $user->bankingConnections()->create([
'provider' => BankingProvider::Coinbase,
...BankingProvider::Coinbase->credentialColumns($validated),
'aspsp_name' => 'Coinbase',
'aspsp_country' => $validated['country'],
'aspsp_logo' => $bank->logo,
'status' => BankingConnectionStatus::Pending,
]);
$pendingAccounts = [
protected function buildPendingAccounts(mixed $providerData, User $user): array
{
return [
[
'uid' => 'coinbase-portfolio',
'currency' => $user->currency_code,
'name' => 'Crypto Portfolio',
],
];
$connection->update([
'status' => BankingConnectionStatus::AwaitingMapping,
'pending_accounts_data' => $pendingAccounts,
]);
if (! $user->isOnboarded()) {
$this->createAccountsFromPending($user, $connection, $accountUserCurrencyService);
SyncBankingConnectionJob::dispatch($connection);
return response()->json([
'redirect_url' => route('onboarding', ['step' => 'create-account']),
'connection_id' => $connection->id,
]);
}
return response()->json([
'redirect_url' => route('open-banking.map-accounts', $connection),
'connection_id' => $connection->id,
]);
}
}

View File

@ -2,95 +2,68 @@
namespace App\Http\Controllers\OpenBanking;
use App\Enums\BankingConnectionStatus;
use App\Enums\BankingProvider;
use App\Http\Controllers\Controller;
use App\Http\Controllers\OpenBanking\Concerns\CreatesAccountsFromPending;
use App\Http\Controllers\OpenBanking\Concerns\HandlesSubscriptionGate;
use App\Http\Requests\OpenBanking\ConnectIndexaCapitalRequest;
use App\Jobs\SyncBankingConnectionJob;
use App\Models\Bank;
use App\Models\User;
use App\Services\AccountUserCurrencyService;
use App\Services\Banking\IndexaCapitalClient;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
class IndexaCapitalController extends Controller
class IndexaCapitalController extends OpenBankingConnectController
{
use CreatesAccountsFromPending;
use HandlesSubscriptionGate;
/**
* Validate the Indexa Capital API token and create a connection.
*/
public function store(ConnectIndexaCapitalRequest $request, AccountUserCurrencyService $accountUserCurrencyService): JsonResponse
{
$validated = $request->validated();
$user = auth()->user();
return $this->connect($request->validated(), $accountUserCurrencyService);
}
if ($this->shouldBlockOpenBankingAccess($user)) {
return $this->subscribeJsonResponse();
}
protected function provider(): BankingProvider
{
return BankingProvider::IndexaCapital;
}
protected function providerName(): string
{
return 'Indexa Capital';
}
protected function bankLogo(): ?string
{
return '/images/banks/logos/indexa-capital.jpg';
}
protected function aspspCountry(array $validated): string
{
return 'ES';
}
protected function fetchProviderData(array $validated): mixed
{
$client = new IndexaCapitalClient($validated['api_token']);
try {
$userData = $client->getUser();
} catch (\Throwable $e) {
Log::warning('Indexa Capital token validation failed', ['error' => $e->getMessage()]);
return $client->getUser();
}
return response()->json([
'message' => 'Invalid API token or failed to connect to Indexa Capital.',
], 422);
}
protected function validationFailureLogMessage(): string
{
return 'Indexa Capital token validation failed';
}
$bank = Bank::firstOrCreate(
['name' => 'Indexa Capital', 'user_id' => null],
['name' => 'Indexa Capital', 'logo' => '/images/banks/logos/indexa-capital.jpg'],
);
$connection = $user->bankingConnections()->create([
'provider' => BankingProvider::IndexaCapital,
...BankingProvider::IndexaCapital->credentialColumns($validated),
'aspsp_name' => 'Indexa Capital',
'aspsp_country' => 'ES',
'aspsp_logo' => $bank->logo,
'status' => BankingConnectionStatus::Pending,
]);
$pendingAccounts = $this->buildPendingAccounts($userData);
$connection->update([
'status' => BankingConnectionStatus::AwaitingMapping,
'pending_accounts_data' => $pendingAccounts,
]);
if (! $user->isOnboarded()) {
$this->createAccountsFromPending($user, $connection, $accountUserCurrencyService);
SyncBankingConnectionJob::dispatch($connection);
return response()->json([
'redirect_url' => route('onboarding', ['step' => 'create-account']),
'connection_id' => $connection->id,
]);
}
return response()->json([
'redirect_url' => route('open-banking.map-accounts', $connection),
'connection_id' => $connection->id,
]);
protected function credentialErrorMessage(\Throwable $e): string
{
return 'Invalid API token or failed to connect to Indexa Capital.';
}
/**
* Build the pending accounts data in the same format as EnableBanking.
*
* @return array<int, array{uid: string, currency: string, name: string}>
*/
private function buildPendingAccounts(array $userData): array
protected function buildPendingAccounts(mixed $providerData, User $user): array
{
$accounts = [];
foreach ($userData['accounts'] ?? [] as $account) {
foreach ($providerData['accounts'] ?? [] as $account) {
$accountNumber = $account['account_number'] ?? null;
if (! $accountNumber) {

View File

@ -2,96 +2,62 @@
namespace App\Http\Controllers\OpenBanking;
use App\Enums\BankingConnectionStatus;
use App\Enums\BankingProvider;
use App\Exceptions\Banking\TransientBankingProviderException;
use App\Http\Controllers\Controller;
use App\Http\Controllers\OpenBanking\Concerns\CreatesAccountsFromPending;
use App\Http\Controllers\OpenBanking\Concerns\HandlesSubscriptionGate;
use App\Http\Requests\OpenBanking\ConnectInteractiveBrokersRequest;
use App\Jobs\SyncBankingConnectionJob;
use App\Models\Bank;
use App\Models\User;
use App\Services\AccountUserCurrencyService;
use App\Services\Banking\InteractiveBrokersClient;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
class InteractiveBrokersController extends Controller
class InteractiveBrokersController extends OpenBankingConnectController
{
use CreatesAccountsFromPending;
use HandlesSubscriptionGate;
/**
* Validate the Flex credentials and create a connection.
*/
public function store(ConnectInteractiveBrokersRequest $request, AccountUserCurrencyService $accountUserCurrencyService): JsonResponse
{
$validated = $request->validated();
$user = auth()->user();
return $this->connect($request->validated(), $accountUserCurrencyService);
}
if ($this->shouldBlockOpenBankingAccess($user)) {
return $this->subscribeJsonResponse();
}
protected function provider(): BankingProvider
{
return BankingProvider::InteractiveBrokers;
}
protected function providerName(): string
{
return 'Interactive Brokers';
}
protected function bankLogo(): ?string
{
return '/images/banks/logos/interactive-brokers.png';
}
protected function aspspCountry(array $validated): string
{
return 'US';
}
protected function fetchProviderData(array $validated): mixed
{
$client = new InteractiveBrokersClient($validated['token'], $validated['query_id']);
try {
$accounts = $client->fetchStatement();
} catch (\Throwable $e) {
Log::warning('Interactive Brokers connection validation failed', ['error' => $e->getMessage()]);
return $client->fetchStatement();
}
return response()->json([
'message' => $this->connectErrorMessage($e),
], 422);
}
if (empty($accounts)) {
return response()->json([
'message' => 'No accounts found in the Flex statement. Check that your Flex Query includes the NAV section.',
], 422);
}
$bank = Bank::firstOrCreate(
['name' => 'Interactive Brokers', 'user_id' => null],
['name' => 'Interactive Brokers', 'logo' => '/images/banks/logos/interactive-brokers.png'],
);
$connection = $user->bankingConnections()->create([
'provider' => BankingProvider::InteractiveBrokers,
...BankingProvider::InteractiveBrokers->credentialColumns($validated),
'aspsp_name' => 'Interactive Brokers',
'aspsp_country' => 'US',
'aspsp_logo' => $bank->logo,
'status' => BankingConnectionStatus::Pending,
]);
$connection->update([
'status' => BankingConnectionStatus::AwaitingMapping,
'pending_accounts_data' => $this->buildPendingAccounts($accounts),
]);
if (! $user->isOnboarded()) {
$this->createAccountsFromPending($user, $connection, $accountUserCurrencyService);
SyncBankingConnectionJob::dispatch($connection);
return response()->json([
'redirect_url' => route('onboarding', ['step' => 'create-account']),
'connection_id' => $connection->id,
]);
}
return response()->json([
'redirect_url' => route('open-banking.map-accounts', $connection),
'connection_id' => $connection->id,
]);
protected function validationFailureLogMessage(): string
{
return 'Interactive Brokers connection validation failed';
}
/**
* Turn a Flex failure into a message the user can act on: bad credentials,
* a busy/rate-limited service, or a statement that is still generating.
*/
private function connectErrorMessage(\Throwable $e): string
protected function credentialErrorMessage(\Throwable $e): string
{
if ($e instanceof RequestException && in_array($e->response->status(), [401, 403], true)) {
return 'Invalid Flex token or query ID, or failed to connect to Interactive Brokers.';
@ -108,17 +74,25 @@ class InteractiveBrokersController extends Controller
return 'Invalid Flex token or query ID, or failed to connect to Interactive Brokers.';
}
protected function emptyProviderDataMessage(mixed $providerData): ?string
{
if (empty($providerData)) {
return 'No accounts found in the Flex statement. Check that your Flex Query includes the NAV section.';
}
return null;
}
/**
* Build pending accounts from the parsed Flex statement.
*
* @param array<string, array{account_id: string, currency: string, navByDate: array<string, float>, investedAmount: float|null}> $accounts
* @return array<int, array{uid: string, currency: string, name: string}>
* @param array<string, array{account_id: string, currency: string, navByDate: array<string, float>, investedAmount: float|null}> $providerData
*/
private function buildPendingAccounts(array $accounts): array
protected function buildPendingAccounts(mixed $providerData, User $user): array
{
$pending = [];
foreach ($accounts as $account) {
foreach ($providerData as $account) {
$pending[] = [
'uid' => $account['account_id'],
'currency' => $account['currency'] !== '' ? $account['currency'] : 'USD',

View File

@ -0,0 +1,130 @@
<?php
namespace App\Http\Controllers\OpenBanking;
use App\Enums\BankingConnectionStatus;
use App\Enums\BankingProvider;
use App\Http\Controllers\Controller;
use App\Http\Controllers\OpenBanking\Concerns\CreatesAccountsFromPending;
use App\Http\Controllers\OpenBanking\Concerns\HandlesSubscriptionGate;
use App\Jobs\SyncBankingConnectionJob;
use App\Models\Bank;
use App\Models\BankingConnection;
use App\Models\User;
use App\Services\AccountUserCurrencyService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
abstract class OpenBankingConnectController extends Controller
{
use CreatesAccountsFromPending;
use HandlesSubscriptionGate;
abstract protected function provider(): BankingProvider;
/**
* Display name used for both the Bank record and the connection's aspsp_name.
*/
abstract protected function providerName(): string;
abstract protected function bankLogo(): ?string;
/**
* @param array<string, mixed> $validated
*/
abstract protected function aspspCountry(array $validated): string;
/**
* Build the provider client, validate the credentials, and return whatever
* data is needed to build pending accounts (or null). Must throw on failure.
*
* @param array<string, mixed> $validated
*/
abstract protected function fetchProviderData(array $validated): mixed;
abstract protected function validationFailureLogMessage(): string;
abstract protected function credentialErrorMessage(\Throwable $e): string;
/**
* @return array<int, array{uid: string, currency: string, name: string}>
*/
abstract protected function buildPendingAccounts(mixed $providerData, User $user): array;
/**
* Optional guard: return a 422 message when the validated provider data is
* unusable (e.g. an empty statement). Returning null keeps the flow going.
*/
protected function emptyProviderDataMessage(mixed $providerData): ?string
{
return null;
}
/**
* Shared connect flow: gate on subscription, validate the credentials, create
* the pending connection, then auto-map (onboarding) or redirect to mapping.
*
* @param array<string, mixed> $validated
*/
protected function connect(array $validated, AccountUserCurrencyService $accountUserCurrencyService): JsonResponse
{
$user = auth()->user();
if ($this->shouldBlockOpenBankingAccess($user)) {
return $this->subscribeJsonResponse();
}
try {
$providerData = $this->fetchProviderData($validated);
} catch (\Throwable $e) {
Log::warning($this->validationFailureLogMessage(), ['error' => $e->getMessage()]);
return response()->json([
'message' => $this->credentialErrorMessage($e),
], 422);
}
if (($message = $this->emptyProviderDataMessage($providerData)) !== null) {
return response()->json(['message' => $message], 422);
}
$bank = Bank::firstOrCreate(
['name' => $this->providerName(), 'user_id' => null],
['name' => $this->providerName(), 'logo' => $this->bankLogo()],
);
$connection = $user->bankingConnections()->create([
'provider' => $this->provider(),
...$this->provider()->credentialColumns($validated),
'aspsp_name' => $this->providerName(),
'aspsp_country' => $this->aspspCountry($validated),
'aspsp_logo' => $bank->logo,
'status' => BankingConnectionStatus::Pending,
]);
$connection->update([
'status' => BankingConnectionStatus::AwaitingMapping,
'pending_accounts_data' => $this->buildPendingAccounts($providerData, $user),
]);
return $this->connectionResponse($user, $connection, $accountUserCurrencyService);
}
private function connectionResponse(User $user, BankingConnection $connection, AccountUserCurrencyService $accountUserCurrencyService): JsonResponse
{
if (! $user->isOnboarded()) {
$this->createAccountsFromPending($user, $connection, $accountUserCurrencyService);
SyncBankingConnectionJob::dispatch($connection);
return response()->json([
'redirect_url' => route('onboarding', ['step' => 'create-account']),
'connection_id' => $connection->id,
]);
}
return response()->json([
'redirect_url' => route('open-banking.map-accounts', $connection),
'connection_id' => $connection->id,
]);
}
}