diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php
index 6481f87d30..4174accacf 100644
--- a/app/Helpers/Bank/Nordigen/Nordigen.php
+++ b/app/Helpers/Bank/Nordigen/Nordigen.php
@@ -23,7 +23,6 @@ use App\Models\Company;
use App\Services\Email\Email;
use App\Models\BankIntegration;
use App\Services\Email\EmailObject;
-use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use Illuminate\Mail\Mailables\Address;
@@ -34,7 +33,7 @@ class Nordigen
{
public bool $test_mode; // https://developer.gocardless.com/bank-account-data/sandbox
- public string $sandbox_institutionId = 'SANDBOXFINANCE_SFIN0000';
+ public string $sandbox_institutionId = "SANDBOXFINANCE_SFIN0000";
protected \Nordigen\NordigenPHP\API\NordigenClient $client;
@@ -61,116 +60,70 @@ class Nordigen
return $this->client->institution->getInstitutions();
}
- /**
- * Get end user agreement details by ID.
- *
- * @return array{
- * id: string,
- * created: string,
- * institution_id: string,
- * max_historical_days: int,
- * access_valid_for_days: int,
- * access_scope: string[],
- * accepted: string
- * } Agreement details
- */
- public function getAgreement(string $euaId): array
+ // requisition-section
+ public function createRequisition(string $redirect, string $institutionId, string $reference, string $userLanguage)
{
- return $this->client->endUserAgreement->getEndUserAgreement($euaId);
- }
-
- /**
- * Get a list of end user agreements
- *
- * @return array{
- * id: string,
- * created: string,
- * institution_id: string,
- * max_historical_days: int,
- * access_valid_for_days: int,
- * access_scope: string[],
- * accepted: ?string,
- * }[] EndUserAgreement list
- */
- public function firstValidAgreement(string $institutionId, int $accessDays, int $txDays): ?array
- {
- $requiredScopes = ['balances', 'details', 'transactions'];
-
- try {
- return Arr::first(
- $this->client->endUserAgreement->getEndUserAgreements()['results'],
- function (array $eua) use ($institutionId, $requiredScopes, $accessDays, $txDays): bool {
- return $eua['institution_id'] === $institutionId
- && $eua['accepted'] === null
- && $eua['max_historical_days'] >= $txDays
- && $eua['access_valid_for_days'] >= $accessDays
- && !array_diff($requiredScopes, $eua['access_scope'] ?? []);
- },
- null
- );
- } catch (\Exception $e) {
- $debug = "{$e->getMessage()} ({$e->getCode()})";
-
- nlog("Nordigen: Unable to fetch End User Agreements for institution '{$institutionId}': {$debug}");
-
- return null;
- }
- }
-
- /**
- * Create a new End User Agreement with the given parameters
- *
- * @param array{id: string, transaction_total_days: int, max_access_valid_for_days: int} $institution
- *
- * @throws \Nordigen\NordigenPHP\Exceptions\NordigenExceptions\NordigenException
- *
- * @return array{
- * id: string,
- * created: string,
- * institution_id: string,
- * max_historical_days: int,
- * access_valid_for_days: int,
- * access_scope: string[],
- * accepted: string
- * }|null Agreement details
- */
- public function createAgreement(array $institution, int $accessDays, int $transactionDays): ?array
- {
- $txDays = $transactionDays < 30 ? 30 : $transactionDays;
- $maxAccess = $institution['max_access_valid_for_days'];
- $maxTx = $institution['transaction_total_days'];
-
- return $this->client->endUserAgreement->createEndUserAgreement(
- accessValidForDays: $accessDays > $maxAccess ? $maxAccess : $accessDays,
- maxHistoricalDays: $txDays > $maxTx ? $maxTx : $txDays,
- institutionId: $institution['id'],
- );
- }
-
- /**
- * Create a new Bank Requisition
- *
- * @param array{id: string} $institution,
- * @param array{id: ?string, transaction_total_days: int} $agreement
- */
- public function createRequisition(
- string $redirect,
- array $institution,
- array $agreement,
- string $reference,
- string $userLanguage,
- ): array {
- if ($this->test_mode && $institution['id'] != $this->sandbox_institutionId) {
+ if ($this->test_mode && $institutionId != $this->sandbox_institutionId) {
throw new \Exception('invalid institutionId while in test-mode');
}
- return $this->client->requisition->createRequisition(
- $redirect,
- $institution['id'],
- $agreement['id'] ?? null,
- $reference,
- $userLanguage
- );
+ return $this->client->requisition->createRequisition($redirect, $institutionId, $this->getExtendedEndUserAggreementId($institutionId), $reference, $userLanguage);
+ }
+
+ private function getExtendedEndUserAggreementId(string $institutionId): string|null
+ {
+
+ $endUserAggreements = null;
+ $endUserAgreement = null;
+
+ // try to fetch endUserAgreements
+ try {
+ $endUserAggreements = $this->client->endUserAgreement->getEndUserAgreements();
+ } catch (\Exception $e) { // not able to accept it
+ nlog("Nordigen: Was not able to fetch endUserAgreements. We continue with defaults to setup bank_integration. {$institutionId} {$e->getMessage()} {$e->getCode()}");
+
+ return null;
+ }
+
+ // try to find an existing valid endUserAgreement
+ foreach ($endUserAggreements["results"] as $row) {
+ $endUserAgreement = $row;
+
+ // Validate Institution
+ if ($endUserAgreement["institution_id"] != $institutionId)
+ continue;
+
+ // Validate Access Scopes
+ $requiredScopes = ["balances", "details", "transactions"];
+ if (isset($endUserAgreement['access_scope']) && array_diff($requiredScopes, $endUserAgreement['access_scope']))
+ continue;
+
+ // try to accept the endUserAgreement when not already accepted
+ if (empty($endUserAgreement["accepted"]))
+ try {
+ $this->client->endUserAgreement->acceptEndUserAgreement($endUserAgreement["id"], request()->userAgent(), request()->ip());
+ } catch (\Exception $e) { // not able to accept it
+ nlog("Nordigen: Was not able to confirm an existing outstanding endUserAgreement for this institution. We now try to find another or will create and confirm a new one. {$institutionId} {$endUserAgreement["id"]} {$e->getMessage()} {$e->getCode()}");
+ $endUserAgreement = null;
+
+ continue;
+ }
+
+ break;
+ }
+
+ // try to create and accept an endUserAgreement
+ if (!$endUserAgreement)
+ try {
+ $endUserAgreement = $this->client->endUserAgreement->createEndUserAgreement($institutionId, ['details', 'balances', 'transactions'], 90, 180);
+ $this->client->endUserAgreement->acceptEndUserAgreement($endUserAgreement["id"], request()->userAgent(), request()->ip());
+ } catch (\Exception $e) { // not able to create this for this institution
+ nlog("Nordigen: Was not able to create and confirm a new endUserAgreement for this institution. We continue with defaults to setup bank_integration. {$institutionId} {$e->getMessage()} {$e->getCode()}");
+
+ return null;
+ }
+
+ return $endUserAgreement["id"];
}
public function getRequisition(string $requisitionId)
@@ -178,7 +131,7 @@ class Nordigen
try {
return $this->client->requisition->getRequisition($requisitionId);
} catch (\Exception $e) {
- if (strpos($e->getMessage(), 'Invalid Requisition ID') !== false) {
+ if (strpos($e->getMessage(), "Invalid Requisition ID") !== false) {
return false;
}
@@ -192,10 +145,10 @@ class Nordigen
try {
$out = new \stdClass();
- $out->data = $this->client->account($account_id)->getAccountDetails()['account'];
+ $out->data = $this->client->account($account_id)->getAccountDetails()["account"];
$out->metadata = $this->client->account($account_id)->getAccountMetaData();
- $out->balances = $this->client->account($account_id)->getAccountBalances()['balances'];
- $out->institution = $this->client->institution->getInstitution($out->metadata['institution_id']);
+ $out->balances = $this->client->account($account_id)->getAccountBalances()["balances"];
+ $out->institution = $this->client->institution->getInstitution($out->metadata["institution_id"]);
$it = new AccountTransformer();
return $it->transform($out);
@@ -227,9 +180,8 @@ class Nordigen
try {
$account = $this->client->account($account_id)->getAccountMetaData();
- if ($account['status'] != 'READY') {
- nlog("Nordigen account '{$account_id}' is not ready (status={$account['status']})");
-
+ if ($account["status"] != "READY") {
+ nlog('nordigen account was not in status ready. accountId: ' . $account_id . ' status: ' . $account["status"]);
return false;
}
@@ -238,7 +190,7 @@ class Nordigen
nlog("Nordigen:: AccountActiveStatus:: {$e->getMessage()} {$e->getCode()}");
- if (strpos($e->getMessage(), 'Invalid Account ID') !== false) {
+ if (strpos($e->getMessage(), "Invalid Account ID") !== false) {
return false;
}
@@ -288,4 +240,4 @@ class Nordigen
}
-}
+}
\ No newline at end of file
diff --git a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php
index 82b0261176..3306c5d27b 100644
--- a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php
+++ b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php
@@ -1,4 +1,5 @@
$nordigen_account->metadata["id"],
'provider_id' => $nordigen_account->institution["id"],
'provider_name' => $nordigen_account->institution["name"],
- 'provider_history' => $nordigen_account->institution["transaction_total_days"],
'nickname' => isset($nordigen_account->data["ownerName"]) ? $nordigen_account->data["ownerName"] : '',
'current_balance' => (float) $used_balance ? $used_balance["balanceAmount"]["amount"] : 0,
'account_currency' => $used_balance ? $used_balance["balanceAmount"]["currency"] : '',
diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php
index aa919dac33..56da6023a6 100644
--- a/app/Http/Controllers/Bank/NordigenController.php
+++ b/app/Http/Controllers/Bank/NordigenController.php
@@ -17,170 +17,216 @@ use App\Http\Requests\Nordigen\ConfirmNordigenBankIntegrationRequest;
use App\Http\Requests\Nordigen\ConnectNordigenBankIntegrationRequest;
use App\Jobs\Bank\ProcessBankTransactionsNordigen;
use App\Models\BankIntegration;
-use App\Models\Company;
use App\Utils\Ninja;
use Cache;
-use Illuminate\Database\Eloquent\ModelNotFoundException;
-use Illuminate\Contracts\View\View;
-use Illuminate\Http\JsonResponse;
-use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Nordigen\NordigenPHP\Exceptions\NordigenExceptions\NordigenException;
class NordigenController extends BaseController
{
/**
- * Handles the initial bank connection flow
+ * VIEW: Connect Nordigen Bank Integration
+ * @param ConnectNordigenBankIntegrationRequest $request
*/
- public function connect(ConnectNordigenBankIntegrationRequest $request): View|RedirectResponse
+ public function connect(ConnectNordigenBankIntegrationRequest $request)
{
$data = $request->all();
+
+ /** @var array $context */
$context = $request->getTokenContent();
+ $company = $request->getCompany();
+ $lang = substr($company->locale(), 0, 2);
+ $context["lang"] = $lang;
if (!$context) {
- return $this->failed('token-invalid', ['lang' => 'en']);
+ return view('bank.nordigen.handler', [
+ 'lang' => $lang,
+ 'failed_reason' => "token-invalid",
+ "redirectUrl" => config("ninja.app_url") . "?action=nordigen_connect&status=failed&reason=token-invalid",
+ ]);
+ }
+
+ $context["redirect"] = $data["redirect"];
+ if ($context["context"] != "nordigen" || array_key_exists("requisitionId", $context)) {
+ return view('bank.nordigen.handler', [
+ 'lang' => $lang,
+ 'failed_reason' => "token-invalid",
+ "redirectUrl" => ($context["redirect"]) . "?action=nordigen_connect&status=failed&reason=token-invalid",
+ ]);
}
$company = $request->getCompany();
- $context['redirect'] = $data['redirect'];
- $context['lang'] = $lang = substr($company->locale(), 0, 2);
-
- if ($context['context'] != 'nordigen' || array_key_exists('requisitionId', $context)) {
- return $this->failed('token-invalid', $context);
- }
+ $account = $company->account;
if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) {
- return $this->failed('account-config-invalid', $context, $company);
- }
-
- if (!(Ninja::isSelfHost() || (Ninja::isHosted() && $company->account->isEnterprisePaidClient()))) {
- return $this->failed('not-available', $context, $company);
- }
-
- $nordigen = new Nordigen();
- $institutions = $nordigen->getInstitutions();
-
- // show bank_selection_screen, when institution_id is not present
- if (!isset($data['institution_id'], $data['tx_days'])) {
return view('bank.nordigen.handler', [
'lang' => $lang,
'company' => $company,
'account' => $company->account,
- 'institutions' => $institutions,
- 'institutionId' => $data['institution_id'] ?? null,
- 'redirectUrl' => $context['redirect'] . '?action=nordigen_connect&status=user-aborted'
+ 'failed_reason' => "account-config-invalid",
+ "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid",
]);
}
- $institution = array_values(array_filter($institutions, function ($institution) use ($data) {
- return $institution['id'] == $data['institution_id'];
- }))[0];
+ if (!(Ninja::isSelfHost() || (Ninja::isHosted() && $account->isEnterprisePaidClient()))) {
+ return view('bank.nordigen.handler', [
+ 'lang' => $lang,
+ 'company' => $company,
+ 'account' => $company->account,
+ 'failed_reason' => "not-available",
+ "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=not-available",
+ ]);
+ }
- try {
- $txDays = $data['tx_days'] ?? 0;
+ $nordigen = new Nordigen();
- $agreement = $nordigen->firstValidAgreement($institution['id'], $data['access_days'] ?? 0, $txDays)
- ?? $nordigen->createAgreement($institution, $data['access_days'] ?? 9999, $txDays);
- } catch (\Exception $e) {
- $debug = "{$e->getMessage()} ({$e->getCode()})";
-
- nlog("Nordigen: Could not create an agreement with ${institution['name']}: {$debug}");
-
- return $this->failed('eua-failure', $context, $company);
+ // show bank_selection_screen, when institution_id is not present
+ if (!array_key_exists("institution_id", $data)) {
+ return view('bank.nordigen.handler', [
+ 'lang' => $lang,
+ 'company' => $company,
+ 'account' => $company->account,
+ 'institutions' => $nordigen->getInstitutions(),
+ 'redirectUrl' => $context["redirect"] . "?action=nordigen_connect&status=user-aborted"
+ ]);
}
// redirect to requisition flow
try {
- $requisition = $nordigen->createRequisition(
- config('ninja.app_url') . '/nordigen/confirm',
- $institution,
- $agreement,
- $request->token,
- $lang,
- );
+ $requisition = $nordigen->createRequisition(config('ninja.app_url') . '/nordigen/confirm', $data['institution_id'], $request->token, $lang);
} catch (NordigenException $e) { // TODO: property_exists returns null in these cases... => why => therefore we just get unknown error everytime $responseBody is typeof GuzzleHttp\Psr7\Stream
$responseBody = (string) $e->getResponse()->getBody();
- if (str_contains($responseBody, '"institution_id"')) {
- return $this->failed('institution-invalid', $context, $company);
+ if (str_contains($responseBody, '"institution_id"')) { // provided institution_id was wrong
+ return view('bank.nordigen.handler', [
+ 'lang' => $lang,
+ 'company' => $company,
+ 'account' => $company->account,
+ 'failed_reason' => "institution-invalid",
+ "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=institution-invalid",
+ ]);
+ } elseif (str_contains($responseBody, '"reference"')) { // this error can occur, when a reference was used double or is invalid => therefor we suggest the frontend to use another token
+ return view('bank.nordigen.handler', [
+ 'lang' => $lang,
+ 'company' => $company,
+ 'account' => $company->account,
+ 'failed_reason' => "token-invalid",
+ "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=token-invalid",
+ ]);
+ } else {
+ nlog("Unknown Error from nordigen: " . $e);
+ nlog($responseBody);
+
+ return view('bank.nordigen.handler', [
+ 'lang' => $lang,
+ 'company' => $company,
+ 'account' => $company->account,
+ 'failed_reason' => "unknown",
+ "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=unknown",
+ ]);
}
-
- // Reference invalid or already used, try a new token
- if (str_contains($responseBody, '"reference"')) {
- return $this->failed('token-invalid', $context, $company);
- }
-
- nlog("Unknown Error from nordigen: {$e}");
- nlog($responseBody);
-
- return $this->failed('unknown', $context, $company);
}
// save cache
- $context['requisitionId'] = $requisition['id'];
+ $context["requisitionId"] = $requisition["id"];
Cache::put($request->token, $context, 3600);
- return response()->redirectTo($requisition['link']);
+ return response()->redirectTo($requisition["link"]);
}
/**
- * Handles the OAuth redirect and account setup after bank authentication
+ * VIEW: Confirm Nordigen Bank Integration (redirect after nordigen flow)
+ * @param ConfirmNordigenBankIntegrationRequest $request
*/
- public function confirm(ConfirmNordigenBankIntegrationRequest $request): View|RedirectResponse
+ public function confirm(ConfirmNordigenBankIntegrationRequest $request)
{
$data = $request->all();
$company = $request->getCompany();
+ $account = $company->account;
$lang = substr($company->locale(), 0, 2);
/** @var array $context */
$context = $request->getTokenContent();
if (!array_key_exists('lang', $data) && $context['lang'] != 'en') {
- return redirect()->route('nordigen.confirm', array_merge(['lang' => $context['lang']], $request->query()));
+ return redirect()->route('nordigen.confirm', array_merge(["lang" => $context['lang']], $request->query()));
}
- if (!$context || $context['context'] != 'nordigen' || !array_key_exists('requisitionId', $context)) {
- return $this->failed('ref-invalid', $context);
+ if (!$context || $context["context"] != "nordigen" || !array_key_exists("requisitionId", $context)) {
+ return view('bank.nordigen.handler', [
+ 'lang' => $lang,
+ 'failed_reason' => "ref-invalid",
+ "redirectUrl" => ($context && array_key_exists("redirect", $context) ? $context["redirect"] : config('ninja.app_url')) . "?action=nordigen_connect&status=failed&reason=ref-invalid",
+ ]);
}
- if (!config('ninja.nordigen.secret_id') || !config('ninja.nordigen.secret_key')) {
- return $this->failed('account-config-invalid', $context, $company);
+ if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) {
+ return view('bank.nordigen.handler', [
+ 'lang' => $lang,
+ 'company' => $company,
+ 'account' => $company->account,
+ 'failed_reason' => "account-config-invalid",
+ "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid",
+ ]);
}
- if (!(Ninja::isSelfHost() || (Ninja::isHosted() && $company->account->isEnterprisePaidClient()))) {
- return $this->failed('not-available', $context, $company);
+ if (!(Ninja::isSelfHost() || (Ninja::isHosted() && $account->isEnterprisePaidClient()))) {
+ return view('bank.nordigen.handler', [
+ 'lang' => $lang,
+ 'company' => $company,
+ 'account' => $company->account,
+ 'failed_reason' => "not-available",
+ "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=not-available",
+ ]);
}
// fetch requisition
$nordigen = new Nordigen();
- $requisition = $nordigen->getRequisition($context['requisitionId']);
+ $requisition = $nordigen->getRequisition($context["requisitionId"]);
// check validity of requisition
if (!$requisition) {
- return $this->failed('requisition-not-found', $context, $company);
+ return view('bank.nordigen.handler', [
+ 'lang' => $lang,
+ 'company' => $company,
+ 'account' => $company->account,
+ 'failed_reason' => "requisition-not-found",
+ "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-not-found",
+ ]);
}
- if ($requisition['status'] != 'LN') {
- return $this->failed('requisition-invalid-status&status=' . $requisition['status'], $context, $company);
+ if ($requisition["status"] != "LN") {
+ return view('bank.nordigen.handler', [
+ 'lang' => $lang,
+ 'company' => $company,
+ 'account' => $company->account,
+ 'failed_reason' => "requisition-invalid-status",
+ "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-invalid-status&status=" . $requisition["status"],
+ ]);
}
- if (sizeof($requisition['accounts']) == 0) {
- return $this->failed('requisition-no-accounts', $context, $company);
+ if (sizeof($requisition["accounts"]) == 0) {
+ return view('bank.nordigen.handler', [
+ 'lang' => $lang,
+ 'company' => $company,
+ 'account' => $company->account,
+ 'failed_reason' => "requisition-no-accounts",
+ "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-no-accounts",
+ ]);
}
// connect new accounts
$bank_integration_ids = [];
- foreach ($requisition['accounts'] as $nordigenAccountId) {
+ foreach ($requisition["accounts"] as $nordigenAccountId) {
+
$nordigen_account = $nordigen->getAccount($nordigenAccountId);
if (isset($nordigen_account['error'])) {
continue;
}
- try {
- $bank_integration = $this->findIntegrationBy('account', $nordigen_account, $company);
+ $existing_bank_integration = BankIntegration::withTrashed()->where('nordigen_account_id', $nordigen_account['id'])->where('company_id', $company->id)->where('is_deleted', 0)->first();
+
+ if (!$existing_bank_integration) {
- $bank_integration->deleted_at = null;
- } catch (ModelNotFoundException $e) {
$bank_integration = new BankIntegration();
-
$bank_integration->integration_type = BankIntegration::INTEGRATION_TYPE_NORDIGEN;
$bank_integration->company_id = $company->id;
$bank_integration->account_id = $company->account_id;
@@ -188,82 +234,53 @@ class NordigenController extends BaseController
$bank_integration->nordigen_account_id = $nordigen_account['id'];
$bank_integration->bank_account_type = $nordigen_account['account_type'];
$bank_integration->bank_account_name = $nordigen_account['account_name'];
+ $bank_integration->bank_account_status = $nordigen_account['account_status'];
$bank_integration->bank_account_number = $nordigen_account['account_number'];
$bank_integration->nordigen_institution_id = $nordigen_account['provider_id'];
$bank_integration->provider_name = $nordigen_account['provider_name'];
$bank_integration->nickname = $nordigen_account['nickname'];
- $bank_integration->currency = $nordigen_account['account_currency'];
- } finally {
- $bank_integration->auto_sync = true;
- $bank_integration->disabled_upstream = false;
$bank_integration->balance = $nordigen_account['current_balance'];
- $bank_integration->bank_account_status = $nordigen_account['account_status'];
- $bank_integration->from_date = now()->subDays($nordigen_account['provider_history']);
+ $bank_integration->currency = $nordigen_account['account_currency'];
+ $bank_integration->disabled_upstream = false;
+ $bank_integration->auto_sync = true;
+ $bank_integration->from_date = now()->subDays(90); // default max-fetch interval of nordigen is 90 days
$bank_integration->save();
array_push($bank_integration_ids, $bank_integration->id);
+
+ } else {
+
+ // resetting metadata for account status
+ $existing_bank_integration->balance = $nordigen_account['current_balance'];
+ $existing_bank_integration->bank_account_status = $nordigen_account['account_status'];
+ $existing_bank_integration->disabled_upstream = false;
+ $existing_bank_integration->auto_sync = true;
+ $existing_bank_integration->from_date = now()->subDays(90); // default max-fetch interval of nordigen is 90 days
+ $existing_bank_integration->deleted_at = null;
+
+ $existing_bank_integration->save();
+
+ array_push($bank_integration_ids, $existing_bank_integration->id);
}
+
}
// perform update in background
- $company->account->bank_integrations
- ->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)
- ->where('auto_sync', true)
- ->each(function ($bank_integration) {
- ProcessBankTransactionsNordigen::dispatch($bank_integration);
- });
+ $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->each(function ($bank_integration) {
+ ProcessBankTransactionsNordigen::dispatch($bank_integration);
+ });
// prevent rerun of this method with same ref
- Cache::delete($data['ref']);
+ Cache::delete($data["ref"]);
// Successfull Response => Redirect
- return response()->redirectTo($context['redirect'] . '?action=nordigen_connect&status=success&bank_integrations=' . implode(',', $bank_integration_ids));
+ return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=success&bank_integrations=" . implode(',', $bank_integration_ids));
}
/**
- * Handles failure scenarios for Nordigen bank integrations
+ * Process Nordigen Institutions GETTER.
*
- * @param array{lang: string, redirect?: string}|null $context
- * @param array{account: array}|null $company
- */
- private function failed(string $reason, array $context, $company = null): View
- {
- $companyData = $company ? [
- 'company' => $company,
- 'account' => $company->account,
- ] : [];
-
- $url = $context['redirect'] ?? config('ninja.app_url');
-
- return view('bank.nordigen.handler', [
- ...$companyData,
- 'lang' => $context['lang'],
- 'failed_reason' => explode('&', $reason)[0],
- 'redirectUrl' => $url . '?action=nordigen_connect&status=failed&reason=' . $reason,
- ]);
- }
-
- /**
- * Find the first available Bank Integration from its Nordigen account or institution.
- *
- * @param 'account'|'institution' $key
- * @param array{id: string} $accountOrInstitution
- */
- private function findIntegrationBy(
- string $key,
- array $accountOrInstitution,
- Company $company,
- ): BankIntegration {
- return BankIntegration::withTrashed()
- ->where($key == 'id' ? 'id' : "nordigen_{$key}_id", $accountOrInstitution['id'])
- ->where('company_id', $company->id)
- ->where('is_deleted', 0)
- ->firstOrFail();
- }
-
- /**
- * Returns list of available banking institutions from Nordigen
*
* @OA\Post(
* path="/api/v1/nordigen/institutions",
@@ -295,14 +312,14 @@ class NordigenController extends BaseController
* ),
* )
*/
- public function institutions(Request $request): JsonResponse
+ public function institutions(Request $request)
{
if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) {
return response()->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/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php b/app/Http/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php
index 494be24c4d..7842289eb8 100644
--- a/app/Http/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php
+++ b/app/Http/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php
@@ -1,4 +1,5 @@
'required|string', // nordigen redirects only with the ref-property
'lang' => 'string',
];
}
-
- /**
- * @return array{
- * user_id: int,
- * company_key: string,
- * context: string,
- * is_react: bool,
- * institution_id: string,
- * lang: string,
- * redirect: string,
- * requisitionId: string
- * }
- */
- public function getTokenContent(): array
+ public function getTokenContent()
{
$input = $this->all();
@@ -58,12 +50,10 @@ class ConfirmNordigenBankIntegrationRequest extends Request
return $data;
}
- public function getCompany(): Company
+ public function getCompany()
{
- $key = $this->getTokenContent()['company_key'];
+ MultiDB::findAndSetDbByCompanyKey($this->getTokenContent()['company_key']);
- MultiDB::findAndSetDbByCompanyKey($key);
-
- return Company::where('company_key', $key)->firstOrFail();
+ return Company::where('company_key', $this->getTokenContent()['company_key'])->firstOrFail();
}
}
diff --git a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php
index 69c90008d4..d4b1e4f4f9 100644
--- a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php
+++ b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php
@@ -1,4 +1,5 @@
all();
@@ -45,24 +50,12 @@ class ConnectNordigenBankIntegrationRequest extends Request
$input['institution_id'] = $context['institution_id'];
}
- $input['redirect'] = ($context['is_react'] ?? false)
- ? config('ninja.react_url') . '/#/settings/bank_accounts'
- : config('ninja.app_url');
+ $input["redirect"] = isset($context["is_react"]) && $context['is_react'] ? config('ninja.react_url') . "/#/settings/bank_accounts" : config('ninja.app_url');
$this->replace($input);
- }
- /**
- * @return array{
- * user_id: int,
- * company_key: string,
- * context: string,
- * is_react: bool,
- * institution_id: string,
- * requisitionId?: string
- * }
- */
- public function getTokenContent(): ?array
+ }
+ public function getTokenContent()
{
if ($this->state) {
$this->token = $this->state;
@@ -73,12 +66,10 @@ class ConnectNordigenBankIntegrationRequest extends Request
return $data;
}
- public function getCompany(): Company
+ public function getCompany()
{
- $key = $this->getTokenContent()['company_key'];
+ MultiDB::findAndSetDbByCompanyKey($this->getTokenContent()['company_key']);
- MultiDB::findAndSetDbByCompanyKey($key);
-
- return Company::where('company_key', $key)->firstOrFail();
+ return Company::where('company_key', $this->getTokenContent()['company_key'])->firstOrFail();
}
}
diff --git a/resources/views/bank/nordigen/handler.blade.php b/resources/views/bank/nordigen/handler.blade.php
index 3ebdbfe18e..3044fb62dd 100644
--- a/resources/views/bank/nordigen/handler.blade.php
+++ b/resources/views/bank/nordigen/handler.blade.php
@@ -4,9 +4,6 @@
@push('head')
-
@endpush
@@ -30,7 +27,7 @@
// Logo URL that will be shown below the modal form.
logoUrl: "{{ ($account ?? false) && !$account->isPaid() ? asset('images/invoiceninja-black-logo-2.png') : (isset($company) && !is_null($company) ? $company->present()->logo() : asset('images/invoiceninja-black-logo-2.png')) }}",
// Will display country list with corresponding institutions. When `countryFilter` is set to `false`, only list of institutions will be shown.
- countryFilter: true,
+ countryFilter: false,
// style configs
styles: {
// Primary
@@ -48,83 +45,25 @@
buttonColor: '#3A53EE',
buttonTextColor: '#fff'
}
- }, createSelectionUI = (donor, institution, skippedSelect = false) => {
- const clone = donor.cloneNode(true),
- container = document.querySelector('.institution-container'),
- max_history = parseInt(institution.transaction_total_days),
- url = new URL(window.location.href);
-
- container.innerHTML = '';
- _changeHeading('Select your transaction history');
-
- clone.classList.replace('ob-list-institution', 'ob-history-option');
- clone.querySelector('.ob-span-text').innerText = `${max_history} days`;
- url.searchParams.set('institution_id', institutionId);
-
- // When we come from the renew button we need to replace the country flag
- if (skippedSelect) {
- const logo = document.createElement('img');
-
- logo.setAttribute('src', institution.logo)
- logo.setAttribute('class', 'ob-institution-logo');
-
- clone.querySelector('span.fi').replaceWith(logo)
- }
-
- for (let i = 30, next = 30; i <= max_history; i += next) {
- // If we're close to max, just use the real value
- if (max_history - i < 15) {
- continue;
- }
-
- const option = clone.cloneNode(true);
-
- url.searchParams.set('tx_days', i == 360 ? 365 : i);
-
- option.querySelector('.ob-span-text').innerText = `${i == 360 ? 365 : i} days`;
- option.querySelector('a').href = url.href;
- container.append(option);
-
- // 1, 2, 3, 4, 6, 9, 12, 14, 18, 24 months--as of 24/12/24, no bank exceeds 730 days of history
- next = i >= 500 ? 180 : i >= 400 ? 120 : i >= 360 ? 60 : i >= 180 ? 90 : i >= 120 ? 60 : 30;
- }
-
- url.searchParams.set('tx_days', max_history);
- clone.querySelector('a').href = url.href;
- container.append(clone);
};
- const failedReason = "{{ $failed_reason ?? '' }}".trim(),
- institutions = @json($institutions ?? []);
+ const failedReason = "{{ $failed_reason ?? '' }}".trim();
- let institutionId = "{{ $institutionId ?? '' }}";
+ new institutionSelector(@json($institutions ?? []), 'institution-modal-content', config);
- new institutionSelector(institutions, 'institution-modal-content', config);
+ if (!failedReason) {
- if (null !== institutionId && !failedReason) {
- createSelectionUI(
- document.querySelector('.ob-institution'),
- institutions.find(i => i.id == institutionId),
- true
- );
- } else if (!failedReason) {
- const observer = new MutationObserver((event) => {
- const institutionButtons = document.querySelectorAll('.ob-list-institution > a');
+ const institutionList = Array.from(document.querySelectorAll('.ob-list-institution > a'));
- Array.from(institutionButtons).forEach((button) => {
- button.addEventListener('click', (e) => {
- e.preventDefault();
-
- createSelectionUI(button.parentElement, institutions.find(
- i => i.id == button.getAttribute('data-institution')
- ));
- });
+ institutionList.forEach((institution) => {
+ institution.addEventListener('click', (e) => {
+ e.preventDefault()
+ const institutionId = institution.getAttribute('data-institution');
+ const url = new URL(window.location.href);
+ url.searchParams.set('institution_id', institutionId);
+ window.location.href = url.href;
});
});
-
- observer.observe(document.querySelector('.institution-container'), {
- childList: true,
- });
} else {
document.getElementsByClassName("institution-search-container")[0].remove();
document.getElementsByClassName("institution-container")[0].remove();
@@ -140,6 +79,7 @@
let restartFlow = false; // return, restart, refresh
heading.innerHTML = "{{ ctrans('texts.nordigen_handler_error_heading_unknown', [], $lang ?? 'en') }}";
contents.innerHTML = "{{ ctrans('texts.nordigen_handler_error_contents_unknown', [], $lang ?? 'en') }} " + failedReason;
+
switch (failedReason) {
// Connect Screen Errors
case "token-invalid":
@@ -159,10 +99,6 @@
heading.innerHTML = "{{ ctrans('texts.nordigen_handler_error_heading_institution_invalid', [], $lang ?? 'en') }}";
contents.innerHTML = "{{ ctrans('texts.nordigen_handler_error_contents_institution_invalid', [], $lang ?? 'en') }}";
break;
- case "eua-failure":
- heading.innerHTML = "{{ ctrans('texts.nordigen_handler_error_heading_eua_failure', [], $lang ?? 'en') }}";
- contents.innerHTML = "{{ ctrans('texts.nordigen_handler_error_contents_eua_failure', [], $lang ?? 'en') }} " + failedReason;
- break;
// Confirm Screen Errors
case "ref-invalid":
heading.innerHTML = "{{ ctrans('texts.nordigen_handler_error_heading_ref_invalid', [], $lang ?? 'en') }}";
@@ -195,20 +131,6 @@
wrapper.appendChild(returnButton);
}
- const backButton = document.querySelector('.institution-arrow-block');
- const backButtonObserver = new MutationObserver((records) => {
- const title = document.querySelector('#institution-modal-header h2').innerText;
-
- backButton.style.visibility = title == 'Select your country' ? 'hidden' : 'visible';
- backButton.style.display = 'flex';
- });
-
- backButton.style.display = 'flex';
- backButton.style.visibility = 'hidden';
-
- backButtonObserver.observe(document.querySelector('header h2'), {
- childList: true,
- });
-@endpush
+@endpush
\ No newline at end of file