diff --git a/app/Helpers/Bank/Nordigen/Http/Client.php b/app/Helpers/Bank/Nordigen/Http/Client.php new file mode 100644 index 0000000000..df51cb660e --- /dev/null +++ b/app/Helpers/Bank/Nordigen/Http/Client.php @@ -0,0 +1,910 @@ +baseUrl = rtrim($baseUrl, '/'); + $this->accessToken = $accessToken; + $this->httpClient = Http::withHeaders([ + 'Authorization' => "Bearer {$this->accessToken}", + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ])->timeout(30); + } + + // ==================== REQUISITIONS ==================== + + /** + * Get all requisitions with pagination + */ + public function getRequisitions(int $limit = 100, ?string $offset = null): Collection + { + $params = ['limit' => $limit]; + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/requisitions/", $params); + + return $this->handlePaginatedResponse($response, 'requisitions'); + } + + /** + * Get a specific requisition by ID + */ + public function getRequisition(string $requisitionId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/requisitions/{$requisitionId}/"); + + return $this->handleResponse($response); + } + + /** + * Create a new requisition + */ + public function createRequisition(array $data): ?array + { + $response = $this->httpClient->post("{$this->baseUrl}/requisitions/", $data); + + return $this->handleResponse($response); + } + + /** + * Update a requisition + */ + public function updateRequisition(string $requisitionId, array $data): ?array + { + $response = $this->httpClient->put("{$this->baseUrl}/requisitions/{$requisitionId}/", $data); + + return $this->handleResponse($response); + } + + /** + * Delete a requisition + */ + public function deleteRequisition(string $requisitionId): bool + { + $response = $this->httpClient->delete("{$this->baseUrl}/requisitions/{$requisitionId}/"); + + return $response->successful(); + } + + /** + * Get all requisitions with full pagination + */ + public function getAllRequisitions(): Collection + { + $allRequisitions = collect(); + $offset = null; + $limit = 100; + + do { + $requisitions = $this->getRequisitions($limit, $offset); + + if ($requisitions->isEmpty()) { + break; + } + + $allRequisitions = $allRequisitions->merge($requisitions); + + $lastRequisition = $requisitions->last(); + $offset = $lastRequisition['id'] ?? null; + + } while ($requisitions->count() === $limit && $offset); + + return $allRequisitions; + } + + // ==================== AGREEMENTS ==================== + + /** + * Get all agreements with pagination + */ + public function getAgreements(int $limit = 100, ?string $offset = null): Collection + { + $params = ['limit' => $limit]; + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/agreements/", $params); + + return $this->handlePaginatedResponse($response, 'agreements'); + } + + /** + * Get a specific agreement by ID + */ + public function getAgreement(string $agreementId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/agreements/{$agreementId}/"); + + return $this->handleResponse($response); + } + + /** + * Create a new agreement + */ + public function createAgreement(array $data): ?array + { + $response = $this->httpClient->post("{$this->baseUrl}/agreements/", $data); + + return $this->handleResponse($response); + } + + /** + * Update an agreement + */ + public function updateAgreement(string $agreementId, array $data): ?array + { + $response = $this->httpClient->put("{$this->baseUrl}/agreements/{$agreementId}/", $data); + + return $this->handleResponse($response); + } + + /** + * Delete an agreement + */ + public function deleteAgreement(string $agreementId): bool + { + $response = $this->httpClient->delete("{$this->baseUrl}/agreements/{$agreementId}/"); + + return $response->successful(); + } + + /** + * Get all agreements with full pagination + */ + public function getAllAgreements(): Collection + { + $allAgreements = collect(); + $offset = null; + $limit = 100; + + do { + $agreements = $this->getAgreements($limit, $offset); + + if ($agreements->isEmpty()) { + break; + } + + $allAgreements = $allAgreements->merge($agreements); + + $lastAgreement = $agreements->last(); + $offset = $lastAgreement['id'] ?? null; + + } while ($agreements->count() === $limit && $offset); + + return $allAgreements; + } + + // ==================== INSTITUTIONS ==================== + + /** + * Get all institutions with pagination + */ + public function getInstitutions(int $limit = 100, ?string $offset = null): Collection + { + $params = ['limit' => $limit]; + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/institutions/", $params); + + return $this->handlePaginatedResponse($response, 'institutions'); + } + + /** + * Get a specific institution by ID + */ + public function getInstitution(string $institutionId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/institutions/{$institutionId}/"); + + return $this->handleResponse($response); + } + + /** + * Get institutions by country + */ + public function getInstitutionsByCountry(string $countryCode, int $limit = 100, ?string $offset = null): Collection + { + $params = ['limit' => $limit, 'country' => $countryCode]; + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/institutions/", $params); + + return $this->handlePaginatedResponse($response, 'institutions'); + } + + /** + * Get all institutions with full pagination + */ + public function getAllInstitutions(): Collection + { + $allInstitutions = collect(); + $offset = null; + $limit = 100; + + do { + $institutions = $this->getInstitutions($limit, $offset); + + if ($institutions->isEmpty()) { + break; + } + + $allInstitutions = $allInstitutions->merge($institutions); + + $lastInstitution = $institutions->last(); + $offset = $lastInstitution['id'] ?? null; + + } while ($institutions->count() === $limit && $offset); + + return $allInstitutions; + } + + // ==================== ENDUSER AGREEMENTS ==================== + + /** + * Get all enduser agreements with pagination + */ + public function getEnduserAgreements(int $limit = 100, ?string $offset = null): Collection + { + $params = ['limit' => $limit]; + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/enduser-agreements/", $params); + + return $this->handlePaginatedResponse($response, 'enduser_agreements'); + } + + /** + * Get a specific enduser agreement by ID + */ + public function getEnduserAgreement(string $agreementId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/enduser-agreements/{$agreementId}/"); + + return $this->handleResponse($response); + } + + /** + * Create a new enduser agreement + */ + public function createEnduserAgreement(array $data): ?array + { + $response = $this->httpClient->post("{$this->baseUrl}/enduser-agreements/", $data); + + return $this->handleResponse($response); + } + + /** + * Update an enduser agreement + */ + public function updateEnduserAgreement(string $agreementId, array $data): ?array + { + $response = $this->httpClient->put("{$this->baseUrl}/enduser-agreements/{$agreementId}/", $data); + + return $this->handleResponse($response); + } + + /** + * Delete an enduser agreement + */ + public function deleteEnduserAgreement(string $agreementId): bool + { + $response = $this->httpClient->delete("{$this->baseUrl}/enduser-agreements/{$agreementId}/"); + + return $response->successful(); + } + + // ==================== BANK ACCOUNTS ==================== + + /** + * Get all bank accounts with pagination + */ + public function getBankAccounts(int $limit = 100, ?string $offset = null): Collection + { + $params = ['limit' => $limit]; + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/accounts/", $params); + + return $this->handlePaginatedResponse($response, 'accounts'); + } + + /** + * Get a specific bank account by ID + */ + public function getBankAccount(string $accountId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/accounts/{$accountId}/"); + + return $this->handleResponse($response); + } + + /** + * Get account balances + */ + public function getAccountBalances(string $accountId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/accounts/{$accountId}/balances/"); + + return $this->handleResponse($response); + } + + /** + * Get account details + */ + public function getAccountDetails(string $accountId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/accounts/{$accountId}/details/"); + + return $this->handleResponse($response); + } + + /** + * Get account metadata + */ + public function getAccountMetadata(string $accountId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/accounts/{$accountId}/metadata/"); + + return $this->handleResponse($response); + } + + /** + * Get account holder information + */ + public function getAccountHolder(string $accountId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/accounts/{$accountId}/holder/"); + + return $this->handleResponse($response); + } + + /** + * Get account status + */ + public function getAccountStatus(string $accountId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/accounts/{$accountId}/status/"); + + return $this->handleResponse($response); + } + + /** + * Get all bank accounts with full pagination + */ + public function getAllBankAccounts(): Collection + { + $allAccounts = collect(); + $offset = null; + $limit = 100; + + do { + $accounts = $this->getBankAccounts($limit, $offset); + + if ($accounts->isEmpty()) { + break; + } + + $allAccounts = $allAccounts->merge($accounts); + + $lastAccount = $accounts->last(); + $offset = $lastAccount['id'] ?? null; + + } while ($accounts->count() === $limit && $offset); + + return $allAccounts; + } + + // ==================== TRANSACTIONS ==================== + + /** + * Get account transactions with pagination + */ + public function getAccountTransactions( + string $accountId, + ?string $dateFrom = null, + ?string $dateTo = null, + int $limit = 100, + ?string $offset = null + ): Collection { + $params = ['limit' => $limit]; + + if ($dateFrom) { + $params['date_from'] = $dateFrom; + } + + if ($dateTo) { + $params['date_to'] = $dateTo; + } + + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/accounts/{$accountId}/transactions/", $params); + + return $this->handlePaginatedResponse($response, 'transactions'); + } + + /** + * Get all transactions across all accounts with pagination + */ + public function getAllTransactions( + ?string $dateFrom = null, + ?string $dateTo = null, + int $limit = 100, + ?string $offset = null + ): Collection { + $params = ['limit' => $limit]; + + if ($dateFrom) { + $params['date_from'] = $dateFrom; + } + + if ($dateTo) { + $params['date_to'] = $dateTo; + } + + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/transactions/", $params); + + return $this->handlePaginatedResponse($response, 'transactions'); + } + + /** + * Get account transactions with full pagination support + */ + public function getAllAccountTransactions( + string $accountId, + ?string $dateFrom = null, + ?string $dateTo = null + ): Collection { + $allTransactions = collect(); + $offset = null; + $limit = 100; + + do { + $transactions = $this->getAccountTransactions($accountId, $dateFrom, $dateTo, $limit, $offset); + + if ($transactions->isEmpty()) { + break; + } + + $allTransactions = $allTransactions->merge($transactions); + + $lastTransaction = $transactions->last(); + $offset = $lastTransaction['id'] ?? null; + + } while ($transactions->count() === $limit && $offset); + + return $allTransactions; + } + + /** + * Get account transactions with specific status + */ + public function getAccountTransactionsByStatus( + string $accountId, + string $status, + int $limit = 100, + ?string $offset = null + ): Collection { + $params = [ + 'limit' => $limit, + 'status' => $status + ]; + + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/accounts/{$accountId}/transactions/", $params); + + return $this->handlePaginatedResponse($response, 'transactions'); + } + + /** + * Get account transactions by category + */ + public function getAccountTransactionsByCategory( + string $accountId, + string $category, + int $limit = 100, + ?string $offset = null + ): Collection { + $params = [ + 'limit' => $limit, + 'category' => $category + ]; + + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/accounts/{$accountId}/transactions/", $params); + + return $this->handlePaginatedResponse($response, 'transactions'); + } + + /** + * Search transactions + */ + public function searchTransactions( + string $accountId, + string $query, + int $limit = 100, + ?string $offset = null + ): Collection { + $params = [ + 'limit' => $limit, + 'search' => $query + ]; + + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/accounts/{$accountId}/transactions/", $params); + + return $this->handlePaginatedResponse($response, 'transactions'); + } + + /** + * Get account transactions with amount range + */ + public function getAccountTransactionsByAmount( + string $accountId, + float $minAmount, + float $maxAmount, + int $limit = 100, + ?string $offset = null + ): Collection { + $params = [ + 'limit' => $limit, + 'min_amount' => $minAmount, + 'max_amount' => $maxAmount + ]; + + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/accounts/{$accountId}/transactions/", $params); + + return $this->handlePaginatedResponse($response, 'transactions'); + } + + // ==================== PAYMENTS ==================== + + /** + * Get all payments with pagination + */ + public function getPayments(int $limit = 100, ?string $offset = null): Collection + { + $params = ['limit' => $limit]; + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/payments/", $params); + + return $this->handlePaginatedResponse($response, 'payments'); + } + + /** + * Get a specific payment by ID + */ + public function getPayment(string $paymentId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/payments/{$paymentId}/"); + + return $this->handleResponse($response); + } + + /** + * Create a new payment + */ + public function createPayment(array $data): ?array + { + $response = $this->httpClient->post("{$this->baseUrl}/payments/", $data); + + return $this->handleResponse($response); + } + + /** + * Update a payment + */ + public function updatePayment(string $paymentId, array $data): ?array + { + $response = $this->httpClient->put("{$this->baseUrl}/payments/{$paymentId}/", $data); + + return $this->handleResponse($response); + } + + /** + * Delete a payment + */ + public function deletePayment(string $paymentId): bool + { + $response = $this->httpClient->delete("{$this->baseUrl}/payments/{$paymentId}/"); + + return $response->successful(); + } + + // ==================== MANDATES ==================== + + /** + * Get all mandates with pagination + */ + public function getMandates(int $limit = 100, ?string $offset = null): Collection + { + $params = ['limit' => $limit]; + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/mandates/", $params); + + return $this->handlePaginatedResponse($response, 'mandates'); + } + + /** + * Get a specific mandate by ID + */ + public function getMandate(string $mandateId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/mandates/{$mandateId}/"); + + return $this->handleResponse($response); + } + + /** + * Create a new mandate + */ + public function createMandate(array $data): ?array + { + $response = $this->httpClient->post("{$this->baseUrl}/mandates/", $data); + + return $this->handleResponse($response); + } + + /** + * Update a mandate + */ + public function updateMandate(string $mandateId, array $data): ?array + { + $response = $this->httpClient->put("{$this->baseUrl}/mandates/{$mandateId}/", $data); + + return $this->handleResponse($response); + } + + /** + * Delete a mandate + */ + public function deleteMandate(string $mandateId): bool + { + $response = $this->httpClient->delete("{$this->baseUrl}/mandates/{$mandateId}/"); + + return $response->successful(); + } + + // ==================== REFUNDS ==================== + + /** + * Get all refunds with pagination + */ + public function getRefunds(int $limit = 100, ?string $offset = null): Collection + { + $params = ['limit' => $limit]; + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/refunds/", $params); + + return $this->handlePaginatedResponse($response, 'refunds'); + } + + /** + * Get a specific refund by ID + */ + public function getRefund(string $refundId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/refunds/{$refundId}/"); + + return $this->handleResponse($response); + } + + /** + * Create a new refund + */ + public function createRefund(array $data): ?array + { + $response = $this->httpClient->post("{$this->baseUrl}/refunds/", $data); + + return $this->handleResponse($response); + } + + // ==================== EVENTS ==================== + + /** + * Get all events with pagination + */ + public function getEvents(int $limit = 100, ?string $offset = null): Collection + { + $params = ['limit' => $limit]; + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/events/", $params); + + return $this->handlePaginatedResponse($response, 'events'); + } + + /** + * Get a specific event by ID + */ + public function getEvent(string $eventId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/events/{$eventId}/"); + + return $this->handleResponse($response); + } + + // ==================== WEBHOOKS ==================== + + /** + * Get all webhooks with pagination + */ + public function getWebhooks(int $limit = 100, ?string $offset = null): Collection + { + $params = ['limit' => $limit]; + if ($offset) { + $params['offset'] = $offset; + } + + $response = $this->httpClient->get("{$this->baseUrl}/webhooks/", $params); + + return $this->handlePaginatedResponse($response, 'webhooks'); + } + + /** + * Get a specific webhook by ID + */ + public function getWebhook(string $webhookId): ?array + { + $response = $this->httpClient->get("{$this->baseUrl}/webhooks/{$webhookId}/"); + + return $this->handleResponse($response); + } + + /** + * Create a new webhook + */ + public function createWebhook(array $data): ?array + { + $response = $this->httpClient->post("{$this->baseUrl}/webhooks/", $data); + + return $this->handleResponse($response); + } + + /** + * Update a webhook + */ + public function updateWebhook(string $webhookId, array $data): ?array + { + $response = $this->httpClient->put("{$this->baseUrl}/webhooks/{$webhookId}/", $data); + + return $this->handleResponse($response); + } + + /** + * Delete a webhook + */ + public function deleteWebhook(string $webhookId): bool + { + $response = $this->httpClient->delete("{$this->baseUrl}/webhooks/{$webhookId}/"); + + return $response->successful(); + } + + // ==================== UTILITY METHODS ==================== + + /** + * Handle paginated response + */ + private function handlePaginatedResponse(Response $response, string $dataKey): Collection + { + if (!$response->successful()) { + $this->logError('Paginated request failed', $response); + return collect(); + } + + $data = $response->json(); + + if (!isset($data[$dataKey])) { + return collect(); + } + + return collect($data[$dataKey]); + } + + /** + * Handle single response + */ + private function handleResponse(Response $response): ?array + { + if (!$response->successful()) { + $this->logError('Request failed', $response); + return null; + } + + return $response->json(); + } + + /** + * Log error responses + */ + private function logError(string $message, Response $response): void + { + Log::error($message, [ + 'status' => $response->status(), + 'body' => $response->body(), + 'headers' => $response->headers() + ]); + } + + /** + * Set custom timeout + */ + public function setTimeout(int $seconds): self + { + $this->httpClient = $this->httpClient->timeout($seconds); + return $this; + } + + /** + * Set custom headers + */ + public function setHeaders(array $headers): self + { + $this->httpClient = $this->httpClient->withHeaders($headers); + return $this; + } + + /** + * Get the underlying HTTP client + */ + public function getHttpClient(): PendingRequest + { + return $this->httpClient; + } +} diff --git a/app/Http/Controllers/Bank/NordigenV2Controller.php b/app/Http/Controllers/Bank/NordigenV2Controller.php new file mode 100644 index 0000000000..eaf11d117f --- /dev/null +++ b/app/Http/Controllers/Bank/NordigenV2Controller.php @@ -0,0 +1,63 @@ +json(['message' => 'Not yet authenticated with Nordigen Bank Integration service'], 400); + } + + $nordigen = new Nordigen(); + + return response()->json($nordigen->getInstitutions()); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index 1bae87b7ca..7e9c9f20e9 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -281,6 +281,8 @@ class TaskController extends BaseController $task = $this->task_repo->save($request->all(), $task); } + $task = $this->task_repo->triggeredActions($request, $task); + if (is_null($task->status_order) || $task->status_order != $old_task_status_order) { $this->task_repo->sortStatuses($task); } diff --git a/database/migrations/2025_08_06_094338_add_requisiton_id_to_bank_integrations_table.php b/database/migrations/2025_08_06_094338_add_requisiton_id_to_bank_integrations_table.php deleted file mode 100644 index b54415324d..0000000000 --- a/database/migrations/2025_08_06_094338_add_requisiton_id_to_bank_integrations_table.php +++ /dev/null @@ -1,26 +0,0 @@ -string('requisition_id')->nullable(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - - } -};