$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 $validated */ abstract protected function fetchProviderData(array $validated): mixed; abstract protected function credentialErrorMessage(\Throwable $e): string; /** * @return array */ 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 $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('OpenBanking credential validation failed', [ 'provider' => $this->provider()->value, '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, ]); } }