From 67f94b7fb9066bf0ccd593fb3b25c18cdaf18e24 Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 4 Dec 2023 08:14:52 +0100 Subject: [PATCH] feat: institutions endpoint ready fix: wrong variable names fix: missing implementation in api-router --- app/Helpers/Bank/Nordigen/Nordigen.php | 39 +-- .../Controllers/Bank/NordigenController.php | 240 +++++++++++++++++- .../Controllers/BankIntegrationController.php | 12 +- .../CreateNortigenRequisitionRequest.php | 40 +++ .../Bank/ProcessBankTransactionsNordigen.php | 4 +- app/Jobs/Ninja/BankTransactionSync.php | 2 +- app/Models/Account.php | 4 +- routes/api.php | 13 +- 8 files changed, 303 insertions(+), 51 deletions(-) create mode 100644 app/Http/Requests/Nordigen/CreateNortigenRequisitionRequest.php diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index 18518847f5..ca62fd3af7 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -18,35 +18,7 @@ use App\Helpers\Bank\Nordigen\Transformer\AccountTransformer; use App\Helpers\Bank\Nordigen\Transformer\IncomeTransformer; use Illuminate\Support\Facades\Http; use Illuminate\Support\Str; - -// Generate new access token. Token is valid for 24 hours -// Token is automatically injected into every response -$token = $client->createAccessToken(); - -// Get access token -$accessToken = $client->getAccessToken(); -// Get refresh token -$refreshToken = $client->getRefreshToken(); - -// Exchange refresh token for new access token -$newToken = $client->refreshAccessToken($refreshToken); - -// Get list of institutions by country. Country should be in ISO 3166 standard. -$institutions = $client->institution->getInstitutionsByCountry("LV"); - -// Institution id can be gathered from getInstitutions response. -// Example Revolut ID -$institutionId = "REVOLUT_REVOGB21"; -$redirectUri = "https://nordigen.com"; - -// Initialize new bank connection session -$session = $client->initSession($institutionId, $redirectUri); - -// Get link to authorize in the bank -// Authorize with your bank via this link, to gain access to account data -$link = $session["link"]; -// requisition id is needed to get accountId in the next step -$requisitionId = $session["requisition_id"]; +use Illuminate\Support\Facades\Log; class Nordigen { @@ -56,17 +28,20 @@ class Nordigen protected \Nordigen\NordigenPHP\API\NordigenClient $client; - public function __construct(string $client_id, string $client_secret) + public function __construct(string $secret_id, string $secret_key) { - $this->client = new \Nordigen\NordigenPHP\API\NordigenClient($client_id, $client_secret); + Log::info($secret_id); + Log::info($secret_key); + $this->client = new \Nordigen\NordigenPHP\API\NordigenClient($secret_id, $secret_key); + + $this->client->createAccessToken(); // access_token is valid 24h -> so we dont have to implement a refresh-cycle } // metadata-section for frontend public function getInstitutions() { - if ($this->test_mode) return (array) $this->client->institution->getInstitution($this->sandbox_institutionId); diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 25f0819422..920519b375 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -13,6 +13,7 @@ namespace App\Http\Controllers\Bank; use App\Helpers\Bank\Nordigen\Nordigen; use App\Http\Controllers\BaseController; +use App\Http\Requests\Nortigen\CreateNortigenRequisitionRequest; use App\Http\Requests\Yodlee\YodleeAuthRequest; use App\Jobs\Bank\ProcessBankTransactionsNordigen; use App\Models\BankIntegration; @@ -36,11 +37,11 @@ class NordigenController extends BaseController //ensure user is enterprise!! - if ($company->account->bank_integration_nordigen_client_id && $company->account->bank_integration_nordigen_client_id) { + if ($company->account->bank_integration_nordigen_secret_id && $company->account->bank_integration_nordigen_secret_id) { $flow = 'edit'; - $token = $company->account->bank_integration_nordigen_client_id; + $token = $company->account->bank_integration_nordigen_secret_id; } else { @@ -50,7 +51,7 @@ class NordigenController extends BaseController $token = $response->user->loginName; - $company->account->bank_integration_nordigen_client_id = $token; + $company->account->bank_integration_nordigen_secret_id = $token; $company->push(); @@ -183,12 +184,241 @@ class NordigenController extends BaseController { $account = auth()->user()->account; - if (!$account->bank_integration_nordigen_client_id || !$account->bank_integration_nordigen_client_secret) + if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); - $nordigen = new Nordigen($account->bank_integration_nordigen_client_id, $account->bank_integration_nordigen_client_secret); + $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); return response()->json($nordigen->getInstitutions()); } + + /** + * Process Nordigen Institutions GETTER. + * + * + * @OA\Post( + * path="/api/v1/nordigen/institutions", + * operationId="nordigenRefreshWebhook", + * tags={"nordigen"}, + * summary="Getting available institutions from nordigen", + * description="Used to determine the available institutions for sending and creating a new connect-link", + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/Credit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + + /* + { + "event":{ + "info":"REFRESH.PROCESS_COMPLETED", + "loginName":"fri21", + "data":{ + "providerAccount":[ + { + "id":10995860, + "providerId":16441, + "isManual":false, + "createdDate":"2017-12-22T05:47:35Z", + "aggregationSource":"USER", + "status":"SUCCESS", + "requestId":"NSyMGo+R4dktywIu3hBIkc3PgWA=", + "dataset":[ + { + "name":"BASIC_AGG_DATA", + "additionalStatus":"AVAILABLE_DATA_RETRIEVED", + "updateEligibility":"ALLOW_UPDATE", + "lastUpdated":"2017-12-22T05:48:16Z", + "lastUpdateAttempt":"2017-12-22T05:48:16Z" + } + ] + } + ] + } + } + }*/ + public function refresh(Request $request) + { + $account = auth()->user()->account; + + if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) + return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); + + // TODO: call job execution + + return response()->json(['message' => 'Refresh Cycle started. This may take a while...']); + } + + /** Creates a new requisition (oAuth like connection of bank-account) + * + * @param CreateNortigenRequisitionRequest $request + * + * @OA\Post( + * path="/api/v1/nordigen/institutions", + * operationId="nordigenRefreshWebhook", + * tags={"nordigen"}, + * summary="Getting available institutions from nordigen", + * description="Used to determine the available institutions for sending and creating a new connect-link", + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/Credit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + + /* TODO + { + "event":{ + "info":"REFRESH.PROCESS_COMPLETED", + "loginName":"fri21", + "data":{ + "providerAccount":[ + { + "id":10995860, + "providerId":16441, + "isManual":false, + "createdDate":"2017-12-22T05:47:35Z", + "aggregationSource":"USER", + "status":"SUCCESS", + "requestId":"NSyMGo+R4dktywIu3hBIkc3PgWA=", + "dataset":[ + { + "name":"BASIC_AGG_DATA", + "additionalStatus":"AVAILABLE_DATA_RETRIEVED", + "updateEligibility":"ALLOW_UPDATE", + "lastUpdated":"2017-12-22T05:48:16Z", + "lastUpdateAttempt":"2017-12-22T05:48:16Z" + } + ] + } + ] + } + } + }*/ + public function connect(Request $request) // TODO: error, when using class CreateNortigenRequisitionRequest + { + $account = auth()->user()->account; + + if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) + return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); + + // TODO: should be moved to CreateNortigenRequisitionRequest + // $this->validate($request, [ + // 'redirect' => 'required|string|max:1000', + // 'institutionId' => 'required|string|max:100', + // ]); + + $data = $request->all(); + + $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); + + return response()->json(['result' => $nordigen->createRequisition($data['redirect'], $data['institutionId'])]); + } + + /** + * Process Nordigen Institutions GETTER. + * + * + * @OA\Post( + * path="/api/v1/nordigen/institutions", + * operationId="nordigenRefreshWebhook", + * tags={"nordigen"}, + * summary="Getting available institutions from nordigen", + * description="Used to determine the available institutions for sending and creating a new connect-link", + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Response( + * response=200, + * description="", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * @OA\JsonContent(ref="#/components/schemas/Credit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + + /* + { + "event":{ + "info":"REFRESH.PROCESS_COMPLETED", + "loginName":"fri21", + "data":{ + "providerAccount":[ + { + "id":10995860, + "providerId":16441, + "isManual":false, + "createdDate":"2017-12-22T05:47:35Z", + "aggregationSource":"USER", + "status":"SUCCESS", + "requestId":"NSyMGo+R4dktywIu3hBIkc3PgWA=", + "dataset":[ + { + "name":"BASIC_AGG_DATA", + "additionalStatus":"AVAILABLE_DATA_RETRIEVED", + "updateEligibility":"ALLOW_UPDATE", + "lastUpdated":"2017-12-22T05:48:16Z", + "lastUpdateAttempt":"2017-12-22T05:48:16Z" + } + ] + } + ] + } + } + }*/ + public function confirm(Request $request) + { + // TODO: use custom-token-auth from reference of request + } /** * Process Yodlee Refresh Webhook. * diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index e9b890893c..fcc5586e09 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -586,10 +586,10 @@ class BankIntegrationController extends BaseController private function refreshAccountsNordigen(Account $account) { - if (!$account->bank_integration_nordigen_client_id || !$account->bank_integration_nordigen_client_secret) + if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); - $nordigen = new Nordigen($account->bank_integration_nordigen_client_id, $account->bank_integration_nordigen_client_secret); + $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); $accounts = $nordigen->getAccounts(); @@ -664,8 +664,8 @@ class BankIntegrationController extends BaseController if ($bank_integration->integration_type == BankIntegration::INTEGRATION_TYPE_YODLEE) $this->removeAccountYodlee($account, $bank_integration); else if ($bank_integration->integration_type == BankIntegration::INTEGRATION_TYPE_NORDIGEN) - $this->removeAccountNordigen($account, $bank_integration); - + $this->removeAccountNordigen($account, $bank_integration); + $this->bank_integration_repo->delete($bank_integration); return $this->itemResponse($bank_integration->fresh()); @@ -682,10 +682,10 @@ class BankIntegrationController extends BaseController private function removeAccountNordigen(Account $account, BankIntegration $bank_integration) { - if (!$account->bank_integration_nordigen_client_id || !$account->bank_integration_nordigen_client_secret) + if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); - $nordigen = new Nordigen($account->bank_integration_nordigen_client_id, $account->bank_integration_nordigen_client_secret); + $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); $nordigen->deleteAccount($bank_integration->bank_account_id); } diff --git a/app/Http/Requests/Nordigen/CreateNortigenRequisitionRequest.php b/app/Http/Requests/Nordigen/CreateNortigenRequisitionRequest.php new file mode 100644 index 0000000000..85ea8bf3b1 --- /dev/null +++ b/app/Http/Requests/Nordigen/CreateNortigenRequisitionRequest.php @@ -0,0 +1,40 @@ + 'required|string|max:100', + 'institutionId' => 'required|string|max:100', + ]; + } +} diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index a71d649438..21b70875d8 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -75,7 +75,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue try { $this->processTransactions(); } catch (\Exception $e) { - nlog("{$this->account->bank_integration_nordigen_client_id} - exited abnormally => " . $e->getMessage()); + nlog("{$this->account->bank_integration_nordigen_secret_id} - exited abnormally => " . $e->getMessage()); return; } @@ -90,7 +90,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue private function processTransactions() { - $nordigen = new Nordigen($this->account->bank_integration_nordigen_client_id, $this->account->bank_integration_nordigen_client_secret); // TODO: maybe implement credentials + $nordigen = new Nordigen($this->account->bank_integration_nordigen_secret_id, $this->account->bank_integration_nordigen_secret_key); // TODO: maybe implement credentials if (!$nordigen->isAccountActive($this->bank_integration->bank_account_id)) { $this->bank_integration->disabled_upstream = true; diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php index 8840661ea7..19c1e9e43b 100644 --- a/app/Jobs/Ninja/BankTransactionSync.php +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -69,7 +69,7 @@ class BankTransactionSync implements ShouldQueue nlog("syncing transactions - nordigen"); - Account::with('bank_integrations')->whereNotNull('bank_integration_nordigen_client_id')->andWhereNotNull('bank_integration_nordigen_client_secret')->cursor()->each(function ($account) { + Account::with('bank_integrations')->whereNotNull('bank_integration_nordigen_secret_id')->andWhereNotNull('bank_integration_nordigen_secret_key')->cursor()->each(function ($account) { $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->andWhere('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { diff --git a/app/Models/Account.php b/app/Models/Account.php index bc36fad082..71adb711d4 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -60,8 +60,8 @@ class Account extends BaseModel 'set_react_as_default_ap', 'inapp_transaction_id', 'num_users', - 'bank_integration_nordigen_client_id', - 'bank_integration_nordigen_client_secret', + 'bank_integration_nordigen_secret_id', + 'bank_integration_nordigen_secret_key', ]; /** diff --git a/routes/api.php b/routes/api.php index bd443d6d73..7e3a78cec2 100644 --- a/routes/api.php +++ b/routes/api.php @@ -18,6 +18,7 @@ use App\Http\Controllers\BankIntegrationController; use App\Http\Controllers\BankTransactionController; use App\Http\Controllers\BankTransactionRuleController; use App\Http\Controllers\Bank\YodleeController; +use App\Http\Controllers\Bank\NordigenController; use App\Http\Controllers\BaseController; use App\Http\Controllers\ChartController; use App\Http\Controllers\ClientController; @@ -103,7 +104,7 @@ Route::group(['middleware' => ['throttle:300,1', 'api_secret_check']], function Route::post('api/v1/oauth_login', [LoginController::class, 'oauthApiLogin']); }); -Route::group(['middleware' => ['throttle:50,1','api_secret_check','email_db']], function () { +Route::group(['middleware' => ['throttle:50,1', 'api_secret_check', 'email_db']], function () { Route::post('api/v1/login', [LoginController::class, 'apiLogin'])->name('login.submit')->middleware('throttle:20,1'); Route::post('api/v1/reset_password', [ForgotPasswordController::class, 'sendResetLinkEmail']); }); @@ -309,7 +310,7 @@ Route::group(['middleware' => ['throttle:300,1', 'api_db', 'token_auth', 'locale Route::post('verify', [TwilioController::class, 'generate'])->name('verify.generate')->middleware('throttle:100,1'); Route::post('verify/confirm', [TwilioController::class, 'confirm'])->name('verify.confirm'); - + Route::resource('vendors', VendorController::class); // name = (vendors. index / create / show / update / destroy / edit Route::post('vendors/bulk', [VendorController::class, 'bulk'])->name('vendors.bulk'); Route::put('vendors/{vendor}/upload', [VendorController::class, 'upload']); @@ -370,9 +371,15 @@ Route::post('api/v1/get_migration_account', [HostedMigrationController::class, ' Route::post('api/v1/confirm_forwarding', [HostedMigrationController::class, 'confirmForwarding'])->middleware('guest')->middleware('throttle:100,1'); Route::post('api/v1/process_webhook', [AppleController::class, 'process_webhook'])->middleware('throttle:1000,1'); Route::post('api/v1/confirm_purchase', [AppleController::class, 'confirm_purchase'])->middleware('throttle:1000,1'); + Route::post('api/v1/yodlee/refresh', [YodleeController::class, 'refreshWebhook'])->middleware('throttle:100,1'); Route::post('api/v1/yodlee/data_updates', [YodleeController::class, 'dataUpdatesWebhook'])->middleware('throttle:100,1'); Route::post('api/v1/yodlee/refresh_updates', [YodleeController::class, 'refreshUpdatesWebhook'])->middleware('throttle:100,1'); Route::post('api/v1/yodlee/balance', [YodleeController::class, 'balanceWebhook'])->middleware('throttle:100,1'); -Route::fallback([BaseController::class, 'notFound']); \ No newline at end of file +Route::get('api/v1/nordigen/institutions', [NordigenController::class, 'institutions'])->middleware('throttle:100,1')->middleware('token_auth')->name('nordigen_institutions'); +Route::any('api/v1/nordigen/refresh', [NordigenController::class, 'refresh'])->middleware('throttle:100,1')->middleware('token_auth')->name('nordigen_refresh'); +Route::post('api/v1/nordigen/connect', [NordigenController::class, 'connect'])->middleware('throttle:100,1')->middleware('token_auth')->name('nordigen_connect'); +Route::any('api/v1/nordigen/callback', [NordigenController::class, 'callback'])->middleware('throttle:100,1')->name('nordigen_callback'); + +Route::fallback([BaseController::class, 'notFound']);