user(); if ($this->shouldBlockOpenBankingAccess($user)) { return $this->subscribeJsonResponse(); } try { $client = new PlaidClient; $result = $client->createLinkToken((string) $user->id); return response()->json([ 'link_token' => $result['link_token'], 'expiration' => $result['expiration'], ]); } catch (\Throwable $e) { Log::error('Failed to create Plaid link token', [ 'user_id' => $user->id, 'error' => $e->getMessage(), ]); return response()->json([ 'message' => 'Failed to create Plaid Link token. Please check your Plaid configuration.', ], 500); } } /** * Exchange a Plaid public_token for an access_token and create a connection. */ public function store(Request $request, AccountUserCurrencyService $accountUserCurrencyService): JsonResponse { $user = auth()->user(); if ($this->shouldBlockOpenBankingAccess($user)) { return $this->subscribeJsonResponse(); } $request->validate([ 'public_token' => ['required', 'string', 'min:10'], ]); try { $client = new PlaidClient; $exchange = $client->exchangePublicToken($request->public_token); $accessToken = $exchange['access_token']; $itemId = $exchange['item_id']; // Fetch accounts from Plaid to build pending accounts $clientWithAccess = new PlaidClient($accessToken, $itemId); $plaidAccounts = $clientWithAccess->getAccounts(); $pendingAccounts = $this->buildPendingAccounts($plaidAccounts['accounts'] ?? []); if ($pendingAccounts === []) { return response()->json(['message' => 'No accounts found for this Plaid Link token.'], 422); } $bank = Bank::firstOrCreate( ['name' => 'Plaid', 'user_id' => null], ['name' => 'Plaid', 'logo' => null], ); $connection = $user->bankingConnections()->create([ 'provider' => BankingProvider::Plaid, 'api_token' => $accessToken, 'api_secret' => $itemId, 'aspsp_name' => 'Plaid', 'aspsp_country' => 'US', 'aspsp_logo' => $bank->logo, '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, ]); } catch (\Throwable $e) { Log::error('Failed to connect Plaid', [ 'user_id' => $user->id, 'error' => $e->getMessage(), ]); return response()->json([ 'message' => 'Failed to connect Plaid. Please try again.', ], 422); } } /** * @param array $plaidAccounts * @return array */ private function buildPendingAccounts(array $plaidAccounts): array { $pending = []; foreach ($plaidAccounts as $account) { $accountId = $account['account_id'] ?? null; if ($accountId === null) { continue; } $currency = $account['balances']['iso_currency_code'] ?? 'USD'; $name = $account['official_name'] ?? $account['name'] ?? 'Plaid Account'; $pending[] = [ 'uid' => $accountId, 'currency' => $currency, 'name' => $name, ]; } return $pending; } }