From e1fbbe426820fc917c701b4f928b9b437e88c9cf Mon Sep 17 00:00:00 2001 From: paulwer Date: Tue, 31 Jan 2023 10:34:39 +0100 Subject: [PATCH 01/69] adding webhooks for products --- app/Models/Webhook.php | 17 ++++++++++++++++- app/Observers/ProductObserver.php | 24 +++++++++++++++++++++--- app/Repositories/BaseRepository.php | 10 ++++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/app/Models/Webhook.php b/app/Models/Webhook.php index 6e412e908db7..7566023ff0fa 100644 --- a/app/Models/Webhook.php +++ b/app/Models/Webhook.php @@ -116,6 +116,16 @@ class Webhook extends BaseModel const EVENT_RESTORE_VENDOR = 49; + const EVENT_CREATE_PRODUCT = 50; + + const EVENT_UPDATE_PRODUCT = 51; + + const EVENT_ARCHIVE_PRODUCT = 52; + + const EVENT_RESTORE_PRODUCT = 53; + + const EVENT_DELETE_PRODUCT = 54; + @@ -169,7 +179,12 @@ class Webhook extends BaseModel self::EVENT_RESTORE_QUOTE, self::EVENT_RESTORE_INVOICE, self::EVENT_RESTORE_PAYMENT, - self::EVENT_RESTORE_VENDOR + self::EVENT_RESTORE_VENDOR, + self::EVENT_CREATE_PRODUCT, + self::EVENT_UPDATE_PRODUCT, + self::EVENT_ARCHIVE_PRODUCT, + self::EVENT_RESTORE_PRODUCT, + self::EVENT_DELETE_PRODUCT ]; diff --git a/app/Observers/ProductObserver.php b/app/Observers/ProductObserver.php index 11707fe6a3c5..cf759b798151 100644 --- a/app/Observers/ProductObserver.php +++ b/app/Observers/ProductObserver.php @@ -23,7 +23,13 @@ class ProductObserver */ public function created(Product $product) { - // + $subscriptions = Webhook::where('company_id', $product->company->id) + ->where('event_id', Webhook::EVENT_CREATE_PRODUCT) + ->exists(); + + if ($subscriptions) { + WebhookHandler::dispatch(Webhook::EVENT_CREATE_PRODUCT, $product, $product->company)->delay(now()->addSeconds(2)); + } } /** @@ -34,7 +40,13 @@ class ProductObserver */ public function updated(Product $product) { - // + $subscriptions = Webhook::where('company_id', $product->company->id) + ->where('event_id', Webhook::EVENT_UPDATE_PRODUCT) + ->exists(); + + if ($subscriptions) { + WebhookHandler::dispatch(Webhook::EVENT_UPDATE_PRODUCT, $product, $product->company)->delay(now()->addSeconds(2)); + } } /** @@ -45,7 +57,13 @@ class ProductObserver */ public function deleted(Product $product) { - // + $subscriptions = Webhook::where('company_id', $product->company->id) + ->where('event_id', Webhook::EVENT_DELETE_PRODUCT) + ->exists(); + + if ($subscriptions) { + WebhookHandler::dispatch(Webhook::EVENT_DELETE_PRODUCT, $product, $product->company)->delay(now()->addSeconds(2)); + } } /** diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php index 6370b0d4236f..6fb22e85e411 100644 --- a/app/Repositories/BaseRepository.php +++ b/app/Repositories/BaseRepository.php @@ -20,6 +20,7 @@ use App\Models\Credit; use App\Models\Expense; use App\Models\Invoice; use App\Models\Payment; +use App\Models\Product; use App\Models\Project; use App\Models\Quote; use App\Models\RecurringInvoice; @@ -143,6 +144,13 @@ class BaseRepository else { $webhookEvent = Webhook::EVENT_ARCHIVE_PROJECT;} break; + case $entity instanceof Product: + if ($restore){ + $webhookEvent = Webhook::EVENT_RESTORE_PRODUCT; + } + else { + $webhookEvent = Webhook::EVENT_ARCHIVE_PRODUCT;} + break; case $entity instanceof Client: if ($restore){ $webhookEvent = Webhook::EVENT_RESTORE_CLIENT; @@ -185,6 +193,8 @@ class BaseRepository case $webhookEvent == Webhook::EVENT_RESTORE_CREDIT: case $webhookEvent == Webhook::EVENT_RESTORE_CLIENT: case $webhookEvent == Webhook::EVENT_ARCHIVE_CLIENT: + case $webhookEvent == Webhook::EVENT_RESTORE_PRODUCT: + case $webhookEvent == Webhook::EVENT_ARCHIVE_PRODUCT: WebhookHandler::dispatch($webhookEvent, $entity, $entity->company)->delay(now()->addSeconds(2)); break; default: From 8a8c3b85c37f036ad2c08e2a9df52394ea3afe9d Mon Sep 17 00:00:00 2001 From: paulwer Date: Thu, 30 Nov 2023 16:00:50 +0100 Subject: [PATCH 02/69] first draft --- app/Exceptions/NordigenApiException.php | 41 + app/Helpers/Bank/Nordigen/Nordigen.php | 126 + .../Transformer/AccountTransformer.php | 104 + .../Transformer/ExpenseTransformer.php | 80 + .../Transformer/IncomeTransformer.php | 187 + .../Controllers/Bank/NordigenController.php | 301 ++ app/Models/BankIntegration.php | 11 +- composer.json | 8 +- composer.lock | 3005 +++++++++-------- ...3_11_26_082959_add_bank_integration_id.php | 38 + public/mix-manifest.json | 94 +- 11 files changed, 2618 insertions(+), 1377 deletions(-) create mode 100644 app/Exceptions/NordigenApiException.php create mode 100644 app/Helpers/Bank/Nordigen/Nordigen.php create mode 100644 app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php create mode 100644 app/Helpers/Bank/Nordigen/Transformer/ExpenseTransformer.php create mode 100644 app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php create mode 100644 app/Http/Controllers/Bank/NordigenController.php create mode 100644 database/migrations/2023_11_26_082959_add_bank_integration_id.php diff --git a/app/Exceptions/NordigenApiException.php b/app/Exceptions/NordigenApiException.php new file mode 100644 index 000000000000..bc81d968a470 --- /dev/null +++ b/app/Exceptions/NordigenApiException.php @@ -0,0 +1,41 @@ +getMessage() && strlen($this->getMessage()) >= 1) { + $msg = $this->getMessage(); + } + + return response()->json([ + 'message' => $msg, + ], 400); + } +} diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php new file mode 100644 index 000000000000..35f18aad5e72 --- /dev/null +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -0,0 +1,126 @@ +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"]; + +class Nordigen +{ + public bool $test_mode = false; + + protected \Nordigen\NordigenPHP\API\NordigenClient $client; + + protected string $secret_id; + + protected string $secret_key; + + public function __construct() + { + $this->secret_id = config('ninja.nordigen.secret_id'); + + $this->secret_key = config('ninja.nordigen.secret_key'); + + $this->client = new \Nordigen\NordigenPHP\API\NordigenClient($this->secret_id, $this->secret_key); + } + + public function getInstitutions() + { + return $this->client->institution->getInstitutions(); + } + + public function getValidAccounts() + { + + // get all valid requisitions + $requisitions = $this->client->requisition->getRequisitions(); + + // fetch all valid accounts for activated requisitions + $accounts = []; + foreach ($requisitions as $requisition) { + foreach ($requisition->accounts as $account) { + $account = $account = $this->client->account($account); + + array_push($accounts, $account); + } + } + + return $accounts; + + } + + public function cleanup() + { + $requisitions = $this->client->requisition->getRequisitions(); + + // TODO: filter to older than 2 days created AND (no accounts or invalid) + + foreach ($requisitions as $requisition) { + $this->client->requisition->deleteRequisition($requisition->id); + } + } + + // account-section: these methods should be used to get data of connected accounts + + public function getAccountMetaData(string $account_id) + { + return $this->client->account($account_id)->getAccountMetaData(); + } + + public function getAccountDetails(string $account_id) + { + return $this->client->account($account_id)->getAccountDetails(); + } + + public function getAccountBalances(string $account_id) + { + return $this->client->account($account_id)->getAccountBalances(); + } + + public function getAccountTransactions(string $account_id) + { + return $this->client->account($account_id)->getAccountTransactions(); + } + +} diff --git a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php new file mode 100644 index 000000000000..e2b90656fb6f --- /dev/null +++ b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php @@ -0,0 +1,104 @@ + stdClass Object +( + [CONTAINER] => bank + [providerAccountId] => 11308693 + [accountName] => My CD - 8878 + [accountStatus] => ACTIVE + [accountNumber] => xxxx8878 + [aggregationSource] => USER + [isAsset] => 1 + [balance] => stdClass Object + ( + [currency] => USD + [amount] => 49778.07 + ) + + [id] => 12331861 + [includeInNetWorth] => 1 + [providerId] => 18769 + [providerName] => Dag Site Captcha + [isManual] => + [currentBalance] => stdClass Object + ( + [currency] => USD + [amount] => 49778.07 + ) + + [accountType] => CD + [displayedName] => LORETTA + [createdDate] => 2022-07-28T06:55:33Z + [lastUpdated] => 2022-07-28T06:56:09Z + [dataset] => Array + ( + [0] => stdClass Object + ( + [name] => BASIC_AGG_DATA + [additionalStatus] => AVAILABLE_DATA_RETRIEVED + [updateEligibility] => ALLOW_UPDATE + [lastUpdated] => 2022-07-28T06:55:50Z + [lastUpdateAttempt] => 2022-07-28T06:55:50Z + ) + + ) + +) + ) + */ + + +class AccountTransformer implements AccountTransformerInterface +{ + + public function transform($yodlee_account) + { + + $data = []; + + if(!property_exists($yodlee_account, 'account')) + return $data; + + foreach($yodlee_account->account as $account) + { + $data[] = $this->transformAccount($account); + } + + return $data; + } + + public function transformAccount($account) + { + + return [ + 'id' => $account->id, + 'account_type' => $account->CONTAINER, + // 'account_name' => $account->accountName, + 'account_name' => property_exists($account, 'accountName') ? $account->accountName : $account->nickname, + 'account_status' => $account->accountStatus, + 'account_number' => property_exists($account, 'accountNumber') ? '**** ' . substr($account?->accountNumber, -7) : '', + 'provider_account_id' => $account->providerAccountId, + 'provider_id' => $account->providerId, + 'provider_name' => $account->providerName, + 'nickname' => property_exists($account, 'nickname') ? $account->nickname : '', + 'current_balance' => property_exists($account, 'currentBalance') ? $account->currentBalance->amount : 0, + 'account_currency' => property_exists($account, 'currency') ? $account->currentBalance->currency : '', + ]; + } +} + + diff --git a/app/Helpers/Bank/Nordigen/Transformer/ExpenseTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/ExpenseTransformer.php new file mode 100644 index 000000000000..6274bb2ed911 --- /dev/null +++ b/app/Helpers/Bank/Nordigen/Transformer/ExpenseTransformer.php @@ -0,0 +1,80 @@ + bank +[id] => 103953585 +[amount] => stdClass Object + ( + [amount] => 480.66 + [currency] => USD + ) + +[categoryType] => UNCATEGORIZE +[categoryId] => 1 +[category] => Uncategorized +[categorySource] => SYSTEM +[highLevelCategoryId] => 10000017 +[createdDate] => 2022-08-04T21:50:17Z +[lastUpdated] => 2022-08-04T21:50:17Z +[description] => stdClass Object + ( + [original] => CHEROKEE NATION TAX TA TAHLEQUAH OK + ) + +[isManual] => +[sourceType] => AGGREGATED +[date] => 2022-08-03 +[transactionDate] => 2022-08-03 +[postDate] => 2022-08-03 +[status] => POSTED +[accountId] => 12331794 +[runningBalance] => stdClass Object + ( + [amount] => 480.66 + [currency] => USD + ) + +[checkNumber] => 998 +) +*/ + +class IncomeTransformer implements BankRevenueInterface +{ + use AppSetup; + + public function transform($transaction) + { + + $data = []; + + if(!property_exists($transaction, 'transaction')) + return $data; + + foreach($transaction->transaction as $transaction) + { + $data[] = $this->transformTransaction($transaction); + } + + return $data; + } + + public function transformTransaction($transaction) + { + + return [ + 'transaction_id' => $transaction->id, + 'amount' => $transaction->amount->amount, + 'currency_id' => $this->convertCurrency($transaction->amount->currency), + 'account_type' => $transaction->CONTAINER, + 'category_id' => $transaction->highLevelCategoryId, + 'category_type' => $transaction->categoryType, + 'date' => $transaction->date, + 'bank_account_id' => $transaction->accountId, + 'description' => $transaction->description->original, + 'base_type' => property_exists($transaction, 'baseType') ? $transaction->baseType : $this->calculateBaseType($transaction), + ]; + } + + private function calculateBaseType($transaction) + { + //CREDIT / DEBIT + + if(property_exists($transaction, 'highLevelCategoryId') && $transaction->highLevelCategoryId == 10000012) + return 'CREDIT'; + + return 'DEBIT'; + + } + + private function convertCurrency(string $code) + { + + $currencies = Cache::get('currencies'); + + if (! $currencies) { + $this->buildCache(true); + } + + $currency = $currencies->filter(function ($item) use($code){ + return $item->code == $code; + })->first(); + + if($currency) + return $currency->id; + + return 1; + + } + + +} + + diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php new file mode 100644 index 000000000000..fe5e0d2a456c --- /dev/null +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -0,0 +1,301 @@ +getCompany(); + + + //ensure user is enterprise!! + + if ($company->account->bank_integration_account_id) { + + $flow = 'edit'; + + $token = $company->account->bank_integration_account_id; + + } else { + + $flow = 'add'; + + $response = $yodlee->createUser($company); + + $token = $response->user->loginName; + + $company->account->bank_integration_account_id = $token; + + $company->push(); + + } + + $yodlee = new Yodlee($token); + + if ($request->has('window_closed') && $request->input("window_closed") == "true") + $this->getAccounts($company, $token); + + $data = [ + 'access_token' => $yodlee->getAccessToken(), + 'fasttrack_url' => $yodlee->getFastTrackUrl(), + 'config_name' => config('ninja.yodlee.config_name'), + 'flow' => $flow, + 'company' => $company, + 'account' => $company->account, + 'completed' => $request->has('window_closed') ? true : false, + ]; + + return view('bank.yodlee.auth', $data); + + } + + private function getAccounts($company, $token) + { + $nordigen = new Nordigen($token); + + $accounts = $nordigen->getAccounts(); + + foreach ($accounts as $account) { + + if (!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $company->id)->exists()) { + $bank_integration = new BankIntegration(); + $bank_integration->company_id = $company->id; + $bank_integration->account_id = $company->account_id; + $bank_integration->user_id = $company->owner()->id; + $bank_integration->bank_account_id = $account['id']; + $bank_integration->bank_account_type = $account['account_type']; + $bank_integration->bank_account_name = $account['account_name']; + $bank_integration->bank_account_status = $account['account_status']; + $bank_integration->bank_account_number = $account['account_number']; + $bank_integration->provider_id = $account['provider_id']; + $bank_integration->provider_name = $account['provider_name']; + $bank_integration->nickname = $account['nickname']; + $bank_integration->balance = $account['current_balance']; + $bank_integration->currency = $account['account_currency']; + $bank_integration->from_date = now()->subYear(); + + $bank_integration->save(); + } + + } + + + $company->account->bank_integrations->each(function ($bank_integration) use ($company) { + + ProcessBankTransactions::dispatch($company->account->bank_integration_account_id, $bank_integration); + + }); + + } + + + /** + * Process Yodlee Refresh Webhook. + * + * + * @OA\Post( + * path="/api/v1/yodlee/refresh", + * operationId="yodleeRefreshWebhook", + * tags={"yodlee"}, + * summary="Processing webhooks from Yodlee", + * description="Notifies the system when a data point can be refreshed", + * @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 refreshWebhook(Request $request) + { + //we should ignore this one + nlog("yodlee refresh"); + nlog($request->all()); + + return response()->json(['message' => 'Success'], 200); + + // + + // return response()->json(['message' => 'Unauthorized'], 403); + } + + /* + { + "event":{ + "notificationId":"63c73475-4db5-49ef-8553-8303337ca7c3", + "info":"LATEST_BALANCE_UPDATES", + "loginName":"user1", + "data":{ + "providerAccountId":658552, + "latestBalanceEvent":[ + { + "accountId":12345, + "status":"SUCCESS" + }, + { + "accountId":12346, + "status":"FAILED" + } + ] + } + } + } + */ + public function balanceWebhook(Request $request) + { + + nlog("yodlee refresh"); + nlog($request->all()); + + return response()->json(['message' => 'Success'], 200); + + // + + // return response()->json(['message' => 'Unauthorized'], 403); + } + + /* + { + "event":{ + "data":[ + { + "autoRefresh":{ + "additionalStatus":"SCHEDULED", + "status":"ENABLED" + }, + "accountIds":[ + 1112645899, + 1112645898 + ], + "loginName":"YSL1555332811628", + "providerAccountId":11381459 + } + ], + "notificationTime":"2019-06-14T04:49:39Z", + "notificationId":"4e672150-156048777", + "info":"AUTO_REFRESH_UPDATES" + } + } + */ + public function refreshUpdatesWebhook(Request $request) + { + //notifies a user if there are problems with yodlee accessing the data + nlog("update refresh"); + nlog($request->all()); + + return response()->json(['message' => 'Success'], 200); + + // + + // return response()->json(['message' => 'Unauthorized'], 403); + } + + + /* + "event": { + "notificationId": "64b7ed1a-1530523285", + "info": "DATA_UPDATES.USER_DATA", + "data": { + "userCount": 1, + "fromDate": "2017-11-10T10:18:44Z", + "toDate": "2017-11-10T11:18:43Z", + "userData": [{ + "user": { + "loginName": "YSL1484052178554" + }, + "links": [{ + "methodType": "GET", + "rel": "getUserData", + "href": "dataExtracts/userData?fromDate=2017-11-10T10:18:44Z&toDate=2017-11-10T11:18:43Z&loginName=YSL1484052178554" + }] + }] + } + } + */ + public function dataUpdatesWebhook(Request $request) + { + //this is the main hook we use for notifications + + nlog("data refresh"); + nlog($request->all()); + + return response()->json(['message' => 'Success'], 200); + + // + + // return response()->json(['message' => 'Unauthorized'], 403); + } + +} diff --git a/app/Models/BankIntegration.php b/app/Models/BankIntegration.php index c6455f2d8004..517382960e57 100644 --- a/app/Models/BankIntegration.php +++ b/app/Models/BankIntegration.php @@ -20,8 +20,9 @@ class BankIntegration extends BaseModel use SoftDeletes; use Filterable; use Excludable; - + protected $fillable = [ + 'integration_type', 'bank_account_name', 'provider_name', 'bank_account_number', @@ -36,6 +37,12 @@ class BankIntegration extends BaseModel protected $dates = [ ]; + const INTEGRATION_TYPE_NONE = null; + + const INTEGRATION_TYPE_YODLEE = 'YODLEE'; + + const INTEGRATION_TYPE_NORDIGEN = 'NORDIGEN'; + public function getEntityType() { return self::class; @@ -61,4 +68,4 @@ class BankIntegration extends BaseModel return $this->hasMany(BankTransaction::class)->withTrashed(); } -} \ No newline at end of file +} diff --git a/composer.json b/composer.json index ea5afee82514..6a2f2b7ae99e 100644 --- a/composer.json +++ b/composer.json @@ -70,11 +70,11 @@ "microsoft/microsoft-graph": "^1.69", "mollie/mollie-api-php": "^2.36", "nelexa/zip": "^4.0", + "nordigen/nordigen-php": "^1.1", "nwidart/laravel-modules": "8.3", "omnipay/paypal": "^3.0", "payfast/payfast-php-sdk": "^1.1", "pragmarx/google2fa": "^8.0", - "turbo124/predis": "^1.1", "razorpay/razorpay": "2.*", "sentry/sentry-laravel": "^3", "setasign/fpdf": "^1.8", @@ -89,6 +89,7 @@ "symfony/postmark-mailer": "^6.1", "tijsverkoyen/css-to-inline-styles": "^2.2", "turbo124/beacon": "^1.3", + "turbo124/predis": "^1.1", "twilio/sdk": "^6.40", "webpatser/laravel-countries": "dev-master#75992ad", "wepay/php-sdk": "^0.3" @@ -157,7 +158,10 @@ "config": { "preferred-install": "dist", "sort-packages": true, - "optimize-autoloader": true + "optimize-autoloader": true, + "allow-plugins": { + "php-http/discovery": true + } }, "minimum-stability": "dev", "prefer-stable": true diff --git a/composer.lock b/composer.lock index 94bdcb16d779..bf64487d5203 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fee0057b8444e2a245cea87dab6c1b3a", + "content-hash": "82f7c9bb40d1811eec9695ab1c4bb8d4", "packages": [ { "name": "afosto/yaac", - "version": "v1.5.1", + "version": "v1.5.2", "source": { "type": "git", "url": "https://github.com/afosto/yaac.git", - "reference": "fe807f841ad4b125bdcf7bba87b6a166b8d23b22" + "reference": "3c09d1a8ae7df6ba0ec23c9ccf641b3295219de7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/afosto/yaac/zipball/fe807f841ad4b125bdcf7bba87b6a166b8d23b22", - "reference": "fe807f841ad4b125bdcf7bba87b6a166b8d23b22", + "url": "https://api.github.com/repos/afosto/yaac/zipball/3c09d1a8ae7df6ba0ec23c9ccf641b3295219de7", + "reference": "3c09d1a8ae7df6ba0ec23c9ccf641b3295219de7", "shasum": "" }, "require": { @@ -54,9 +54,9 @@ ], "support": { "issues": "https://github.com/afosto/yaac/issues", - "source": "https://github.com/afosto/yaac/tree/v1.5.1" + "source": "https://github.com/afosto/yaac/tree/v1.5.2" }, - "time": "2023-01-05T08:42:38+00:00" + "time": "2023-05-02T15:11:17+00:00" }, { "name": "apimatic/jsonmapper", @@ -174,26 +174,26 @@ }, { "name": "asm/php-ansible", - "version": "v4.0.0", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/maschmann/php-ansible.git", - "reference": "654592557a8cae60169de6f2ba145ef7ebb38f37" + "reference": "8d03a841907c20c5afa7ed2ac9f5ef30586f9bc2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maschmann/php-ansible/zipball/654592557a8cae60169de6f2ba145ef7ebb38f37", - "reference": "654592557a8cae60169de6f2ba145ef7ebb38f37", + "url": "https://api.github.com/repos/maschmann/php-ansible/zipball/8d03a841907c20c5afa7ed2ac9f5ef30586f9bc2", + "reference": "8d03a841907c20c5afa7ed2ac9f5ef30586f9bc2", "shasum": "" }, "require": { - "php": "^8.0.0", - "psr/log": "^1.1", + "php": "^8.0.0|^8.1.0|^8.2.0", + "psr/log": "^1.1|^2.0|^3.0", "symfony/process": "^5.3|^6.0" }, "require-dev": { "mikey179/vfsstream": "^1.6", - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.5|^10.0 " }, "type": "library", "autoload": { @@ -219,9 +219,9 @@ ], "support": { "issues": "https://github.com/maschmann/php-ansible/issues", - "source": "https://github.com/maschmann/php-ansible/tree/v4.0.0" + "source": "https://github.com/maschmann/php-ansible/tree/v4.1.0" }, - "time": "2022-02-20T18:59:30+00:00" + "time": "2023-02-28T11:00:12+00:00" }, { "name": "authorizenet/authorizenet", @@ -268,23 +268,25 @@ }, { "name": "awobaz/compoships", - "version": "2.1.4", + "version": "2.2.3", "source": { "type": "git", "url": "https://github.com/topclaudy/compoships.git", - "reference": "ba86741d9b439d1179a6432dded92b0ecc89a63a" + "reference": "404901e2ebd6794f70d2710a56edd4b0c500ce1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/topclaudy/compoships/zipball/ba86741d9b439d1179a6432dded92b0ecc89a63a", - "reference": "ba86741d9b439d1179a6432dded92b0ecc89a63a", + "url": "https://api.github.com/repos/topclaudy/compoships/zipball/404901e2ebd6794f70d2710a56edd4b0c500ce1f", + "reference": "404901e2ebd6794f70d2710a56edd4b0c500ce1f", "shasum": "" }, "require": { - "illuminate/database": ">=5.6 <10.0" + "fakerphp/faker": "^1.18", + "illuminate/database": ">=5.6 <11.0" }, "require-dev": { - "ext-sqlite3": "*" + "ext-sqlite3": "*", + "phpunit/phpunit": "^6.0|^8.0|^9.0" }, "suggest": { "awobaz/blade-active": "Blade directives for the Laravel 'Active' package", @@ -316,7 +318,7 @@ ], "support": { "issues": "https://github.com/topclaudy/compoships/issues", - "source": "https://github.com/topclaudy/compoships/tree/2.1.4" + "source": "https://github.com/topclaudy/compoships/tree/2.2.3" }, "funding": [ { @@ -324,27 +326,31 @@ "type": "custom" } ], - "time": "2022-06-22T11:42:37+00:00" + "time": "2023-02-22T16:52:55+00:00" }, { "name": "aws/aws-crt-php", - "version": "v1.0.2", + "version": "v1.2.3", "source": { "type": "git", "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "3942776a8c99209908ee0b287746263725685732" + "reference": "5545a4fa310aec39f54279fdacebcce33b3ff382" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/3942776a8c99209908ee0b287746263725685732", - "reference": "3942776a8c99209908ee0b287746263725685732", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/5545a4fa310aec39f54279fdacebcce33b3ff382", + "reference": "5545a4fa310aec39f54279fdacebcce33b3ff382", "shasum": "" }, "require": { "php": ">=5.5" }, "require-dev": { - "phpunit/phpunit": "^4.8.35|^5.4.3" + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." }, "type": "library", "autoload": { @@ -363,7 +369,7 @@ } ], "description": "AWS Common Runtime for PHP", - "homepage": "http://aws.amazon.com/sdkforphp", + "homepage": "https://github.com/awslabs/aws-crt-php", "keywords": [ "amazon", "aws", @@ -372,34 +378,35 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.0.2" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.3" }, - "time": "2021-09-03T22:57:30+00:00" + "time": "2023-10-16T20:10:06+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.257.10", + "version": "3.287.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "c1168bc3f6a6f370ff400774fdf421c57db31707" + "reference": "06978bfc63111fccd78b364238bf214b4ade8d18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c1168bc3f6a6f370ff400774fdf421c57db31707", - "reference": "c1168bc3f6a6f370ff400774fdf421c57db31707", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/06978bfc63111fccd78b364238bf214b4ade8d18", + "reference": "06978bfc63111fccd78b364238bf214b4ade8d18", "shasum": "" }, "require": { - "aws/aws-crt-php": "^1.0.2", + "aws/aws-crt-php": "^1.2.3", "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", - "guzzlehttp/promises": "^1.4.0", - "guzzlehttp/psr7": "^1.8.5 || ^2.3", + "guzzlehttp/promises": "^1.4.0 || ^2.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", "mtdowling/jmespath.php": "^2.6", - "php": ">=5.5" + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0" }, "require-dev": { "andrewsville/php-token-reflection": "^1.4", @@ -414,7 +421,7 @@ "ext-sockets": "*", "nette/neon": "^2.3", "paragonie/random_compat": ">= 2", - "phpunit/phpunit": "^4.8.35 || ^5.6.3 || ^9.5", + "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", "psr/cache": "^1.0", "psr/simple-cache": "^1.0", "sebastian/comparator": "^1.2.3 || ^4.0", @@ -466,9 +473,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.257.10" + "source": "https://github.com/aws/aws-sdk-php/tree/3.287.0" }, - "time": "2023-01-27T19:23:30+00:00" + "time": "2023-11-17T20:03:36+00:00" }, { "name": "bacon/bacon-qr-code", @@ -578,16 +585,16 @@ }, { "name": "braintree/braintree_php", - "version": "6.11.1", + "version": "6.15.0", "source": { "type": "git", "url": "https://github.com/braintree/braintree_php.git", - "reference": "2cd8fe23b4e3bc96f84ec6b95d95330aec5757ec" + "reference": "16efb08e19cb6c579deba11e119ef6409d28eae3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/braintree/braintree_php/zipball/2cd8fe23b4e3bc96f84ec6b95d95330aec5757ec", - "reference": "2cd8fe23b4e3bc96f84ec6b95d95330aec5757ec", + "url": "https://api.github.com/repos/braintree/braintree_php/zipball/16efb08e19cb6c579deba11e119ef6409d28eae3", + "reference": "16efb08e19cb6c579deba11e119ef6409d28eae3", "shasum": "" }, "require": { @@ -621,32 +628,31 @@ "description": "Braintree PHP Client Library", "support": { "issues": "https://github.com/braintree/braintree_php/issues", - "source": "https://github.com/braintree/braintree_php/tree/6.11.1" + "source": "https://github.com/braintree/braintree_php/tree/6.15.0" }, - "time": "2023-01-18T16:12:07+00:00" + "time": "2023-11-08T00:15:11+00:00" }, { "name": "brick/math", - "version": "0.10.2", + "version": "0.11.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "459f2781e1a08d52ee56b0b1444086e038561e3f" + "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/459f2781e1a08d52ee56b0b1444086e038561e3f", - "reference": "459f2781e1a08d52ee56b0b1444086e038561e3f", + "url": "https://api.github.com/repos/brick/math/zipball/0ad82ce168c82ba30d1c01ec86116ab52f589478", + "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478", "shasum": "" }, "require": { - "ext-json": "*", - "php": "^7.4 || ^8.0" + "php": "^8.0" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^9.0", - "vimeo/psalm": "4.25.0" + "vimeo/psalm": "5.0.0" }, "type": "library", "autoload": { @@ -671,7 +677,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.10.2" + "source": "https://github.com/brick/math/tree/0.11.0" }, "funding": [ { @@ -679,7 +685,7 @@ "type": "github" } ], - "time": "2022-08-10T22:54:19+00:00" + "time": "2023-01-15T23:15:59+00:00" }, { "name": "checkout/checkout-sdk-php", @@ -876,16 +882,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.3.5", + "version": "1.3.7", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "74780ccf8c19d6acb8d65c5f39cd72110e132bbd" + "reference": "76e46335014860eec1aa5a724799a00a2e47cc85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/74780ccf8c19d6acb8d65c5f39cd72110e132bbd", - "reference": "74780ccf8c19d6acb8d65c5f39cd72110e132bbd", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/76e46335014860eec1aa5a724799a00a2e47cc85", + "reference": "76e46335014860eec1aa5a724799a00a2e47cc85", "shasum": "" }, "require": { @@ -932,7 +938,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.3.5" + "source": "https://github.com/composer/ca-bundle/tree/1.3.7" }, "funding": [ { @@ -948,25 +954,28 @@ "type": "tidelift" } ], - "time": "2023-01-11T08:27:00+00:00" + "time": "2023-08-30T09:31:38+00:00" }, { "name": "dasprid/enum", - "version": "1.0.3", + "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/DASPRiD/Enum.git", - "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2" + "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/5abf82f213618696dda8e3bf6f64dd042d8542b2", - "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/6faf451159fb8ba4126b925ed2d78acfce0dc016", + "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016", "shasum": "" }, + "require": { + "php": ">=7.1 <9.0" + }, "require-dev": { "phpunit/phpunit": "^7 | ^8 | ^9", - "squizlabs/php_codesniffer": "^3.4" + "squizlabs/php_codesniffer": "*" }, "type": "library", "autoload": { @@ -993,9 +1002,9 @@ ], "support": { "issues": "https://github.com/DASPRiD/Enum/issues", - "source": "https://github.com/DASPRiD/Enum/tree/1.0.3" + "source": "https://github.com/DASPRiD/Enum/tree/1.0.5" }, - "time": "2020-10-02T16:03:48+00:00" + "time": "2023-08-25T16:18:39+00:00" }, { "name": "dflydev/dot-access-data", @@ -1167,16 +1176,16 @@ }, { "name": "doctrine/dbal", - "version": "3.5.3", + "version": "3.7.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "88fa7e5189fd5ec6682477044264dc0ed4e3aa1e" + "reference": "5b7bd66c9ff58c04c5474ab85edce442f8081cb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/88fa7e5189fd5ec6682477044264dc0ed4e3aa1e", - "reference": "88fa7e5189fd5ec6682477044264dc0ed4e3aa1e", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/5b7bd66c9ff58c04c5474ab85edce442f8081cb2", + "reference": "5b7bd66c9ff58c04c5474ab85edce442f8081cb2", "shasum": "" }, "require": { @@ -1189,13 +1198,15 @@ "psr/log": "^1|^2|^3" }, "require-dev": { - "doctrine/coding-standard": "11.0.0", - "jetbrains/phpstorm-stubs": "2022.3", - "phpstan/phpstan": "1.9.4", - "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "9.5.27", + "doctrine/coding-standard": "12.0.0", + "fig/log-test": "^1", + "jetbrains/phpstorm-stubs": "2023.1", + "phpstan/phpstan": "1.10.35", + "phpstan/phpstan-strict-rules": "^1.5", + "phpunit/phpunit": "9.6.13", "psalm/plugin-phpunit": "0.18.4", - "squizlabs/php_codesniffer": "3.7.1", + "slevomat/coding-standard": "8.13.1", + "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^5.4|^6.0", "symfony/console": "^4.4|^5.4|^6.0", "vimeo/psalm": "4.30.0" @@ -1258,7 +1269,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.5.3" + "source": "https://github.com/doctrine/dbal/tree/3.7.1" }, "funding": [ { @@ -1274,29 +1285,33 @@ "type": "tidelift" } ], - "time": "2023-01-12T10:21:44+00:00" + "time": "2023-10-06T05:06:20+00:00" }, { "name": "doctrine/deprecations", - "version": "v1.0.0", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931", + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931", "shasum": "" }, "require": { - "php": "^7.1|^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5|^8.5|^9.5", - "psr/log": "^1|^2|^3" + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -1315,9 +1330,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" + "source": "https://github.com/doctrine/deprecations/tree/1.1.2" }, - "time": "2022-05-02T15:47:09+00:00" + "time": "2023-09-27T20:04:15+00:00" }, { "name": "doctrine/event-manager", @@ -1412,28 +1427,28 @@ }, { "name": "doctrine/inflector", - "version": "2.0.6", + "version": "2.0.8", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024" + "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/d9d313a36c872fd6ee06d9a6cbcf713eaa40f024", - "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/f9301a5b2fb1216b2b08f02ba04dc45423db6bff", + "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^10", + "doctrine/coding-standard": "^11.0", "phpstan/phpstan": "^1.8", "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.3", "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25" + "vimeo/psalm": "^4.25 || ^5.4" }, "type": "library", "autoload": { @@ -1483,7 +1498,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.6" + "source": "https://github.com/doctrine/inflector/tree/2.0.8" }, "funding": [ { @@ -1499,7 +1514,7 @@ "type": "tidelift" } ], - "time": "2022-10-20T09:10:12+00:00" + "time": "2023-06-16T13:40:37+00:00" }, { "name": "doctrine/lexer", @@ -1581,16 +1596,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.3.2", + "version": "v3.3.3", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8" + "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/782ca5968ab8b954773518e9e49a6f892a34b2a8", - "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", + "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", "shasum": "" }, "require": { @@ -1630,7 +1645,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.2" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3" }, "funding": [ { @@ -1638,20 +1653,20 @@ "type": "github" } ], - "time": "2022-09-10T18:51:20+00:00" + "time": "2023-08-10T19:36:49+00:00" }, { "name": "egulias/email-validator", - "version": "4.0.1", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff" + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/3a85486b709bc384dae8eb78fb2eec649bdb64ff", - "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e", + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e", "shasum": "" }, "require": { @@ -1660,8 +1675,8 @@ "symfony/polyfill-intl-idn": "^1.26" }, "require-dev": { - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^4.30" + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -1697,7 +1712,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.1" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.2" }, "funding": [ { @@ -1705,7 +1720,7 @@ "type": "github" } ], - "time": "2023-01-14T14:17:03+00:00" + "time": "2023-10-06T06:47:41+00:00" }, { "name": "endroid/qr-code", @@ -1840,16 +1855,16 @@ }, { "name": "fakerphp/faker", - "version": "v1.21.0", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "92efad6a967f0b79c499705c69b662f738cc9e4d" + "reference": "e3daa170d00fde61ea7719ef47bb09bb8f1d9b01" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/92efad6a967f0b79c499705c69b662f738cc9e4d", - "reference": "92efad6a967f0b79c499705c69b662f738cc9e4d", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e3daa170d00fde61ea7719ef47bb09bb8f1d9b01", + "reference": "e3daa170d00fde61ea7719ef47bb09bb8f1d9b01", "shasum": "" }, "require": { @@ -1902,36 +1917,37 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.21.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.23.0" }, - "time": "2022-12-13T13:54:32+00:00" + "time": "2023-06-12T08:44:38+00:00" }, { "name": "firebase/php-jwt", - "version": "v6.3.2", + "version": "v6.9.0", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "ea7dda77098b96e666c5ef382452f94841e439cd" + "reference": "f03270e63eaccf3019ef0f32849c497385774e11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/ea7dda77098b96e666c5ef382452f94841e439cd", - "reference": "ea7dda77098b96e666c5ef382452f94841e439cd", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/f03270e63eaccf3019ef0f32849c497385774e11", + "reference": "f03270e63eaccf3019ef0f32849c497385774e11", "shasum": "" }, "require": { - "php": "^7.1||^8.0" + "php": "^7.4||^8.0" }, "require-dev": { "guzzlehttp/guzzle": "^6.5||^7.4", - "phpspec/prophecy-phpunit": "^1.1", - "phpunit/phpunit": "^7.5||^9.5", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", "psr/cache": "^1.0||^2.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0" }, "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" }, "type": "library", @@ -1964,27 +1980,27 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.3.2" + "source": "https://github.com/firebase/php-jwt/tree/v6.9.0" }, - "time": "2022-12-19T17:10:46+00:00" + "time": "2023-10-05T00:24:42+00:00" }, { "name": "fruitcake/php-cors", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/fruitcake/php-cors.git", - "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e" + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/58571acbaa5f9f462c9c77e911700ac66f446d4e", - "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/3d158f36e7875e2f040f37bc0573956240a5a38b", + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "symfony/http-foundation": "^4.4|^5.4|^6" + "symfony/http-foundation": "^4.4|^5.4|^6|^7" }, "require-dev": { "phpstan/phpstan": "^1.4", @@ -1994,7 +2010,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.1-dev" + "dev-master": "1.2-dev" } }, "autoload": { @@ -2025,7 +2041,7 @@ ], "support": { "issues": "https://github.com/fruitcake/php-cors/issues", - "source": "https://github.com/fruitcake/php-cors/tree/v1.2.0" + "source": "https://github.com/fruitcake/php-cors/tree/v1.3.0" }, "funding": [ { @@ -2037,20 +2053,20 @@ "type": "github" } ], - "time": "2022-02-20T15:07:15+00:00" + "time": "2023-10-12T05:21:21+00:00" }, { "name": "gocardless/gocardless-pro", - "version": "4.24.0", + "version": "4.28.0", "source": { "type": "git", "url": "https://github.com/gocardless/gocardless-pro-php.git", - "reference": "e75dc9098119a0d306b1dd0666a6fde3a18bcdfe" + "reference": "d843a4a94cbe20ab5c0533eb805c48a87600abdf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/gocardless/gocardless-pro-php/zipball/e75dc9098119a0d306b1dd0666a6fde3a18bcdfe", - "reference": "e75dc9098119a0d306b1dd0666a6fde3a18bcdfe", + "url": "https://api.github.com/repos/gocardless/gocardless-pro-php/zipball/d843a4a94cbe20ab5c0533eb805c48a87600abdf", + "reference": "d843a4a94cbe20ab5c0533eb805c48a87600abdf", "shasum": "" }, "require": { @@ -2090,44 +2106,43 @@ ], "support": { "issues": "https://github.com/gocardless/gocardless-pro-php/issues", - "source": "https://github.com/gocardless/gocardless-pro-php/tree/v4.24.0" + "source": "https://github.com/gocardless/gocardless-pro-php/tree/v4.28.0" }, - "time": "2022-12-21T16:48:49+00:00" + "time": "2023-04-11T17:13:38+00:00" }, { "name": "google/apiclient", - "version": "v2.13.0", + "version": "v2.15.1", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client.git", - "reference": "b653a338c5a658adf6df4bb2f44c2cc02fe7eb1d" + "reference": "7a95ed29e4b6c6859d2d22300c5455a92e2622ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/b653a338c5a658adf6df4bb2f44c2cc02fe7eb1d", - "reference": "b653a338c5a658adf6df4bb2f44c2cc02fe7eb1d", + "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/7a95ed29e4b6c6859d2d22300c5455a92e2622ad", + "reference": "7a95ed29e4b6c6859d2d22300c5455a92e2622ad", "shasum": "" }, "require": { - "firebase/php-jwt": "~2.0||~3.0||~4.0||~5.0||~6.0", + "firebase/php-jwt": "~6.0", "google/apiclient-services": "~0.200", - "google/auth": "^1.10", - "guzzlehttp/guzzle": "~5.3.3||~6.0||~7.0", + "google/auth": "^1.28", + "guzzlehttp/guzzle": "~6.5||~7.0", "guzzlehttp/psr7": "^1.8.4||^2.2.1", - "monolog/monolog": "^1.17||^2.0||^3.0", - "php": "^5.6|^7.0|^8.0", - "phpseclib/phpseclib": "~2.0||^3.0.2" + "monolog/monolog": "^2.9||^3.0", + "php": "^7.4|^8.0", + "phpseclib/phpseclib": "^3.0.19" }, "require-dev": { - "cache/filesystem-adapter": "^0.3.2|^1.1", + "cache/filesystem-adapter": "^1.1", "composer/composer": "^1.10.22", "phpcompatibility/php-compatibility": "^9.2", - "phpspec/prophecy-phpunit": "^1.1||^2.0", - "phpunit/phpunit": "^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", "squizlabs/php_codesniffer": "^3.0", "symfony/css-selector": "~2.1", - "symfony/dom-crawler": "~2.1", - "yoast/phpunit-polyfills": "^1.0" + "symfony/dom-crawler": "~2.1" }, "suggest": { "cache/filesystem-adapter": "For caching certs and tokens (using Google\\Client::setCache)" @@ -2160,26 +2175,26 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client/issues", - "source": "https://github.com/googleapis/google-api-php-client/tree/v2.13.0" + "source": "https://github.com/googleapis/google-api-php-client/tree/v2.15.1" }, - "time": "2022-12-19T22:17:11+00:00" + "time": "2023-09-13T21:46:39+00:00" }, { "name": "google/apiclient-services", - "version": "v0.284.0", + "version": "v0.324.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "29d471cfaf0fc6f856f01dd82a89ac0fb7ac7fec" + "reference": "585cc823c3d59788e4a0829d5b7e41c76950d801" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/29d471cfaf0fc6f856f01dd82a89ac0fb7ac7fec", - "reference": "29d471cfaf0fc6f856f01dd82a89ac0fb7ac7fec", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/585cc823c3d59788e4a0829d5b7e41c76950d801", + "reference": "585cc823c3d59788e4a0829d5b7e41c76950d801", "shasum": "" }, "require": { - "php": ">=5.6" + "php": "^7.4||^8.0" }, "require-dev": { "phpunit/phpunit": "^5.7||^8.5.13" @@ -2204,38 +2219,38 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.284.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.324.0" }, - "time": "2023-01-22T01:08:15+00:00" + "time": "2023-11-13T01:06:14+00:00" }, { "name": "google/auth", - "version": "v1.25.0", + "version": "v1.32.1", "source": { "type": "git", "url": "https://github.com/googleapis/google-auth-library-php.git", - "reference": "0865c44ab50378f7b145827dfcbd1e7a238f7759" + "reference": "999e9ce8b9d17914f04e1718271a0a46da4de2f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/0865c44ab50378f7b145827dfcbd1e7a238f7759", - "reference": "0865c44ab50378f7b145827dfcbd1e7a238f7759", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/999e9ce8b9d17914f04e1718271a0a46da4de2f3", + "reference": "999e9ce8b9d17914f04e1718271a0a46da4de2f3", "shasum": "" }, "require": { - "firebase/php-jwt": "^5.5||^6.0", + "firebase/php-jwt": "^6.0", "guzzlehttp/guzzle": "^6.2.1|^7.0", - "guzzlehttp/psr7": "^1.7|^2.0", - "php": "^7.1||^8.0", - "psr/cache": "^1.0|^2.0|^3.0", - "psr/http-message": "^1.0" + "guzzlehttp/psr7": "^2.4.5", + "php": "^7.4||^8.0", + "psr/cache": "^1.0||^2.0||^3.0", + "psr/http-message": "^1.1||^2.0" }, "require-dev": { - "guzzlehttp/promises": "0.1.1|^1.3", - "kelvinmo/simplejwt": "^0.2.5|^0.5.1", - "phpseclib/phpseclib": "^2.0.31", - "phpspec/prophecy-phpunit": "^1.1||^2.0", - "phpunit/phpunit": "^7.5||^9.0.0", + "guzzlehttp/promises": "^2.0", + "kelvinmo/simplejwt": "0.7.1", + "phpseclib/phpseclib": "^3.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.0.0", "sebastian/comparator": ">=1.2.3", "squizlabs/php_codesniffer": "^3.5" }, @@ -2262,30 +2277,30 @@ "support": { "docs": "https://googleapis.github.io/google-auth-library-php/main/", "issues": "https://github.com/googleapis/google-auth-library-php/issues", - "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.25.0" + "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.32.1" }, - "time": "2023-01-26T22:04:14+00:00" + "time": "2023-10-17T21:13:22+00:00" }, { "name": "graham-campbell/result-type", - "version": "v1.1.0", + "version": "v1.1.2", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8" + "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/a878d45c1914464426dc94da61c9e1d36ae262a8", - "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862", + "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9" + "phpoption/phpoption": "^1.9.2" }, "require-dev": { - "phpunit/phpunit": "^8.5.28 || ^9.5.21" + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" }, "type": "library", "autoload": { @@ -2314,7 +2329,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.0" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2" }, "funding": [ { @@ -2326,7 +2341,7 @@ "type": "tidelift" } ], - "time": "2022-07-30T15:56:11+00:00" + "time": "2023-11-12T22:16:48+00:00" }, { "name": "graylog2/gelf-php", @@ -2388,22 +2403,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.5.0", + "version": "7.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" + "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1110f66a6530a40fe7aea0378fe608ee2b2248f9", + "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.9 || ^2.4", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -2414,7 +2429,8 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.1", "ext-curl": "*", - "php-http/client-integration-tests": "^3.0", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", "phpunit/phpunit": "^8.5.29 || ^9.5.23", "psr/log": "^1.1 || ^2.0 || ^3.0" }, @@ -2428,9 +2444,6 @@ "bamarni-bin": { "bin-links": true, "forward-command": false - }, - "branch-alias": { - "dev-master": "7.5-dev" } }, "autoload": { @@ -2496,7 +2509,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + "source": "https://github.com/guzzle/guzzle/tree/7.8.0" }, "funding": [ { @@ -2512,38 +2525,37 @@ "type": "tidelift" } ], - "time": "2022-08-28T15:39:27+00:00" + "time": "2023-08-27T10:20:53+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.5.2", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "b94b2807d85443f9719887892882d0329d1e2598" + "reference": "111166291a0f8130081195ac4556a5587d7f1b5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", - "reference": "b94b2807d85443f9719887892882d0329d1e2598", + "url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", + "reference": "111166291a0f8130081195ac4556a5587d7f1b5d", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" + "bamarni/composer-bin-plugin": "^1.8.1", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.5-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\Promise\\": "src/" } @@ -2580,7 +2592,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.2" + "source": "https://github.com/guzzle/promises/tree/2.0.1" }, "funding": [ { @@ -2596,26 +2608,26 @@ "type": "tidelift" } ], - "time": "2022-08-28T14:55:35+00:00" + "time": "2023-08-03T15:11:55+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.4.3", + "version": "2.6.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "67c26b443f348a51926030c83481b85718457d3d" + "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d", - "reference": "67c26b443f348a51926030c83481b85718457d3d", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/be45764272e8873c72dbe3d2edcfdfcc3bc9f727", + "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.1 || ^2.0", "ralouphie/getallheaders": "^3.0" }, "provide": { @@ -2635,9 +2647,6 @@ "bamarni-bin": { "bin-links": true, "forward-command": false - }, - "branch-alias": { - "dev-master": "2.4-dev" } }, "autoload": { @@ -2699,7 +2708,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.3" + "source": "https://github.com/guzzle/psr7/tree/2.6.1" }, "funding": [ { @@ -2715,7 +2724,87 @@ "type": "tidelift" } ], - "time": "2022-10-26T14:07:24+00:00" + "time": "2023-08-27T10:13:57+00:00" + }, + { + "name": "guzzlehttp/uri-template", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/uri-template.git", + "reference": "61bf437fc2197f587f6857d3ff903a24f1731b5d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/61bf437fc2197f587f6857d3ff903a24f1731b5d", + "reference": "61bf437fc2197f587f6857d3ff903a24f1731b5d", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-php80": "^1.17" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "phpunit/phpunit": "^8.5.19 || ^9.5.8", + "uri-template/tests": "1.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "GuzzleHttp\\UriTemplate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + } + ], + "description": "A polyfill class for uri_template of PHP", + "keywords": [ + "guzzlehttp", + "uri-template" + ], + "support": { + "issues": "https://github.com/guzzle/uri-template/issues", + "source": "https://github.com/guzzle/uri-template/tree/v1.0.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "type": "tidelift" + } + ], + "time": "2023-08-27T10:19:19+00:00" }, { "name": "halaxa/json-machine", @@ -3441,20 +3530,20 @@ }, { "name": "laracasts/presenter", - "version": "0.2.5", + "version": "0.2.6", "source": { "type": "git", "url": "https://github.com/laracasts/Presenter.git", - "reference": "d9b9050abf0af1d75465284a62b537c3f4ac7d1f" + "reference": "4353fb5a1a3b9b9073db66fd823eaaf04c0c5db0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laracasts/Presenter/zipball/d9b9050abf0af1d75465284a62b537c3f4ac7d1f", - "reference": "d9b9050abf0af1d75465284a62b537c3f4ac7d1f", + "url": "https://api.github.com/repos/laracasts/Presenter/zipball/4353fb5a1a3b9b9073db66fd823eaaf04c0c5db0", + "reference": "4353fb5a1a3b9b9073db66fd823eaaf04c0c5db0", "shasum": "" }, "require": { - "illuminate/support": "~5.0|~6.0|~7.0|~8.0|^9.0", + "illuminate/support": "~5.0|~6.0|~7.0|~8.0|^9.0|^10.0", "php": ">=5.4.0" }, "require-dev": { @@ -3485,32 +3574,38 @@ ], "support": { "issues": "https://github.com/laracasts/Presenter/issues", - "source": "https://github.com/laracasts/Presenter/tree/0.2.5" + "source": "https://github.com/laracasts/Presenter/tree/0.2.6" }, - "time": "2022-04-12T18:22:42+00:00" + "time": "2023-02-21T16:06:10+00:00" }, { "name": "laravel/framework", - "version": "v9.48.0", + "version": "v9.52.16", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "c78ae7aeb0cbcb1a205050d3592247ba07f5b711" + "reference": "082345d76fc6a55b649572efe10b11b03e279d24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/c78ae7aeb0cbcb1a205050d3592247ba07f5b711", - "reference": "c78ae7aeb0cbcb1a205050d3592247ba07f5b711", + "url": "https://api.github.com/repos/laravel/framework/zipball/082345d76fc6a55b649572efe10b11b03e279d24", + "reference": "082345d76fc6a55b649572efe10b11b03e279d24", "shasum": "" }, "require": { - "brick/math": "^0.10.2", - "doctrine/inflector": "^2.0", + "brick/math": "^0.9.3|^0.10.2|^0.11", + "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.3.2", "egulias/email-validator": "^3.2.1|^4.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", "ext-mbstring": "*", "ext-openssl": "*", + "ext-session": "*", + "ext-tokenizer": "*", "fruitcake/php-cors": "^1.2", + "guzzlehttp/uri-template": "^1.0", "laravel/serializable-closure": "^1.2.2", "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", @@ -3582,6 +3677,7 @@ "ably/ably-php": "^1.0", "aws/aws-sdk-php": "^3.235.5", "doctrine/dbal": "^2.13.3|^3.1.4", + "ext-gmp": "*", "fakerphp/faker": "^1.21", "guzzlehttp/guzzle": "^7.5", "league/flysystem-aws-s3-v3": "^3.0", @@ -3590,7 +3686,7 @@ "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^7.16", + "orchestra/testbench-core": "^7.24", "pda/pheanstalk": "^4.0", "phpstan/phpdoc-parser": "^1.15", "phpstan/phpstan": "^1.4.7", @@ -3604,11 +3700,13 @@ "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).", "brianium/paratest": "Required to run tests in parallel (^6.0).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).", - "ext-bcmath": "Required to use the multiple_of validation rule.", + "ext-apcu": "Required to use the APC cache driver.", + "ext-fileinfo": "Required to use the Filesystem class.", "ext-ftp": "Required to use the Flysystem FTP driver.", "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", "ext-memcached": "Required to use the memcache cache driver.", - "ext-pcntl": "Required to use all features of the queue worker.", + "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", + "ext-pdo": "Required to use all database features.", "ext-posix": "Required to use all features of the queue worker.", "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", @@ -3676,20 +3774,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-01-17T15:06:19+00:00" + "time": "2023-10-03T13:02:30+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.2.2", + "version": "v1.3.3", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "47afb7fae28ed29057fdca37e16a84f90cc62fae" + "reference": "3dbf8a8e914634c48d389c1234552666b3d43754" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/47afb7fae28ed29057fdca37e16a84f90cc62fae", - "reference": "47afb7fae28ed29057fdca37e16a84f90cc62fae", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/3dbf8a8e914634c48d389c1234552666b3d43754", + "reference": "3dbf8a8e914634c48d389c1234552666b3d43754", "shasum": "" }, "require": { @@ -3736,7 +3834,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2022-09-08T13:45:54+00:00" + "time": "2023-11-08T14:08:06+00:00" }, { "name": "laravel/slack-notification-channel", @@ -3801,16 +3899,16 @@ }, { "name": "laravel/socialite", - "version": "v5.6.1", + "version": "v5.10.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "a14a177f2cc71d8add71e2b19e00800e83bdda09" + "reference": "f376b6eda9084899e37ac08bafd64a95edf9c6c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/a14a177f2cc71d8add71e2b19e00800e83bdda09", - "reference": "a14a177f2cc71d8add71e2b19e00800e83bdda09", + "url": "https://api.github.com/repos/laravel/socialite/zipball/f376b6eda9084899e37ac08bafd64a95edf9c6c0", + "reference": "f376b6eda9084899e37ac08bafd64a95edf9c6c0", "shasum": "" }, "require": { @@ -3825,6 +3923,7 @@ "require-dev": { "mockery/mockery": "^1.0", "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^8.0|^9.3" }, "type": "library", @@ -3866,20 +3965,20 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2023-01-20T15:42:35+00:00" + "time": "2023-10-30T22:09:58+00:00" }, { "name": "laravel/tinker", - "version": "v2.8.0", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "74d0b287cc4ae65d15c368dd697aae71d62a73ad" + "reference": "b936d415b252b499e8c3b1f795cd4fc20f57e1f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/74d0b287cc4ae65d15c368dd697aae71d62a73ad", - "reference": "74d0b287cc4ae65d15c368dd697aae71d62a73ad", + "url": "https://api.github.com/repos/laravel/tinker/zipball/b936d415b252b499e8c3b1f795cd4fc20f57e1f3", + "reference": "b936d415b252b499e8c3b1f795cd4fc20f57e1f3", "shasum": "" }, "require": { @@ -3892,6 +3991,7 @@ }, "require-dev": { "mockery/mockery": "~1.3.3|^1.4.2", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^8.5.8|^9.3.3" }, "suggest": { @@ -3932,9 +4032,9 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.8.0" + "source": "https://github.com/laravel/tinker/tree/v2.8.2" }, - "time": "2023-01-10T18:03:30+00:00" + "time": "2023-08-15T14:27:00+00:00" }, { "name": "laravel/ui", @@ -3999,20 +4099,20 @@ }, { "name": "lcobucci/clock", - "version": "3.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/lcobucci/clock.git", - "reference": "039ef98c6b57b101d10bd11d8fdfda12cbd996dc" + "reference": "30a854ceb22bd87d83a7a4563b3f6312453945fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/clock/zipball/039ef98c6b57b101d10bd11d8fdfda12cbd996dc", - "reference": "039ef98c6b57b101d10bd11d8fdfda12cbd996dc", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/30a854ceb22bd87d83a7a4563b3f6312453945fc", + "reference": "30a854ceb22bd87d83a7a4563b3f6312453945fc", "shasum": "" }, "require": { - "php": "~8.1.0 || ~8.2.0", + "php": "~8.2.0", "psr/clock": "^1.0" }, "provide": { @@ -4020,13 +4120,13 @@ }, "require-dev": { "infection/infection": "^0.26", - "lcobucci/coding-standard": "^9.0", + "lcobucci/coding-standard": "^10.0.0", "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-deprecation-rules": "^1.1.1", - "phpstan/phpstan-phpunit": "^1.3.2", - "phpstan/phpstan-strict-rules": "^1.4.4", - "phpunit/phpunit": "^9.5.27" + "phpstan/phpstan": "^1.10.7", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.10", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^10.0.17" }, "type": "library", "autoload": { @@ -4047,7 +4147,7 @@ "description": "Yet another clock abstraction", "support": { "issues": "https://github.com/lcobucci/clock/issues", - "source": "https://github.com/lcobucci/clock/tree/3.0.0" + "source": "https://github.com/lcobucci/clock/tree/3.1.0" }, "funding": [ { @@ -4059,43 +4159,44 @@ "type": "patreon" } ], - "time": "2022-12-19T15:00:24+00:00" + "time": "2023-03-20T19:12:25+00:00" }, { "name": "lcobucci/jwt", - "version": "4.3.0", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "4d7de2fe0d51a96418c0d04004986e410e87f6b4" + "reference": "f0031c07b96db6a0ca649206e7eacddb7e9d5908" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/4d7de2fe0d51a96418c0d04004986e410e87f6b4", - "reference": "4d7de2fe0d51a96418c0d04004986e410e87f6b4", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/f0031c07b96db6a0ca649206e7eacddb7e9d5908", + "reference": "f0031c07b96db6a0ca649206e7eacddb7e9d5908", "shasum": "" }, "require": { "ext-hash": "*", "ext-json": "*", - "ext-mbstring": "*", "ext-openssl": "*", "ext-sodium": "*", - "lcobucci/clock": "^2.0 || ^3.0", - "php": "^7.4 || ^8.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0", + "psr/clock": "^1.0" }, "require-dev": { - "infection/infection": "^0.21", - "lcobucci/coding-standard": "^6.0", - "mikey179/vfsstream": "^1.6.7", - "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/php-invoker": "^3.1", - "phpunit/phpunit": "^9.5" + "infection/infection": "^0.27.0", + "lcobucci/clock": "^3.0", + "lcobucci/coding-standard": "^11.0", + "phpbench/phpbench": "^1.2.9", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.10.7", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.10", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^10.2.6" + }, + "suggest": { + "lcobucci/clock": ">= 3.0" }, "type": "library", "autoload": { @@ -4121,7 +4222,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/4.3.0" + "source": "https://github.com/lcobucci/jwt/tree/5.1.0" }, "funding": [ { @@ -4133,20 +4234,20 @@ "type": "patreon" } ], - "time": "2023-01-02T13:28:00+00:00" + "time": "2023-10-31T06:41:47+00:00" }, { "name": "league/commonmark", - "version": "2.3.8", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "c493585c130544c4e91d2e0e131e6d35cb0cbc47" + "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c493585c130544c4e91d2e0e131e6d35cb0cbc47", - "reference": "c493585c130544c4e91d2e0e131e6d35cb0cbc47", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/3669d6d5f7a47a93c08ddff335e6d945481a1dd5", + "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5", "shasum": "" }, "require": { @@ -4182,7 +4283,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "2.5-dev" } }, "autoload": { @@ -4239,7 +4340,7 @@ "type": "tidelift" } ], - "time": "2022-12-10T16:02:17+00:00" + "time": "2023-08-30T16:55:00+00:00" }, { "name": "league/config", @@ -4325,34 +4426,38 @@ }, { "name": "league/csv", - "version": "9.8.0", + "version": "9.11.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "9d2e0265c5d90f5dd601bc65ff717e05cec19b47" + "reference": "33149c4bea4949aa4fa3d03fb11ed28682168b39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/9d2e0265c5d90f5dd601bc65ff717e05cec19b47", - "reference": "9d2e0265c5d90f5dd601bc65ff717e05cec19b47", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/33149c4bea4949aa4fa3d03fb11ed28682168b39", + "reference": "33149c4bea4949aa4fa3d03fb11ed28682168b39", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", - "php": "^7.4 || ^8.0" + "php": "^8.1.2" }, "require-dev": { - "ext-curl": "*", + "doctrine/collections": "^2.1.3", "ext-dom": "*", - "friendsofphp/php-cs-fixer": "^v3.4.0", - "phpstan/phpstan": "^1.3.0", - "phpstan/phpstan-phpunit": "^1.0.0", - "phpstan/phpstan-strict-rules": "^1.1.0", - "phpunit/phpunit": "^9.5.11" + "ext-xdebug": "*", + "friendsofphp/php-cs-fixer": "^v3.22.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/phpstan": "^1.10.26", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.13", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^10.3.1", + "symfony/var-dumper": "^6.3.3" }, "suggest": { - "ext-dom": "Required to use the XMLConverter and or the HTMLConverter classes", + "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters" }, "type": "library", @@ -4405,27 +4510,30 @@ "type": "github" } ], - "time": "2022-01-04T00:13:07+00:00" + "time": "2023-09-23T10:09:54+00:00" }, { "name": "league/flysystem", - "version": "3.12.2", + "version": "3.21.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "f6377c709d2275ed6feaf63e44be7a7162b0e77f" + "reference": "a326d8a2d007e4ca327a57470846e34363789258" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f6377c709d2275ed6feaf63e44be7a7162b0e77f", - "reference": "f6377c709d2275ed6feaf63e44be7a7162b0e77f", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a326d8a2d007e4ca327a57470846e34363789258", + "reference": "a326d8a2d007e4ca327a57470846e34363789258", "shasum": "" }, "require": { + "league/flysystem-local": "^3.0.0", "league/mime-type-detection": "^1.0.0", "php": "^8.0.2" }, "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", "aws/aws-sdk-php": "3.209.31 || 3.210.0", "guzzlehttp/guzzle": "<7.0", "guzzlehttp/ringphp": "<1.1.1", @@ -4433,8 +4541,8 @@ "symfony/http-client": "<5.2" }, "require-dev": { - "async-aws/s3": "^1.5", - "async-aws/simple-s3": "^1.1", + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", "aws/aws-sdk-php": "^3.220.0", "composer/semver": "^3.0", "ext-fileinfo": "*", @@ -4444,8 +4552,8 @@ "google/cloud-storage": "^1.23", "microsoft/azure-storage-blob": "^1.1", "phpseclib/phpseclib": "^3.0.14", - "phpstan/phpstan": "^0.12.26", - "phpunit/phpunit": "^9.5.11", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", "sabre/dav": "^4.3.1" }, "type": "library", @@ -4480,7 +4588,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.12.2" + "source": "https://github.com/thephpleague/flysystem/tree/3.21.0" }, "funding": [ { @@ -4490,26 +4598,22 @@ { "url": "https://github.com/frankdejonge", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", - "type": "tidelift" } ], - "time": "2023-01-19T12:02:19+00:00" + "time": "2023-11-18T13:59:15+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.12.2", + "version": "3.21.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "645e14e4a80bd2da8b01e57388e7296a695a80c2" + "reference": "2a1784eec09ee8e190fc27b93e725bc518338929" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/645e14e4a80bd2da8b01e57388e7296a695a80c2", - "reference": "645e14e4a80bd2da8b01e57388e7296a695a80c2", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/2a1784eec09ee8e190fc27b93e725bc518338929", + "reference": "2a1784eec09ee8e190fc27b93e725bc518338929", "shasum": "" }, "require": { @@ -4550,7 +4654,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.12.2" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.21.0" }, "funding": [ { @@ -4560,13 +4664,69 @@ { "url": "https://github.com/frankdejonge", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", - "type": "tidelift" } ], - "time": "2023-01-17T14:15:08+00:00" + "time": "2023-11-14T11:54:45+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.21.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "470eb1c09eaabd49ebd908ae06f23983ba3ecfe7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/470eb1c09eaabd49ebd908ae06f23983ba3ecfe7", + "reference": "470eb1c09eaabd49ebd908ae06f23983ba3ecfe7", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem-local/issues", + "source": "https://github.com/thephpleague/flysystem-local/tree/3.21.0" + }, + "funding": [ + { + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + } + ], + "time": "2023-11-18T13:41:42+00:00" }, { "name": "league/fractal", @@ -4638,26 +4798,26 @@ }, { "name": "league/mime-type-detection", - "version": "1.11.0", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" + "reference": "b6a5854368533df0295c5761a0253656a2e52d9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/b6a5854368533df0295c5761a0253656a2e52d9e", + "reference": "b6a5854368533df0295c5761a0253656a2e52d9e", "shasum": "" }, "require": { "ext-fileinfo": "*", - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3" + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" }, "type": "library", "autoload": { @@ -4678,7 +4838,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.14.0" }, "funding": [ { @@ -4690,7 +4850,7 @@ "type": "tidelift" } ], - "time": "2022-04-17T13:12:02+00:00" + "time": "2023-10-17T14:13:20+00:00" }, { "name": "league/oauth1-client", @@ -4833,16 +4993,16 @@ }, { "name": "livewire/livewire", - "version": "v2.11.1", + "version": "v2.12.6", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "3ecbb4885d61427e538fcfb9bb9598b513a2176f" + "reference": "7d3a57b3193299cf1a0639a3935c696f4da2cf92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/3ecbb4885d61427e538fcfb9bb9598b513a2176f", - "reference": "3ecbb4885d61427e538fcfb9bb9598b513a2176f", + "url": "https://api.github.com/repos/livewire/livewire/zipball/7d3a57b3193299cf1a0639a3935c696f4da2cf92", + "reference": "7d3a57b3193299cf1a0639a3935c696f4da2cf92", "shasum": "" }, "require": { @@ -4894,7 +5054,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v2.11.1" + "source": "https://github.com/livewire/livewire/tree/v2.12.6" }, "funding": [ { @@ -4902,29 +5062,30 @@ "type": "github" } ], - "time": "2023-01-28T00:15:41+00:00" + "time": "2023-08-11T04:02:34+00:00" }, { "name": "microsoft/microsoft-graph", - "version": "1.87.0", + "version": "1.109.0", "source": { "type": "git", "url": "https://github.com/microsoftgraph/msgraph-sdk-php.git", - "reference": "0df84fcf0d5842c96839f11f51a1df451521764b" + "reference": "14b1b9f24a6b6ace91323b1121030c96a0988ee0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/zipball/0df84fcf0d5842c96839f11f51a1df451521764b", - "reference": "0df84fcf0d5842c96839f11f51a1df451521764b", + "url": "https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/zipball/14b1b9f24a6b6ace91323b1121030c96a0988ee0", + "reference": "14b1b9f24a6b6ace91323b1121030c96a0988ee0", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/guzzle": "^6.0 || ^7.0", "php": "^8.0 || ^7.3", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "require-dev": { + "guzzlehttp/promises": "^1.0 || ^2.0", "mikey179/vfsstream": "^1.2", "phpstan/phpstan": "^0.12.90 || ^1.0.0", "phpunit/phpunit": "^8.0 || ^9.0" @@ -4951,22 +5112,22 @@ "homepage": "https://developer.microsoft.com/en-us/graph", "support": { "issues": "https://github.com/microsoftgraph/msgraph-sdk-php/issues", - "source": "https://github.com/microsoftgraph/msgraph-sdk-php/tree/1.87.0" + "source": "https://github.com/microsoftgraph/msgraph-sdk-php/tree/1.109.0" }, - "time": "2023-01-24T11:01:40+00:00" + "time": "2023-11-02T10:25:39+00:00" }, { "name": "mollie/mollie-api-php", - "version": "v2.50.0", + "version": "v2.63.0", "source": { "type": "git", "url": "https://github.com/mollie/mollie-api-php.git", - "reference": "2291b114ec636392e3e48b73d33199b3b4a790d5" + "reference": "642f1b87624bd1535cd198134113e14bc01ba245" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/2291b114ec636392e3e48b73d33199b3b4a790d5", - "reference": "2291b114ec636392e3e48b73d33199b3b4a790d5", + "url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/642f1b87624bd1535cd198134113e14bc01ba245", + "reference": "642f1b87624bd1535cd198134113e14bc01ba245", "shasum": "" }, "require": { @@ -5043,22 +5204,22 @@ ], "support": { "issues": "https://github.com/mollie/mollie-api-php/issues", - "source": "https://github.com/mollie/mollie-api-php/tree/v2.50.0" + "source": "https://github.com/mollie/mollie-api-php/tree/v2.63.0" }, - "time": "2023-01-02T08:49:24+00:00" + "time": "2023-11-06T09:20:50+00:00" }, { "name": "moneyphp/money", - "version": "v4.1.0", + "version": "v4.2.0", "source": { "type": "git", "url": "https://github.com/moneyphp/money.git", - "reference": "c8eeeb1f7b7e6ca95490b94a301dc9cb8cb76c2d" + "reference": "f660ab7f1d7a4c2ffdd30f50c55ed2c95c26fc3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/moneyphp/money/zipball/c8eeeb1f7b7e6ca95490b94a301dc9cb8cb76c2d", - "reference": "c8eeeb1f7b7e6ca95490b94a301dc9cb8cb76c2d", + "url": "https://api.github.com/repos/moneyphp/money/zipball/f660ab7f1d7a4c2ffdd30f50c55ed2c95c26fc3f", + "reference": "f660ab7f1d7a4c2ffdd30f50c55ed2c95c26fc3f", "shasum": "" }, "require": { @@ -5132,22 +5293,22 @@ ], "support": { "issues": "https://github.com/moneyphp/money/issues", - "source": "https://github.com/moneyphp/money/tree/v4.1.0" + "source": "https://github.com/moneyphp/money/tree/v4.2.0" }, - "time": "2022-12-19T20:35:32+00:00" + "time": "2023-08-16T14:31:24+00:00" }, { "name": "monolog/monolog", - "version": "2.8.0", + "version": "2.9.2", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "720488632c590286b88b80e62aa3d3d551ad4a50" + "reference": "437cb3628f4cf6042cc10ae97fc2b8472e48ca1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/720488632c590286b88b80e62aa3d3d551ad4a50", - "reference": "720488632c590286b88b80e62aa3d3d551ad4a50", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/437cb3628f4cf6042cc10ae97fc2b8472e48ca1f", + "reference": "437cb3628f4cf6042cc10ae97fc2b8472e48ca1f", "shasum": "" }, "require": { @@ -5162,7 +5323,7 @@ "doctrine/couchdb": "~1.0@dev", "elasticsearch/elasticsearch": "^7 || ^8", "ext-json": "*", - "graylog2/gelf-php": "^1.4.2", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", "guzzlehttp/guzzle": "^7.4", "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", @@ -5224,7 +5385,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.8.0" + "source": "https://github.com/Seldaek/monolog/tree/2.9.2" }, "funding": [ { @@ -5236,29 +5397,29 @@ "type": "tidelift" } ], - "time": "2022-07-24T11:55:47+00:00" + "time": "2023-10-27T15:25:26+00:00" }, { "name": "mtdowling/jmespath.php", - "version": "2.6.1", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb" + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b", + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0 || ^8.0", + "php": "^7.2.5 || ^8.0", "symfony/polyfill-mbstring": "^1.17" }, "require-dev": { - "composer/xdebug-handler": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^7.5.15" + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" }, "bin": [ "bin/jp.php" @@ -5266,7 +5427,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "2.7-dev" } }, "autoload": { @@ -5282,6 +5443,11 @@ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", @@ -5295,9 +5461,9 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1" + "source": "https://github.com/jmespath/jmespath.php/tree/2.7.0" }, - "time": "2021-06-14T00:11:39+00:00" + "time": "2023-08-25T10:54:48+00:00" }, { "name": "myclabs/php-enum", @@ -5437,25 +5603,29 @@ }, { "name": "nesbot/carbon", - "version": "2.65.0", + "version": "2.71.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "09acf64155c16dc6f580f36569ae89344e9734a3" + "reference": "98276233188583f2ff845a0f992a235472d9466a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/09acf64155c16dc6f580f36569ae89344e9734a3", - "reference": "09acf64155c16dc6f580f36569ae89344e9734a3", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/98276233188583f2ff845a0f992a235472d9466a", + "reference": "98276233188583f2ff845a0f992a235472d9466a", "shasum": "" }, "require": { "ext-json": "*", "php": "^7.1.8 || ^8.0", + "psr/clock": "^1.0", "symfony/polyfill-mbstring": "^1.0", "symfony/polyfill-php80": "^1.16", "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" }, + "provide": { + "psr/clock-implementation": "1.0" + }, "require-dev": { "doctrine/dbal": "^2.0 || ^3.1.4", "doctrine/orm": "^2.7", @@ -5535,25 +5705,25 @@ "type": "tidelift" } ], - "time": "2023-01-06T15:55:01+00:00" + "time": "2023-09-25T11:31:05+00:00" }, { "name": "nette/schema", - "version": "v1.2.3", + "version": "v1.2.5", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f" + "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", - "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "url": "https://api.github.com/repos/nette/schema/zipball/0462f0166e823aad657c9224d0f849ecac1ba10a", + "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a", "shasum": "" }, "require": { "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": ">=7.1 <8.3" + "php": "7.1 - 8.3" }, "require-dev": { "nette/tester": "^2.3 || ^2.4", @@ -5595,35 +5765,36 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.3" + "source": "https://github.com/nette/schema/tree/v1.2.5" }, - "time": "2022-10-13T01:24:26+00:00" + "time": "2023-10-05T20:37:59+00:00" }, { "name": "nette/utils", - "version": "v3.2.9", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "c91bac3470c34b2ecd5400f6e6fdf0b64a836a5c" + "reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/c91bac3470c34b2ecd5400f6e6fdf0b64a836a5c", - "reference": "c91bac3470c34b2ecd5400f6e6fdf0b64a836a5c", + "url": "https://api.github.com/repos/nette/utils/zipball/a9d127dd6a203ce6d255b2e2db49759f7506e015", + "reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015", "shasum": "" }, "require": { - "php": ">=7.2 <8.3" + "php": ">=8.0 <8.4" }, "conflict": { - "nette/di": "<3.0.6" + "nette/finder": "<3", + "nette/schema": "<1.2.2" }, "require-dev": { "jetbrains/phpstorm-attributes": "dev-master", - "nette/tester": "~2.0", + "nette/tester": "^2.5", "phpstan/phpstan": "^1.0", - "tracy/tracy": "^2.3" + "tracy/tracy": "^2.9" }, "suggest": { "ext-gd": "to use Image", @@ -5631,13 +5802,12 @@ "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", "ext-json": "to use Nette\\Utils\\Json", "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -5681,22 +5851,22 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.9" + "source": "https://github.com/nette/utils/tree/v4.0.3" }, - "time": "2023-01-18T03:26:20+00:00" + "time": "2023-10-29T21:02:13+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.3", + "version": "v4.17.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", "shasum": "" }, "require": { @@ -5737,22 +5907,77 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" }, - "time": "2023-01-16T22:05:37+00:00" + "time": "2023-08-13T19:53:39+00:00" }, { - "name": "nunomaduro/termwind", - "version": "v1.15.0", + "name": "nordigen/nordigen-php", + "version": "1.1.1", "source": { "type": "git", - "url": "https://github.com/nunomaduro/termwind.git", - "reference": "594ab862396c16ead000de0c3c38f4a5cbe1938d" + "url": "https://github.com/nordigen/nordigen-php.git", + "reference": "1770384ceb8042c7275cb0e404d8b7131bdccc56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/594ab862396c16ead000de0c3c38f4a5cbe1938d", - "reference": "594ab862396c16ead000de0c3c38f4a5cbe1938d", + "url": "https://api.github.com/repos/nordigen/nordigen-php/zipball/1770384ceb8042c7275cb0e404d8b7131bdccc56", + "reference": "1770384ceb8042c7275cb0e404d8b7131bdccc56", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "guzzlehttp/guzzle": "^7.3", + "php": ">=7.4" + }, + "require-dev": { + "blastcloud/guzzler": "^2.0", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nordigen\\NordigenPHP\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nordigen Solutions", + "email": "support@nordigen.com", + "homepage": "https://nordigen.com" + } + ], + "description": "Nordigen official API client for PHP", + "homepage": "https://nordigen.com", + "keywords": [ + "Open Banking", + "api", + "nordigen", + "psd2" + ], + "support": { + "issues": "https://github.com/nordigen/nordigen-php/issues", + "source": "https://github.com/nordigen/nordigen-php/tree/1.1.1" + }, + "time": "2023-06-22T10:53:06+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v1.15.1", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/8ab0b32c8caa4a2e09700ea32925441385e4a5dc", + "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc", "shasum": "" }, "require": { @@ -5809,7 +6034,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v1.15.0" + "source": "https://github.com/nunomaduro/termwind/tree/v1.15.1" }, "funding": [ { @@ -5825,7 +6050,7 @@ "type": "github" } ], - "time": "2022-12-20T19:00:15+00:00" + "time": "2023-02-08T01:06:31+00:00" }, { "name": "nwidart/laravel-modules", @@ -5910,38 +6135,39 @@ }, { "name": "nyholm/psr7", - "version": "1.5.1", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/Nyholm/psr7.git", - "reference": "f734364e38a876a23be4d906a2a089e1315be18a" + "reference": "aa5fc277a4f5508013d571341ade0c3886d4d00e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nyholm/psr7/zipball/f734364e38a876a23be4d906a2a089e1315be18a", - "reference": "f734364e38a876a23be4d906a2a089e1315be18a", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/aa5fc277a4f5508013d571341ade0c3886d4d00e", + "reference": "aa5fc277a4f5508013d571341ade0c3886d4d00e", "shasum": "" }, "require": { - "php": ">=7.1", - "php-http/message-factory": "^1.0", + "php": ">=7.2", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.1 || ^2.0" }, "provide": { + "php-http/message-factory-implementation": "1.0", "psr/http-factory-implementation": "1.0", "psr/http-message-implementation": "1.0" }, "require-dev": { "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", "php-http/psr7-integration-tests": "^1.0", - "phpunit/phpunit": "^7.5 || 8.5 || 9.4", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", "symfony/error-handler": "^4.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -5971,7 +6197,7 @@ ], "support": { "issues": "https://github.com/Nyholm/psr7/issues", - "source": "https://github.com/Nyholm/psr7/tree/1.5.1" + "source": "https://github.com/Nyholm/psr7/tree/1.8.1" }, "funding": [ { @@ -5983,20 +6209,20 @@ "type": "github" } ], - "time": "2022-06-22T07:13:36+00:00" + "time": "2023-11-13T09:31:12+00:00" }, { "name": "omnipay/common", - "version": "v3.2.0", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-common.git", - "reference": "e278ff00676c05cd0f4aaaf6189a226f26ae056e" + "reference": "80545e9f4faab0efad36cc5f1e11a184dda22baf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-common/zipball/e278ff00676c05cd0f4aaaf6189a226f26ae056e", - "reference": "e278ff00676c05cd0f4aaaf6189a226f26ae056e", + "url": "https://api.github.com/repos/thephpleague/omnipay-common/zipball/80545e9f4faab0efad36cc5f1e11a184dda22baf", + "reference": "80545e9f4faab0efad36cc5f1e11a184dda22baf", "shasum": "" }, "require": { @@ -6005,6 +6231,7 @@ "php-http/client-implementation": "^1", "php-http/discovery": "^1.14", "php-http/message": "^1.5", + "php-http/message-factory": "^1.1", "symfony/http-foundation": "^2.1|^3|^4|^5|^6" }, "require-dev": { @@ -6067,7 +6294,7 @@ ], "support": { "issues": "https://github.com/thephpleague/omnipay-common/issues", - "source": "https://github.com/thephpleague/omnipay-common/tree/v3.2.0" + "source": "https://github.com/thephpleague/omnipay-common/tree/v3.2.1" }, "funding": [ { @@ -6075,7 +6302,7 @@ "type": "github" } ], - "time": "2021-12-30T11:32:00+00:00" + "time": "2023-05-30T12:44:03+00:00" }, { "name": "omnipay/paypal", @@ -6260,22 +6487,22 @@ }, { "name": "payfast/payfast-php-sdk", - "version": "v1.1.4", + "version": "v1.1.5", "source": { "type": "git", - "url": "https://github.com/PayFast/payfast-php-sdk.git", - "reference": "897e88dabc99283e891d6f8dbad25ccca7a787b9" + "url": "https://github.com/Payfast/payfast-php-sdk.git", + "reference": "902b2cfa7318ad947ed0eba953eea4a3831c526a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PayFast/payfast-php-sdk/zipball/897e88dabc99283e891d6f8dbad25ccca7a787b9", - "reference": "897e88dabc99283e891d6f8dbad25ccca7a787b9", + "url": "https://api.github.com/repos/Payfast/payfast-php-sdk/zipball/902b2cfa7318ad947ed0eba953eea4a3831c526a", + "reference": "902b2cfa7318ad947ed0eba953eea4a3831c526a", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/guzzle": ">=6.0.0", - "php": ">=7.2.5" + "php": ">=8.1" }, "require-dev": { "phpunit/phpunit": "^9" @@ -6283,7 +6510,7 @@ "type": "library", "autoload": { "psr-4": { - "PayFast\\": "lib/" + "Payfast\\": "lib/" } }, "notification-url": "https://packagist.org/downloads/", @@ -6292,11 +6519,11 @@ ], "authors": [ { - "name": "Claire Grant", - "email": "claire.grant@payfast.co.za" + "name": "Payfast", + "email": "support@payfast.help" } ], - "description": "PayFast PHP Library", + "description": "Payfast PHP Library", "keywords": [ "api", "onsite", @@ -6304,33 +6531,33 @@ "php" ], "support": { - "issues": "https://github.com/PayFast/payfast-php-sdk/issues", - "source": "https://github.com/PayFast/payfast-php-sdk/tree/v1.1.4" + "issues": "https://github.com/Payfast/payfast-php-sdk/issues", + "source": "https://github.com/Payfast/payfast-php-sdk/tree/v1.1.5" }, - "time": "2022-12-20T10:39:51+00:00" + "abandoned": true, + "time": "2023-10-11T09:57:01+00:00" }, { "name": "php-http/client-common", - "version": "2.6.0", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/php-http/client-common.git", - "reference": "45db684cd4e186dcdc2b9c06b22970fe123796c0" + "reference": "880509727a447474d2a71b7d7fa5d268ddd3db4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/client-common/zipball/45db684cd4e186dcdc2b9c06b22970fe123796c0", - "reference": "45db684cd4e186dcdc2b9c06b22970fe123796c0", + "url": "https://api.github.com/repos/php-http/client-common/zipball/880509727a447474d2a71b7d7fa5d268ddd3db4b", + "reference": "880509727a447474d2a71b7d7fa5d268ddd3db4b", "shasum": "" }, "require": { "php": "^7.1 || ^8.0", "php-http/httplug": "^2.0", "php-http/message": "^1.6", - "php-http/message-factory": "^1.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.0 || ^2.0", "symfony/options-resolver": "~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0 || ^6.0", "symfony/polyfill-php80": "^1.17" }, @@ -6340,7 +6567,7 @@ "nyholm/psr7": "^1.2", "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1", "phpspec/prophecy": "^1.10.2", - "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.3" + "phpunit/phpunit": "^7.5.20 || ^8.5.33 || ^9.6.7" }, "suggest": { "ext-json": "To detect JSON responses with the ContentTypePlugin", @@ -6350,11 +6577,6 @@ "php-http/stopwatch-plugin": "Symfony Stopwatch plugin" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3.x-dev" - } - }, "autoload": { "psr-4": { "Http\\Client\\Common\\": "src/" @@ -6380,49 +6602,59 @@ ], "support": { "issues": "https://github.com/php-http/client-common/issues", - "source": "https://github.com/php-http/client-common/tree/2.6.0" + "source": "https://github.com/php-http/client-common/tree/2.7.0" }, - "time": "2022-09-29T09:59:43+00:00" + "time": "2023-05-17T06:46:59+00:00" }, { "name": "php-http/discovery", - "version": "1.14.3", + "version": "1.19.1", "source": { "type": "git", "url": "https://github.com/php-http/discovery.git", - "reference": "31d8ee46d0215108df16a8527c7438e96a4d7735" + "reference": "57f3de01d32085fea20865f9b16fb0e69347c39e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/discovery/zipball/31d8ee46d0215108df16a8527c7438e96a4d7735", - "reference": "31d8ee46d0215108df16a8527c7438e96a4d7735", + "url": "https://api.github.com/repos/php-http/discovery/zipball/57f3de01d32085fea20865f9b16fb0e69347c39e", + "reference": "57f3de01d32085fea20865f9b16fb0e69347c39e", "shasum": "" }, "require": { + "composer-plugin-api": "^1.0|^2.0", "php": "^7.1 || ^8.0" }, "conflict": { - "nyholm/psr7": "<1.0" + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" }, "require-dev": { + "composer/composer": "^1.0.2|^2.0", "graham-campbell/phpspec-skip-example-extension": "^5.0", "php-http/httplug": "^1.0 || ^2.0", "php-http/message-factory": "^1.0", - "phpspec/phpspec": "^5.1 || ^6.1" + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "symfony/phpunit-bridge": "^6.2" }, - "suggest": { - "php-http/message": "Allow to use Guzzle, Diactoros or Slim Framework factories" - }, - "type": "library", + "type": "composer-plugin", "extra": { - "branch-alias": { - "dev-master": "1.9-dev" - } + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true }, "autoload": { "psr-4": { "Http\\Discovery\\": "src/" - } + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6434,7 +6666,7 @@ "email": "mark.sagikazar@gmail.com" } ], - "description": "Finds installed HTTPlug implementations and PSR-7 message factories", + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", "homepage": "http://php-http.org", "keywords": [ "adapter", @@ -6443,13 +6675,14 @@ "factory", "http", "message", + "psr17", "psr7" ], "support": { "issues": "https://github.com/php-http/discovery/issues", - "source": "https://github.com/php-http/discovery/tree/1.14.3" + "source": "https://github.com/php-http/discovery/tree/1.19.1" }, - "time": "2022-07-11T14:04:40+00:00" + "time": "2023-07-11T07:02:26+00:00" }, { "name": "php-http/guzzle7-adapter", @@ -6515,34 +6748,29 @@ }, { "name": "php-http/httplug", - "version": "2.3.0", + "version": "2.4.0", "source": { "type": "git", "url": "https://github.com/php-http/httplug.git", - "reference": "f640739f80dfa1152533976e3c112477f69274eb" + "reference": "625ad742c360c8ac580fcc647a1541d29e257f67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/httplug/zipball/f640739f80dfa1152533976e3c112477f69274eb", - "reference": "f640739f80dfa1152533976e3c112477f69274eb", + "url": "https://api.github.com/repos/php-http/httplug/zipball/625ad742c360c8ac580fcc647a1541d29e257f67", + "reference": "625ad742c360c8ac580fcc647a1541d29e257f67", "shasum": "" }, "require": { "php": "^7.1 || ^8.0", "php-http/promise": "^1.1", "psr/http-client": "^1.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "require-dev": { - "friends-of-phpspec/phpspec-code-coverage": "^4.1", - "phpspec/phpspec": "^5.1 || ^6.0" + "friends-of-phpspec/phpspec-code-coverage": "^4.1 || ^5.0 || ^6.0", + "phpspec/phpspec": "^5.1 || ^6.0 || ^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, "autoload": { "psr-4": { "Http\\Client\\": "src/" @@ -6571,29 +6799,28 @@ ], "support": { "issues": "https://github.com/php-http/httplug/issues", - "source": "https://github.com/php-http/httplug/tree/2.3.0" + "source": "https://github.com/php-http/httplug/tree/2.4.0" }, - "time": "2022-02-21T09:52:22+00:00" + "time": "2023-04-14T15:10:03+00:00" }, { "name": "php-http/message", - "version": "1.13.0", + "version": "1.16.0", "source": { "type": "git", "url": "https://github.com/php-http/message.git", - "reference": "7886e647a30a966a1a8d1dad1845b71ca8678361" + "reference": "47a14338bf4ebd67d317bf1144253d7db4ab55fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/message/zipball/7886e647a30a966a1a8d1dad1845b71ca8678361", - "reference": "7886e647a30a966a1a8d1dad1845b71ca8678361", + "url": "https://api.github.com/repos/php-http/message/zipball/47a14338bf4ebd67d317bf1144253d7db4ab55fd", + "reference": "47a14338bf4ebd67d317bf1144253d7db4ab55fd", "shasum": "" }, "require": { "clue/stream-filter": "^1.5", - "php": "^7.1 || ^8.0", - "php-http/message-factory": "^1.0.2", - "psr/http-message": "^1.0" + "php": "^7.2 || ^8.0", + "psr/http-message": "^1.1 || ^2.0" }, "provide": { "php-http/message-factory-implementation": "1.0" @@ -6601,8 +6828,9 @@ "require-dev": { "ergebnis/composer-normalize": "^2.6", "ext-zlib": "*", - "guzzlehttp/psr7": "^1.0", - "laminas/laminas-diactoros": "^2.0", + "guzzlehttp/psr7": "^1.0 || ^2.0", + "laminas/laminas-diactoros": "^2.0 || ^3.0", + "php-http/message-factory": "^1.0.2", "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1", "slim/slim": "^3.0" }, @@ -6613,11 +6841,6 @@ "slim/slim": "Used with Slim Framework PSR-7 implementation" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, "autoload": { "files": [ "src/filters.php" @@ -6645,32 +6868,32 @@ ], "support": { "issues": "https://github.com/php-http/message/issues", - "source": "https://github.com/php-http/message/tree/1.13.0" + "source": "https://github.com/php-http/message/tree/1.16.0" }, - "time": "2022-02-11T13:41:14+00:00" + "time": "2023-05-17T06:43:38+00:00" }, { "name": "php-http/message-factory", - "version": "v1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-http/message-factory.git", - "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1" + "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1", - "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/4d8778e1c7d405cbb471574821c1ff5b68cc8f57", + "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57", "shasum": "" }, "require": { "php": ">=5.4", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -6699,37 +6922,33 @@ ], "support": { "issues": "https://github.com/php-http/message-factory/issues", - "source": "https://github.com/php-http/message-factory/tree/master" + "source": "https://github.com/php-http/message-factory/tree/1.1.0" }, - "time": "2015-12-19T14:08:53+00:00" + "abandoned": "psr/http-factory", + "time": "2023-04-14T14:16:17+00:00" }, { "name": "php-http/promise", - "version": "1.1.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/php-http/promise.git", - "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88" + "reference": "44a67cb59f708f826f3bec35f22030b3edb90119" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/promise/zipball/4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", - "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", + "url": "https://api.github.com/repos/php-http/promise/zipball/44a67cb59f708f826f3bec35f22030b3edb90119", + "reference": "44a67cb59f708f826f3bec35f22030b3edb90119", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "friends-of-phpspec/phpspec-code-coverage": "^4.3.2", - "phpspec/phpspec": "^5.1.2 || ^6.2" + "friends-of-phpspec/phpspec-code-coverage": "^4.3.2 || ^6.3", + "phpspec/phpspec": "^5.1.2 || ^6.2 || ^7.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, "autoload": { "psr-4": { "Http\\Promise\\": "src/" @@ -6756,30 +6975,30 @@ ], "support": { "issues": "https://github.com/php-http/promise/issues", - "source": "https://github.com/php-http/promise/tree/1.1.0" + "source": "https://github.com/php-http/promise/tree/1.2.1" }, - "time": "2020-07-07T09:29:14+00:00" + "time": "2023-11-08T12:57:08+00:00" }, { "name": "phpoption/phpoption", - "version": "1.9.0", + "version": "1.9.2", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab" + "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", - "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820", + "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8", - "phpunit/phpunit": "^8.5.28 || ^9.5.21" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" }, "type": "library", "extra": { @@ -6821,7 +7040,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.0" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.2" }, "funding": [ { @@ -6833,20 +7052,20 @@ "type": "tidelift" } ], - "time": "2022-07-30T15:51:26+00:00" + "time": "2023-11-12T21:59:55+00:00" }, { "name": "phpseclib/phpseclib", - "version": "3.0.18", + "version": "3.0.33", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "f28693d38ba21bb0d9f0c411ee5dae2b178201da" + "reference": "33fa69b2514a61138dd48e7a49f99445711e0ad0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/f28693d38ba21bb0d9f0c411ee5dae2b178201da", - "reference": "f28693d38ba21bb0d9f0c411ee5dae2b178201da", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/33fa69b2514a61138dd48e7a49f99445711e0ad0", + "reference": "33fa69b2514a61138dd48e7a49f99445711e0ad0", "shasum": "" }, "require": { @@ -6927,7 +7146,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.18" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.33" }, "funding": [ { @@ -6943,7 +7162,7 @@ "type": "tidelift" } ], - "time": "2022-12-17T18:26:50+00:00" + "time": "2023-10-21T14:00:39+00:00" }, { "name": "pragmarx/google2fa", @@ -7199,21 +7418,21 @@ }, { "name": "psr/http-client", - "version": "1.0.1", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -7233,7 +7452,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP clients", @@ -7245,27 +7464,27 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/master" + "source": "https://github.com/php-fig/http-client" }, - "time": "2020-06-29T06:28:15+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + "reference": "e616d01114759c4c489f93b099585439f795fe35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", "shasum": "" }, "require": { "php": ">=7.0.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -7285,7 +7504,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for PSR-7 HTTP message factories", @@ -7300,31 +7519,31 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" }, - "time": "2019-04-30T12:38:16+00:00" + "time": "2023-04-10T20:10:41+00:00" }, { "name": "psr/http-message", - "version": "1.0.1", + "version": "2.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -7339,7 +7558,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -7353,36 +7572,36 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/master" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, - "time": "2016-08-06T14:39:51+00:00" + "time": "2023-04-04T09:54:51+00:00" }, { "name": "psr/log", - "version": "1.1.4", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -7403,9 +7622,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/2.0.0" }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2021-07-14T16:41:46+00:00" }, { "name": "psr/simple-cache", @@ -7460,16 +7679,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.11", + "version": "v0.11.22", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "ba67f2d26278ec9266a5cfe0acba33a8ca1277ae" + "reference": "128fa1b608be651999ed9789c95e6e2a31b5802b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ba67f2d26278ec9266a5cfe0acba33a8ca1277ae", - "reference": "ba67f2d26278ec9266a5cfe0acba33a8ca1277ae", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/128fa1b608be651999ed9789c95e6e2a31b5802b", + "reference": "128fa1b608be651999ed9789c95e6e2a31b5802b", "shasum": "" }, "require": { @@ -7498,7 +7717,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "0.11.x-dev" + "dev-0.11": "0.11.x-dev" + }, + "bamarni-bin": { + "bin-links": false, + "forward-command": false } }, "autoload": { @@ -7530,9 +7753,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.11" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.22" }, - "time": "2023-01-23T16:14:59+00:00" + "time": "2023-10-14T21:56:36+00:00" }, { "name": "ralouphie/getallheaders", @@ -7669,20 +7892,20 @@ }, { "name": "ramsey/uuid", - "version": "4.7.3", + "version": "4.7.5", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "433b2014e3979047db08a17a205f410ba3869cf2" + "reference": "5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/433b2014e3979047db08a17a205f410ba3869cf2", - "reference": "433b2014e3979047db08a17a205f410ba3869cf2", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e", + "reference": "5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11", "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" @@ -7745,7 +7968,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.3" + "source": "https://github.com/ramsey/uuid/tree/4.7.5" }, "funding": [ { @@ -7757,20 +7980,20 @@ "type": "tidelift" } ], - "time": "2023-01-12T18:13:24+00:00" + "time": "2023-11-08T05:53:05+00:00" }, { "name": "razorpay/razorpay", - "version": "2.8.5", + "version": "2.8.7", "source": { "type": "git", "url": "https://github.com/razorpay/razorpay-php.git", - "reference": "31027cfb689b9480d67419dbec7c203097e9d9ac" + "reference": "2180c8c3c39678623f5cb8f639c39a706de14c44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/razorpay/razorpay-php/zipball/31027cfb689b9480d67419dbec7c203097e9d9ac", - "reference": "31027cfb689b9480d67419dbec7c203097e9d9ac", + "url": "https://api.github.com/repos/razorpay/razorpay-php/zipball/2180c8c3c39678623f5cb8f639c39a706de14c44", + "reference": "2180c8c3c39678623f5cb8f639c39a706de14c44", "shasum": "" }, "require": { @@ -7822,20 +8045,20 @@ "issues": "https://github.com/Razorpay/razorpay-php/issues", "source": "https://github.com/Razorpay/razorpay-php" }, - "time": "2022-10-19T07:41:27+00:00" + "time": "2023-09-11T08:31:26+00:00" }, { "name": "rmccue/requests", - "version": "v2.0.5", + "version": "v2.0.9", "source": { "type": "git", "url": "https://github.com/WordPress/Requests.git", - "reference": "b717f1d2f4ef7992ec0c127747ed8b7e170c2f49" + "reference": "422612952ff3bd5163039f8889eaaaab95a432eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/WordPress/Requests/zipball/b717f1d2f4ef7992ec0c127747ed8b7e170c2f49", - "reference": "b717f1d2f4ef7992ec0c127747ed8b7e170c2f49", + "url": "https://api.github.com/repos/WordPress/Requests/zipball/422612952ff3bd5163039f8889eaaaab95a432eb", + "reference": "422612952ff3bd5163039f8889eaaaab95a432eb", "shasum": "" }, "require": { @@ -7853,6 +8076,12 @@ "wp-coding-standards/wpcs": "^2.0", "yoast/phpunit-polyfills": "^1.0.0" }, + "suggest": { + "art4/requests-psr18-adapter": "For using Requests as a PSR-18 HTTP Client", + "ext-curl": "For improved performance", + "ext-openssl": "For secure transport support", + "ext-zlib": "For improved performance when decompressing encoded streams" + }, "type": "library", "autoload": { "files": [ @@ -7903,29 +8132,32 @@ "issues": "https://github.com/WordPress/Requests/issues", "source": "https://github.com/WordPress/Requests" }, - "time": "2022-10-11T08:15:28+00:00" + "time": "2023-11-08T19:22:04+00:00" }, { "name": "sabre/uri", - "version": "2.3.2", + "version": "2.3.3", "source": { "type": "git", "url": "https://github.com/sabre-io/uri.git", - "reference": "eceb4a1b8b680b45e215574222d6ca00be541970" + "reference": "7e0e7dfd0b7e14346a27eabd66e843a6e7f1812b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sabre-io/uri/zipball/eceb4a1b8b680b45e215574222d6ca00be541970", - "reference": "eceb4a1b8b680b45e215574222d6ca00be541970", + "url": "https://api.github.com/repos/sabre-io/uri/zipball/7e0e7dfd0b7e14346a27eabd66e843a6e7f1812b", + "reference": "7e0e7dfd0b7e14346a27eabd66e843a6e7f1812b", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.9", - "phpstan/phpstan": "^1.8", - "phpunit/phpunit": "^9.0" + "friendsofphp/php-cs-fixer": "^3.17", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-phpunit": "^1.3", + "phpstan/phpstan-strict-rules": "^1.5", + "phpunit/phpunit": "^9.6" }, "type": "library", "autoload": { @@ -7960,7 +8192,7 @@ "issues": "https://github.com/sabre-io/uri/issues", "source": "https://github.com/fruux/sabre-uri" }, - "time": "2022-09-19T11:58:52+00:00" + "time": "2023-06-09T06:54:04+00:00" }, { "name": "sabre/xml", @@ -8032,21 +8264,21 @@ }, { "name": "sentry/sdk", - "version": "3.3.0", + "version": "3.5.0", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-php-sdk.git", - "reference": "d0678fc7274dbb03046ed05cb24eb92945bedf8e" + "reference": "cd91b752f07c4bab9fb3b173f81af68a78a78d6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-php-sdk/zipball/d0678fc7274dbb03046ed05cb24eb92945bedf8e", - "reference": "d0678fc7274dbb03046ed05cb24eb92945bedf8e", + "url": "https://api.github.com/repos/getsentry/sentry-php-sdk/zipball/cd91b752f07c4bab9fb3b173f81af68a78a78d6d", + "reference": "cd91b752f07c4bab9fb3b173f81af68a78a78d6d", "shasum": "" }, "require": { "http-interop/http-factory-guzzle": "^1.0", - "sentry/sentry": "^3.9", + "sentry/sentry": "^3.19", "symfony/http-client": "^4.3|^5.0|^6.0" }, "type": "metapackage", @@ -8073,7 +8305,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-php-sdk/issues", - "source": "https://github.com/getsentry/sentry-php-sdk/tree/3.3.0" + "source": "https://github.com/getsentry/sentry-php-sdk/tree/3.5.0" }, "funding": [ { @@ -8085,38 +8317,38 @@ "type": "custom" } ], - "time": "2022-10-11T09:05:00+00:00" + "time": "2023-06-12T17:50:36+00:00" }, { "name": "sentry/sentry", - "version": "3.12.1", + "version": "3.22.1", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-php.git", - "reference": "155bb9b78438999de4529d6f051465be15a58bc5" + "reference": "8859631ba5ab15bc1af420b0eeed19ecc6c9d81d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/155bb9b78438999de4529d6f051465be15a58bc5", - "reference": "155bb9b78438999de4529d6f051465be15a58bc5", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/8859631ba5ab15bc1af420b0eeed19ecc6c9d81d", + "reference": "8859631ba5ab15bc1af420b0eeed19ecc6c9d81d", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", - "guzzlehttp/promises": "^1.4", - "guzzlehttp/psr7": "^1.8.4|^2.1.1", + "guzzlehttp/promises": "^1.5.3|^2.0", "jean85/pretty-package-versions": "^1.5|^2.0.4", "php": "^7.2|^8.0", "php-http/async-client-implementation": "^1.0", "php-http/client-common": "^1.5|^2.0", - "php-http/discovery": "^1.11", + "php-http/discovery": "^1.15", "php-http/httplug": "^1.1|^2.0", "php-http/message": "^1.5", + "php-http/message-factory": "^1.1", "psr/http-factory": "^1.0", - "psr/http-message-implementation": "^1.0", + "psr/http-factory-implementation": "^1.0", "psr/log": "^1.0|^2.0|^3.0", - "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0", + "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0|^7.0", "symfony/polyfill-php80": "^1.17" }, "conflict": { @@ -8125,6 +8357,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.19|3.4.*", + "guzzlehttp/psr7": "^1.8.4|^2.1.1", "http-interop/http-factory-guzzle": "^1.0", "monolog/monolog": "^1.6|^2.0|^3.0", "nikic/php-parser": "^4.10.3", @@ -8141,11 +8374,6 @@ "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.12.x-dev" - } - }, "autoload": { "files": [ "src/functions.php" @@ -8156,7 +8384,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { @@ -8177,7 +8405,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-php/issues", - "source": "https://github.com/getsentry/sentry-php/tree/3.12.1" + "source": "https://github.com/getsentry/sentry-php/tree/3.22.1" }, "funding": [ { @@ -8189,38 +8417,37 @@ "type": "custom" } ], - "time": "2023-01-12T12:24:27+00:00" + "time": "2023-11-13T11:47:28+00:00" }, { "name": "sentry/sentry-laravel", - "version": "3.1.3", + "version": "3.8.2", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "392efc852241ee95b1a68141247d461dd690814a" + "reference": "1293e5732f8405e12f000cdf5dee78c927a18de0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/392efc852241ee95b1a68141247d461dd690814a", - "reference": "392efc852241ee95b1a68141247d461dd690814a", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/1293e5732f8405e12f000cdf5dee78c927a18de0", + "reference": "1293e5732f8405e12f000cdf5dee78c927a18de0", "shasum": "" }, "require": { - "illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0", + "illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0", "nyholm/psr7": "^1.0", "php": "^7.2 | ^8.0", - "sentry/sdk": "^3.3", - "sentry/sentry": "^3.12", + "sentry/sdk": "^3.4", + "sentry/sentry": "^3.20.1", "symfony/psr-http-message-bridge": "^1.0 | ^2.0" }, - "conflict": { - "laravel/lumen-framework": "*" - }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.11", - "laravel/framework": "^6.0 | ^7.0 | ^8.0 | ^9.0", + "laravel/folio": "^1.0", + "laravel/framework": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0", "mockery/mockery": "^1.3", - "orchestra/testbench": "^4.7 | ^5.1 | ^6.0 | ^7.0", + "orchestra/testbench": "^4.7 | ^5.1 | ^6.0 | ^7.0 | ^8.0", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^8.4 | ^9.3" }, "type": "library", @@ -8248,7 +8475,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { @@ -8270,7 +8497,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-laravel/issues", - "source": "https://github.com/getsentry/sentry-laravel/tree/3.1.3" + "source": "https://github.com/getsentry/sentry-laravel/tree/3.8.2" }, "funding": [ { @@ -8282,26 +8509,22 @@ "type": "custom" } ], - "time": "2023-01-12T12:24:44+00:00" + "time": "2023-10-12T14:38:46+00:00" }, { "name": "setasign/fpdf", - "version": "1.8.5", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/Setasign/FPDF.git", - "reference": "f4104a04c9a3f95c4c26a0a0531abebcc980987a" + "reference": "d77904018090c17dc9f3ab6e944679a7a47e710a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Setasign/FPDF/zipball/f4104a04c9a3f95c4c26a0a0531abebcc980987a", - "reference": "f4104a04c9a3f95c4c26a0a0531abebcc980987a", + "url": "https://api.github.com/repos/Setasign/FPDF/zipball/d77904018090c17dc9f3ab6e944679a7a47e710a", + "reference": "d77904018090c17dc9f3ab6e944679a7a47e710a", "shasum": "" }, - "require": { - "ext-gd": "*", - "ext-zlib": "*" - }, "type": "library", "autoload": { "classmap": [ @@ -8326,22 +8549,22 @@ "pdf" ], "support": { - "source": "https://github.com/Setasign/FPDF/tree/1.8.5" + "source": "https://github.com/Setasign/FPDF/tree/1.8.2" }, - "time": "2022-11-18T07:02:00+00:00" + "time": "2019-12-08T10:32:10+00:00" }, { "name": "setasign/fpdi", - "version": "v2.3.6", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/Setasign/FPDI.git", - "reference": "6231e315f73e4f62d72b73f3d6d78ff0eed93c31" + "reference": "ecf0459643ec963febfb9a5d529dcd93656006a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Setasign/FPDI/zipball/6231e315f73e4f62d72b73f3d6d78ff0eed93c31", - "reference": "6231e315f73e4f62d72b73f3d6d78ff0eed93c31", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/ecf0459643ec963febfb9a5d529dcd93656006a4", + "reference": "ecf0459643ec963febfb9a5d529dcd93656006a4", "shasum": "" }, "require": { @@ -8354,7 +8577,7 @@ "require-dev": { "phpunit/phpunit": "~5.7", "setasign/fpdf": "~1.8", - "setasign/tfpdf": "1.31", + "setasign/tfpdf": "~1.31", "squizlabs/php_codesniffer": "^3.5", "tecnickcom/tcpdf": "~6.2" }, @@ -8392,7 +8615,7 @@ ], "support": { "issues": "https://github.com/Setasign/FPDI/issues", - "source": "https://github.com/Setasign/FPDI/tree/v2.3.6" + "source": "https://github.com/Setasign/FPDI/tree/v2.5.0" }, "funding": [ { @@ -8400,29 +8623,30 @@ "type": "tidelift" } ], - "time": "2021-02-11T11:37:01+00:00" + "time": "2023-09-28T10:46:27+00:00" }, { "name": "socialiteproviders/apple", - "version": "5.3.0", + "version": "5.6.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Apple.git", - "reference": "13bfd5ad4a6ab33ecab35d933deba01e9de6e404" + "reference": "4f0f06e463824f0df6151c768db2fec6610ea055" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Apple/zipball/13bfd5ad4a6ab33ecab35d933deba01e9de6e404", - "reference": "13bfd5ad4a6ab33ecab35d933deba01e9de6e404", + "url": "https://api.github.com/repos/SocialiteProviders/Apple/zipball/4f0f06e463824f0df6151c768db2fec6610ea055", + "reference": "4f0f06e463824f0df6151c768db2fec6610ea055", "shasum": "" }, "require": { "ext-json": "*", "ext-openssl": "*", - "firebase/php-jwt": "^6.2", - "lcobucci/jwt": "^4.1.5", - "php": "^7.4 || ^8.0", - "socialiteproviders/manager": "~4.0" + "firebase/php-jwt": "^6.8", + "lcobucci/clock": "^2.0 || ^3.0", + "lcobucci/jwt": "^4.1.5 || ^5.0.0", + "php": "^8.0", + "socialiteproviders/manager": "^4.4" }, "suggest": { "ahilmurugesan/socialite-apple-helper": "Automatic Apple client key generation and management." @@ -8471,26 +8695,26 @@ "issues": "https://github.com/socialiteproviders/providers/issues", "source": "https://github.com/socialiteproviders/providers" }, - "time": "2022-07-18T08:37:00+00:00" + "time": "2023-09-11T21:59:09+00:00" }, { "name": "socialiteproviders/manager", - "version": "v4.3.0", + "version": "v4.4.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Manager.git", - "reference": "47402cbc5b7ef445317e799bf12fd5a12062206c" + "reference": "df5e45b53d918ec3d689f014d98a6c838b98ed96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/47402cbc5b7ef445317e799bf12fd5a12062206c", - "reference": "47402cbc5b7ef445317e799bf12fd5a12062206c", + "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/df5e45b53d918ec3d689f014d98a6c838b98ed96", + "reference": "df5e45b53d918ec3d689f014d98a6c838b98ed96", "shasum": "" }, "require": { "illuminate/support": "^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0", "laravel/socialite": "~5.0", - "php": "^7.4 || ^8.0" + "php": "^8.0" }, "require-dev": { "mockery/mockery": "^1.2", @@ -8545,20 +8769,20 @@ "issues": "https://github.com/socialiteproviders/manager/issues", "source": "https://github.com/socialiteproviders/manager" }, - "time": "2023-01-26T23:11:27+00:00" + "time": "2023-08-27T23:46:34+00:00" }, { "name": "socialiteproviders/microsoft", - "version": "4.2.1", + "version": "4.2.2", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Microsoft.git", - "reference": "2289ce5b037e5d48fff9f38c2354a8401f37609e" + "reference": "19bc79810d7319c466f38552546b2c233b634059" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Microsoft/zipball/2289ce5b037e5d48fff9f38c2354a8401f37609e", - "reference": "2289ce5b037e5d48fff9f38c2354a8401f37609e", + "url": "https://api.github.com/repos/SocialiteProviders/Microsoft/zipball/19bc79810d7319c466f38552546b2c233b634059", + "reference": "19bc79810d7319c466f38552546b2c233b634059", "shasum": "" }, "require": { @@ -8595,7 +8819,7 @@ "issues": "https://github.com/socialiteproviders/providers/issues", "source": "https://github.com/socialiteproviders/providers" }, - "time": "2023-01-13T01:15:41+00:00" + "time": "2023-03-02T09:58:36+00:00" }, { "name": "sprain/swiss-qr-bill", @@ -8776,23 +9000,23 @@ }, { "name": "symfony/console", - "version": "v6.2.5", + "version": "v6.3.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "3e294254f2191762c1d137aed4b94e966965e985" + "reference": "0d14a9f6d04d4ac38a8cea1171f4554e325dae92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/3e294254f2191762c1d137aed4b94e966965e985", - "reference": "3e294254f2191762c1d137aed4b94e966965e985", + "url": "https://api.github.com/repos/symfony/console/zipball/0d14a9f6d04d4ac38a8cea1171f4554e325dae92", + "reference": "0d14a9f6d04d4ac38a8cea1171f4554e325dae92", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/string": "^5.4|^6.0" }, "conflict": { @@ -8814,12 +9038,6 @@ "symfony/process": "^5.4|^6.0", "symfony/var-dumper": "^5.4|^6.0" }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, "type": "library", "autoload": { "psr-4": { @@ -8847,12 +9065,12 @@ "homepage": "https://symfony.com", "keywords": [ "cli", - "command line", + "command-line", "console", "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.2.5" + "source": "https://github.com/symfony/console/tree/v6.3.8" }, "funding": [ { @@ -8868,20 +9086,20 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-10-31T08:09:35+00:00" }, { "name": "symfony/css-selector", - "version": "v6.2.5", + "version": "v6.3.2", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "bf1b9d4ad8b1cf0dbde8b08e0135a2f6259b9ba1" + "reference": "883d961421ab1709877c10ac99451632a3d6fa57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/bf1b9d4ad8b1cf0dbde8b08e0135a2f6259b9ba1", - "reference": "bf1b9d4ad8b1cf0dbde8b08e0135a2f6259b9ba1", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/883d961421ab1709877c10ac99451632a3d6fa57", + "reference": "883d961421ab1709877c10ac99451632a3d6fa57", "shasum": "" }, "require": { @@ -8917,7 +9135,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.2.5" + "source": "https://github.com/symfony/css-selector/tree/v6.3.2" }, "funding": [ { @@ -8933,20 +9151,20 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-07-12T16:00:22+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.2.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", - "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", "shasum": "" }, "require": { @@ -8955,7 +9173,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -8984,7 +9202,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -9000,20 +9218,20 @@ "type": "tidelift" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/error-handler", - "version": "v6.2.5", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "0092696af0be8e6124b042fbe2890ca1788d7b28" + "reference": "1f69476b64fb47105c06beef757766c376b548c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/0092696af0be8e6124b042fbe2890ca1788d7b28", - "reference": "0092696af0be8e6124b042fbe2890ca1788d7b28", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/1f69476b64fb47105c06beef757766c376b548c4", + "reference": "1f69476b64fb47105c06beef757766c376b548c4", "shasum": "" }, "require": { @@ -9021,8 +9239,11 @@ "psr/log": "^1|^2|^3", "symfony/var-dumper": "^5.4|^6.0" }, + "conflict": { + "symfony/deprecation-contracts": "<2.5" + }, "require-dev": { - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-kernel": "^5.4|^6.0", "symfony/serializer": "^5.4|^6.0" }, @@ -9055,7 +9276,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.2.5" + "source": "https://github.com/symfony/error-handler/tree/v6.3.5" }, "funding": [ { @@ -9071,28 +9292,29 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-09-12T06:57:20+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.2.5", + "version": "v6.3.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "f02d108b5e9fd4a6245aa73a9d2df2ec060c3e68" + "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f02d108b5e9fd4a6245aa73a9d2df2ec060c3e68", - "reference": "f02d108b5e9fd4a6245aa73a9d2df2ec060c3e68", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/adb01fe097a4ee930db9258a3cc906b5beb5cf2e", + "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/event-dispatcher-contracts": "^2|^3" + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<5.4" + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", @@ -9105,13 +9327,9 @@ "symfony/error-handler": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/http-foundation": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/stopwatch": "^5.4|^6.0" }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, "type": "library", "autoload": { "psr-4": { @@ -9138,7 +9356,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.5" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.2" }, "funding": [ { @@ -9154,33 +9372,30 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-07-06T06:56:43+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.2.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "0782b0b52a737a05b4383d0df35a474303cabdae" + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0782b0b52a737a05b4383d0df35a474303cabdae", - "reference": "0782b0b52a737a05b4383d0df35a474303cabdae", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", "shasum": "" }, "require": { "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -9217,7 +9432,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.2.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0" }, "funding": [ { @@ -9233,20 +9448,20 @@ "type": "tidelift" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/filesystem", - "version": "v6.2.5", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "e59e8a4006afd7f5654786a83b4fcb8da98f4593" + "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e59e8a4006afd7f5654786a83b4fcb8da98f4593", - "reference": "e59e8a4006afd7f5654786a83b4fcb8da98f4593", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/edd36776956f2a6fcf577edb5b05eb0e3bdc52ae", + "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae", "shasum": "" }, "require": { @@ -9280,7 +9495,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.2.5" + "source": "https://github.com/symfony/filesystem/tree/v6.3.1" }, "funding": [ { @@ -9296,20 +9511,20 @@ "type": "tidelift" } ], - "time": "2023-01-20T17:45:48+00:00" + "time": "2023-06-01T08:30:39+00:00" }, { "name": "symfony/finder", - "version": "v6.2.5", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "c90dc446976a612e3312a97a6ec0069ab0c2099c" + "reference": "a1b31d88c0e998168ca7792f222cbecee47428c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/c90dc446976a612e3312a97a6ec0069ab0c2099c", - "reference": "c90dc446976a612e3312a97a6ec0069ab0c2099c", + "url": "https://api.github.com/repos/symfony/finder/zipball/a1b31d88c0e998168ca7792f222cbecee47428c4", + "reference": "a1b31d88c0e998168ca7792f222cbecee47428c4", "shasum": "" }, "require": { @@ -9344,7 +9559,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.2.5" + "source": "https://github.com/symfony/finder/tree/v6.3.5" }, "funding": [ { @@ -9360,28 +9575,32 @@ "type": "tidelift" } ], - "time": "2023-01-20T17:45:48+00:00" + "time": "2023-09-26T12:56:25+00:00" }, { "name": "symfony/http-client", - "version": "v6.2.5", + "version": "v6.3.8", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "c5e5b772033eae96ae82f2190546399ad18c1373" + "reference": "0314e2d49939a9831929d6fc81c01c6df137fd0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/c5e5b772033eae96ae82f2190546399ad18c1373", - "reference": "c5e5b772033eae96ae82f2190546399ad18c1373", + "url": "https://api.github.com/repos/symfony/http-client/zipball/0314e2d49939a9831929d6fc81c01c6df137fd0a", + "reference": "0314e2d49939a9831929d6fc81c01c6df137fd0a", "shasum": "" }, "require": { "php": ">=8.1", "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "^3", - "symfony/service-contracts": "^1.0|^2|^3" + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.3" }, "provide": { "php-http/async-client-implementation": "*", @@ -9428,8 +9647,11 @@ ], "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", "homepage": "https://symfony.com", + "keywords": [ + "http" + ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.2.5" + "source": "https://github.com/symfony/http-client/tree/v6.3.8" }, "funding": [ { @@ -9445,32 +9667,29 @@ "type": "tidelift" } ], - "time": "2023-01-13T08:35:57+00:00" + "time": "2023-11-06T18:31:59+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.2.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "c5f587eb445224ddfeb05b5ee703476742d730bf" + "reference": "1ee70e699b41909c209a0c930f11034b93578654" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/c5f587eb445224ddfeb05b5ee703476742d730bf", - "reference": "c5f587eb445224ddfeb05b5ee703476742d730bf", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1ee70e699b41909c209a0c930f11034b93578654", + "reference": "1ee70e699b41909c209a0c930f11034b93578654", "shasum": "" }, "require": { "php": ">=8.1" }, - "suggest": { - "symfony/http-client-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -9510,7 +9729,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.2.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.4.0" }, "funding": [ { @@ -9526,42 +9745,41 @@ "type": "tidelift" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2023-07-30T20:28:31+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.2.5", + "version": "v6.3.8", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "9d081ead9d3432e2e8002178d14c4c9dd4b8ffbf" + "reference": "ce332676de1912c4389222987193c3ef38033df6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9d081ead9d3432e2e8002178d14c4c9dd4b8ffbf", - "reference": "9d081ead9d3432e2e8002178d14c4c9dd4b8ffbf", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ce332676de1912c4389222987193c3ef38033df6", + "reference": "ce332676de1912c4389222987193c3ef38033df6", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.1" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" }, "conflict": { - "symfony/cache": "<6.2" + "symfony/cache": "<6.3" }, "require-dev": { - "predis/predis": "~1.0", - "symfony/cache": "^5.4|^6.0", + "doctrine/dbal": "^2.13.1|^3|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.3", "symfony/dependency-injection": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", "symfony/mime": "^5.4|^6.0", "symfony/rate-limiter": "^5.2|^6.0" }, - "suggest": { - "symfony/mime": "To use the file extension guesser" - }, "type": "library", "autoload": { "psr-4": { @@ -9588,7 +9806,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.2.5" + "source": "https://github.com/symfony/http-foundation/tree/v6.3.8" }, "funding": [ { @@ -9604,29 +9822,29 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-11-07T10:17:15+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.2.5", + "version": "v6.3.8", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "f68aaa11eee6b21c99bce0f3d98815924888fe62" + "reference": "929202375ccf44a309c34aeca8305408442ebcc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f68aaa11eee6b21c99bce0f3d98815924888fe62", - "reference": "f68aaa11eee6b21c99bce0f3d98815924888fe62", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/929202375ccf44a309c34aeca8305408442ebcc1", + "reference": "929202375ccf44a309c34aeca8305408442ebcc1", "shasum": "" }, "require": { "php": ">=8.1", "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/error-handler": "^6.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.3", "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-foundation": "^6.3.4", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -9634,15 +9852,18 @@ "symfony/cache": "<5.4", "symfony/config": "<6.1", "symfony/console": "<5.4", - "symfony/dependency-injection": "<6.2", + "symfony/dependency-injection": "<6.3.4", "symfony/doctrine-bridge": "<5.4", "symfony/form": "<5.4", "symfony/http-client": "<5.4", + "symfony/http-client-contracts": "<2.5", "symfony/mailer": "<5.4", "symfony/messenger": "<5.4", "symfony/translation": "<5.4", + "symfony/translation-contracts": "<2.5", "symfony/twig-bridge": "<5.4", "symfony/validator": "<5.4", + "symfony/var-dumper": "<6.3", "twig/twig": "<2.13" }, "provide": { @@ -9651,28 +9872,27 @@ "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", "symfony/browser-kit": "^5.4|^6.0", + "symfony/clock": "^6.2", "symfony/config": "^6.1", "symfony/console": "^5.4|^6.0", "symfony/css-selector": "^5.4|^6.0", - "symfony/dependency-injection": "^6.2", + "symfony/dependency-injection": "^6.3.4", "symfony/dom-crawler": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", - "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/http-client-contracts": "^2.5|^3", "symfony/process": "^5.4|^6.0", + "symfony/property-access": "^5.4.5|^6.0.5", "symfony/routing": "^5.4|^6.0", + "symfony/serializer": "^6.3", "symfony/stopwatch": "^5.4|^6.0", "symfony/translation": "^5.4|^6.0", - "symfony/translation-contracts": "^1.1|^2|^3", + "symfony/translation-contracts": "^2.5|^3", "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^6.3", + "symfony/var-exporter": "^6.2", "twig/twig": "^2.13|^3.0.4" }, - "suggest": { - "symfony/browser-kit": "", - "symfony/config": "", - "symfony/console": "", - "symfony/dependency-injection": "" - }, "type": "library", "autoload": { "psr-4": { @@ -9699,7 +9919,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.2.5" + "source": "https://github.com/symfony/http-kernel/tree/v6.3.8" }, "funding": [ { @@ -9715,20 +9935,20 @@ "type": "tidelift" } ], - "time": "2023-01-24T15:33:24+00:00" + "time": "2023-11-10T13:47:32+00:00" }, { "name": "symfony/intl", - "version": "v6.2.5", + "version": "v6.3.7", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "3e5671e7676723db90a1b3c0b8b27e00407d69d5" + "reference": "4cc98c05f2c55150a6aa5b3e20667f7a6d06cca9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/3e5671e7676723db90a1b3c0b8b27e00407d69d5", - "reference": "3e5671e7676723db90a1b3c0b8b27e00407d69d5", + "url": "https://api.github.com/repos/symfony/intl/zipball/4cc98c05f2c55150a6aa5b3e20667f7a6d06cca9", + "reference": "4cc98c05f2c55150a6aa5b3e20667f7a6d06cca9", "shasum": "" }, "require": { @@ -9736,7 +9956,8 @@ }, "require-dev": { "symfony/filesystem": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0" + "symfony/finder": "^5.4|^6.0", + "symfony/var-exporter": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -9769,7 +9990,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a PHP replacement layer for the C intl extension that includes additional data from the ICU library", + "description": "Provides access to the localization data of the ICU library", "homepage": "https://symfony.com", "keywords": [ "i18n", @@ -9780,7 +10001,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v6.2.5" + "source": "https://github.com/symfony/intl/tree/v6.3.7" }, "funding": [ { @@ -9796,20 +10017,20 @@ "type": "tidelift" } ], - "time": "2023-01-05T09:45:19+00:00" + "time": "2023-10-28T23:11:45+00:00" }, { "name": "symfony/mailer", - "version": "v6.2.5", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "29729ac0b4e5113f24c39c46746bd6afb79e0aaa" + "reference": "d89611a7830d51b5e118bca38e390dea92f9ea06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/29729ac0b4e5113f24c39c46746bd6afb79e0aaa", - "reference": "29729ac0b4e5113f24c39c46746bd6afb79e0aaa", + "url": "https://api.github.com/repos/symfony/mailer/zipball/d89611a7830d51b5e118bca38e390dea92f9ea06", + "reference": "d89611a7830d51b5e118bca38e390dea92f9ea06", "shasum": "" }, "require": { @@ -9819,9 +10040,10 @@ "psr/log": "^1|^2|^3", "symfony/event-dispatcher": "^5.4|^6.0", "symfony/mime": "^6.2", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "conflict": { + "symfony/http-client-contracts": "<2.5", "symfony/http-kernel": "<5.4", "symfony/messenger": "<6.2", "symfony/mime": "<6.2", @@ -9829,7 +10051,7 @@ }, "require-dev": { "symfony/console": "^5.4|^6.0", - "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/http-client": "^5.4|^6.0", "symfony/messenger": "^6.2", "symfony/twig-bridge": "^6.2" }, @@ -9859,7 +10081,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.2.5" + "source": "https://github.com/symfony/mailer/tree/v6.3.5" }, "funding": [ { @@ -9875,28 +10097,32 @@ "type": "tidelift" } ], - "time": "2023-01-10T18:53:53+00:00" + "time": "2023-09-06T09:47:15+00:00" }, { "name": "symfony/mailgun-mailer", - "version": "v6.2.5", + "version": "v6.3.6", "source": { "type": "git", "url": "https://github.com/symfony/mailgun-mailer.git", - "reference": "d1eb7283e30752f2802ced37bffdee2c67cad42a" + "reference": "8d9741467c53750dc8ccda23a1cdb91cda732571" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/d1eb7283e30752f2802ced37bffdee2c67cad42a", - "reference": "d1eb7283e30752f2802ced37bffdee2c67cad42a", + "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/8d9741467c53750dc8ccda23a1cdb91cda732571", + "reference": "8d9741467c53750dc8ccda23a1cdb91cda732571", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/mailer": "^5.4|^6.0" + "symfony/mailer": "^5.4.21|^6.2.7" + }, + "conflict": { + "symfony/http-foundation": "<6.2" }, "require-dev": { - "symfony/http-client": "^5.4|^6.0" + "symfony/http-client": "^5.4|^6.0", + "symfony/webhook": "^6.3" }, "type": "symfony-mailer-bridge", "autoload": { @@ -9924,7 +10150,7 @@ "description": "Symfony Mailgun Mailer Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailgun-mailer/tree/v6.2.5" + "source": "https://github.com/symfony/mailgun-mailer/tree/v6.3.6" }, "funding": [ { @@ -9940,24 +10166,25 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-10-12T13:32:47+00:00" }, { "name": "symfony/mime", - "version": "v6.2.5", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "4b7b349f67d15cd0639955c8179a76c89f6fd610" + "reference": "d5179eedf1cb2946dbd760475ebf05c251ef6a6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/4b7b349f67d15cd0639955c8179a76c89f6fd610", - "reference": "4b7b349f67d15cd0639955c8179a76c89f6fd610", + "url": "https://api.github.com/repos/symfony/mime/zipball/d5179eedf1cb2946dbd760475ebf05c251ef6a6e", + "reference": "d5179eedf1cb2946dbd760475ebf05c251ef6a6e", "shasum": "" }, "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, @@ -9966,7 +10193,7 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/mailer": "<5.4", - "symfony/serializer": "<6.2" + "symfony/serializer": "<6.2.13|>=6.3,<6.3.2" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", @@ -9975,7 +10202,7 @@ "symfony/dependency-injection": "^5.4|^6.0", "symfony/property-access": "^5.4|^6.0", "symfony/property-info": "^5.4|^6.0", - "symfony/serializer": "^6.2" + "symfony/serializer": "~6.2.13|^6.3.2" }, "type": "library", "autoload": { @@ -10007,7 +10234,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.2.5" + "source": "https://github.com/symfony/mime/tree/v6.3.5" }, "funding": [ { @@ -10023,20 +10250,20 @@ "type": "tidelift" } ], - "time": "2023-01-10T18:53:53+00:00" + "time": "2023-09-29T06:59:36+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.4.19", + "version": "v5.4.21", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "b03c99236445492f20c61666e8f7e5d388b078e5" + "reference": "4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b03c99236445492f20c61666e8f7e5d388b078e5", - "reference": "b03c99236445492f20c61666e8f7e5d388b078e5", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9", + "reference": "4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9", "shasum": "" }, "require": { @@ -10076,7 +10303,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.4.19" + "source": "https://github.com/symfony/options-resolver/tree/v5.4.21" }, "funding": [ { @@ -10092,20 +10319,20 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:32:19+00:00" + "time": "2023-02-14T08:03:56+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", "shasum": "" }, "require": { @@ -10120,7 +10347,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10158,7 +10385,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" }, "funding": [ { @@ -10174,20 +10401,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + "reference": "875e90aeea2777b6f135677f618529449334a612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", + "reference": "875e90aeea2777b6f135677f618529449334a612", "shasum": "" }, "require": { @@ -10199,7 +10426,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10239,7 +10466,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" }, "funding": [ { @@ -10255,20 +10482,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "a3d9148e2c363588e05abbdd4ee4f971f0a5330c" + "reference": "e46b4da57951a16053cd751f63f4a24292788157" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/a3d9148e2c363588e05abbdd4ee4f971f0a5330c", - "reference": "a3d9148e2c363588e05abbdd4ee4f971f0a5330c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/e46b4da57951a16053cd751f63f4a24292788157", + "reference": "e46b4da57951a16053cd751f63f4a24292788157", "shasum": "" }, "require": { @@ -10280,7 +10507,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10326,7 +10553,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.28.0" }, "funding": [ { @@ -10342,20 +10569,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-03-21T17:27:24+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da" + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d", + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d", "shasum": "" }, "require": { @@ -10369,7 +10596,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10413,7 +10640,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0" }, "funding": [ { @@ -10429,20 +10656,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:30:37+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", "shasum": "" }, "require": { @@ -10454,7 +10681,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10497,7 +10724,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" }, "funding": [ { @@ -10513,20 +10740,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "42292d99c55abe617799667f454222c54c60e229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", "shasum": "" }, "require": { @@ -10541,7 +10768,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10580,7 +10807,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" }, "funding": [ { @@ -10596,20 +10823,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-07-28T09:04:16+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179", + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179", "shasum": "" }, "require": { @@ -10618,7 +10845,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10656,7 +10883,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0" }, "funding": [ { @@ -10672,20 +10899,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" + "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fe2f306d1d9d346a7fee353d0d5012e401e984b5", + "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5", "shasum": "" }, "require": { @@ -10694,7 +10921,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10735,7 +10962,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.28.0" }, "funding": [ { @@ -10751,20 +10978,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "shasum": "" }, "require": { @@ -10773,7 +11000,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10818,7 +11045,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" }, "funding": [ { @@ -10834,20 +11061,100 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { - "name": "symfony/polyfill-uuid", - "version": "v1.27.0", + "name": "symfony/polyfill-php83", + "version": "v1.28.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/f3cf1a645c2734236ed1e2e671e273eeb3586166", - "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", + "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-php80": "^1.14" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-08-16T06:22:46+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "9c44518a5aff8da565c8a55dbe85d2769e6f630e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/9c44518a5aff8da565c8a55dbe85d2769e6f630e", + "reference": "9c44518a5aff8da565c8a55dbe85d2769e6f630e", "shasum": "" }, "require": { @@ -10862,7 +11169,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -10900,7 +11207,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.28.0" }, "funding": [ { @@ -10916,28 +11223,33 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/postmark-mailer", - "version": "v6.2.5", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/postmark-mailer.git", - "reference": "b6630e287f94fbc1f0f0a20a3a147a69ac8f862b" + "reference": "3294c1b98fbc53fdd149f947c209b4503de77797" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/postmark-mailer/zipball/b6630e287f94fbc1f0f0a20a3a147a69ac8f862b", - "reference": "b6630e287f94fbc1f0f0a20a3a147a69ac8f862b", + "url": "https://api.github.com/repos/symfony/postmark-mailer/zipball/3294c1b98fbc53fdd149f947c209b4503de77797", + "reference": "3294c1b98fbc53fdd149f947c209b4503de77797", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/mailer": "^5.4|^6.0" + "psr/event-dispatcher": "^1", + "symfony/mailer": "^5.4.21|^6.2.7" + }, + "conflict": { + "symfony/http-foundation": "<6.2" }, "require-dev": { - "symfony/http-client": "^5.4|^6.0" + "symfony/http-client": "^5.4|^6.0", + "symfony/webhook": "^6.3" }, "type": "symfony-mailer-bridge", "autoload": { @@ -10965,7 +11277,7 @@ "description": "Symfony Postmark Mailer Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/postmark-mailer/tree/v6.2.5" + "source": "https://github.com/symfony/postmark-mailer/tree/v6.3.0" }, "funding": [ { @@ -10981,20 +11293,20 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:36:55+00:00" + "time": "2023-04-21T14:42:22+00:00" }, { "name": "symfony/process", - "version": "v6.2.5", + "version": "v6.3.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "9ead139f63dfa38c4e4a9049cc64a8b2748c83b7" + "reference": "0b5c29118f2e980d455d2e34a5659f4579847c54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/9ead139f63dfa38c4e4a9049cc64a8b2748c83b7", - "reference": "9ead139f63dfa38c4e4a9049cc64a8b2748c83b7", + "url": "https://api.github.com/repos/symfony/process/zipball/0b5c29118f2e980d455d2e34a5659f4579847c54", + "reference": "0b5c29118f2e980d455d2e34a5659f4579847c54", "shasum": "" }, "require": { @@ -11026,7 +11338,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.2.5" + "source": "https://github.com/symfony/process/tree/v6.3.4" }, "funding": [ { @@ -11042,20 +11354,20 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-08-07T10:39:22+00:00" }, { "name": "symfony/property-access", - "version": "v5.4.19", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "20fcf370aed6b2b4a2d8170fa23d2d07250e94ab" + "reference": "0249e46f69e92049a488f39fcf531cb42c50caaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/20fcf370aed6b2b4a2d8170fa23d2d07250e94ab", - "reference": "20fcf370aed6b2b4a2d8170fa23d2d07250e94ab", + "url": "https://api.github.com/repos/symfony/property-access/zipball/0249e46f69e92049a488f39fcf531cb42c50caaa", + "reference": "0249e46f69e92049a488f39fcf531cb42c50caaa", "shasum": "" }, "require": { @@ -11103,11 +11415,11 @@ "injection", "object", "property", - "property path", + "property-path", "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v5.4.19" + "source": "https://github.com/symfony/property-access/tree/v5.4.26" }, "funding": [ { @@ -11123,20 +11435,20 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:32:19+00:00" + "time": "2023-07-13T15:20:41+00:00" }, { "name": "symfony/property-info", - "version": "v6.2.5", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "267c798e87dc56dd0832c29cf9012ac983ed7194" + "reference": "7f3a03716112269741fe2a809f8f791a371d1fcd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/267c798e87dc56dd0832c29cf9012ac983ed7194", - "reference": "267c798e87dc56dd0832c29cf9012ac983ed7194", + "url": "https://api.github.com/repos/symfony/property-info/zipball/7f3a03716112269741fe2a809f8f791a371d1fcd", + "reference": "7f3a03716112269741fe2a809f8f791a371d1fcd", "shasum": "" }, "require": { @@ -11156,12 +11468,6 @@ "symfony/dependency-injection": "^5.4|^6.0", "symfony/serializer": "^5.4|^6.0" }, - "suggest": { - "phpdocumentor/reflection-docblock": "To use the PHPDoc", - "psr/cache-implementation": "To cache results", - "symfony/doctrine-bridge": "To use Doctrine metadata", - "symfony/serializer": "To use Serializer metadata" - }, "type": "library", "autoload": { "psr-4": { @@ -11196,7 +11502,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v6.2.5" + "source": "https://github.com/symfony/property-info/tree/v6.3.0" }, "funding": [ { @@ -11212,36 +11518,37 @@ "type": "tidelift" } ], - "time": "2023-01-20T17:45:48+00:00" + "time": "2023-05-19T08:06:44+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v2.1.4", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "a125b93ef378c492e274f217874906fb9babdebb" + "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/a125b93ef378c492e274f217874906fb9babdebb", - "reference": "a125b93ef378c492e274f217874906fb9babdebb", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/581ca6067eb62640de5ff08ee1ba6850a0ee472e", + "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e", "shasum": "" }, "require": { - "php": ">=7.1", - "psr/http-message": "^1.0", - "symfony/http-foundation": "^4.4 || ^5.0 || ^6.0" + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/http-foundation": "^5.4 || ^6.0" }, "require-dev": { "nyholm/psr7": "^1.1", "psr/log": "^1.1 || ^2 || ^3", - "symfony/browser-kit": "^4.4 || ^5.0 || ^6.0", - "symfony/config": "^4.4 || ^5.0 || ^6.0", - "symfony/event-dispatcher": "^4.4 || ^5.0 || ^6.0", - "symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0", - "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0", - "symfony/phpunit-bridge": "^5.4@dev || ^6.0" + "symfony/browser-kit": "^5.4 || ^6.0", + "symfony/config": "^5.4 || ^6.0", + "symfony/event-dispatcher": "^5.4 || ^6.0", + "symfony/framework-bundle": "^5.4 || ^6.0", + "symfony/http-kernel": "^5.4 || ^6.0", + "symfony/phpunit-bridge": "^6.2" }, "suggest": { "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" @@ -11249,7 +11556,7 @@ "type": "symfony-bridge", "extra": { "branch-alias": { - "dev-main": "2.1-dev" + "dev-main": "2.3-dev" } }, "autoload": { @@ -11284,7 +11591,7 @@ ], "support": { "issues": "https://github.com/symfony/psr-http-message-bridge/issues", - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.1.4" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.3.1" }, "funding": [ { @@ -11300,24 +11607,25 @@ "type": "tidelift" } ], - "time": "2022-11-28T22:46:34+00:00" + "time": "2023-07-26T11:53:26+00:00" }, { "name": "symfony/routing", - "version": "v6.2.5", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "589bd742d5d03c192c8521911680fe88f61712fe" + "reference": "82616e59acd3e3d9c916bba798326cb7796d7d31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/589bd742d5d03c192c8521911680fe88f61712fe", - "reference": "589bd742d5d03c192c8521911680fe88f61712fe", + "url": "https://api.github.com/repos/symfony/routing/zipball/82616e59acd3e3d9c916bba798326cb7796d7d31", + "reference": "82616e59acd3e3d9c916bba798326cb7796d7d31", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "doctrine/annotations": "<1.12", @@ -11334,12 +11642,6 @@ "symfony/http-foundation": "^5.4|^6.0", "symfony/yaml": "^5.4|^6.0" }, - "suggest": { - "symfony/config": "For using the all-in-one router or any loader", - "symfony/expression-language": "For using expression matching", - "symfony/http-foundation": "For using a Symfony Request object", - "symfony/yaml": "For using the YAML loader" - }, "type": "library", "autoload": { "psr-4": { @@ -11372,7 +11674,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.2.5" + "source": "https://github.com/symfony/routing/tree/v6.3.5" }, "funding": [ { @@ -11388,20 +11690,20 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-09-20T16:05:51+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.2.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75" + "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/aac98028c69df04ee77eb69b96b86ee51fbf4b75", - "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/b3313c2dbffaf71c8de2934e2ea56ed2291a3838", + "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838", "shasum": "" }, "require": { @@ -11411,13 +11713,10 @@ "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -11457,7 +11756,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.2.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.4.0" }, "funding": [ { @@ -11473,20 +11772,20 @@ "type": "tidelift" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2023-07-30T20:28:31+00:00" }, { "name": "symfony/string", - "version": "v6.2.5", + "version": "v6.3.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0" + "reference": "13880a87790c76ef994c91e87efb96134522577a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0", - "reference": "b2dac0fa27b1ac0f9c0c0b23b43977f12308d0b0", + "url": "https://api.github.com/repos/symfony/string/zipball/13880a87790c76ef994c91e87efb96134522577a", + "reference": "13880a87790c76ef994c91e87efb96134522577a", "shasum": "" }, "require": { @@ -11497,13 +11796,13 @@ "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/translation-contracts": "<2.0" + "symfony/translation-contracts": "<2.5" }, "require-dev": { "symfony/error-handler": "^5.4|^6.0", "symfony/http-client": "^5.4|^6.0", "symfony/intl": "^6.2", - "symfony/translation-contracts": "^2.0|^3.0", + "symfony/translation-contracts": "^2.5|^3.0", "symfony/var-exporter": "^5.4|^6.0" }, "type": "library", @@ -11543,7 +11842,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.2.5" + "source": "https://github.com/symfony/string/tree/v6.3.8" }, "funding": [ { @@ -11559,32 +11858,35 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-11-09T08:28:21+00:00" }, { "name": "symfony/translation", - "version": "v6.2.5", + "version": "v6.3.7", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "60556925a703cfbc1581cde3b3f35b0bb0ea904c" + "reference": "30212e7c87dcb79c83f6362b00bde0e0b1213499" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/60556925a703cfbc1581cde3b3f35b0bb0ea904c", - "reference": "60556925a703cfbc1581cde3b3f35b0bb0ea904c", + "url": "https://api.github.com/repos/symfony/translation/zipball/30212e7c87dcb79c83f6362b00bde0e0b1213499", + "reference": "30212e7c87dcb79c83f6362b00bde0e0b1213499", "shasum": "" }, "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.3|^3.0" + "symfony/translation-contracts": "^2.5|^3.0" }, "conflict": { "symfony/config": "<5.4", "symfony/console": "<5.4", "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", "symfony/twig-bundle": "<5.4", "symfony/yaml": "<5.4" }, @@ -11598,20 +11900,14 @@ "symfony/console": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/finder": "^5.4|^6.0", - "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-client-contracts": "^2.5|^3.0", "symfony/http-kernel": "^5.4|^6.0", "symfony/intl": "^5.4|^6.0", "symfony/polyfill-intl-icu": "^1.21", "symfony/routing": "^5.4|^6.0", - "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/service-contracts": "^2.5|^3", "symfony/yaml": "^5.4|^6.0" }, - "suggest": { - "nikic/php-parser": "To use PhpAstExtractor", - "psr/log-implementation": "To use logging capability in translator", - "symfony/config": "", - "symfony/yaml": "" - }, "type": "library", "autoload": { "files": [ @@ -11641,7 +11937,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.2.5" + "source": "https://github.com/symfony/translation/tree/v6.3.7" }, "funding": [ { @@ -11657,32 +11953,29 @@ "type": "tidelift" } ], - "time": "2023-01-05T07:00:27+00:00" + "time": "2023-10-28T23:11:45+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.2.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "68cce71402305a015f8c1589bfada1280dc64fe7" + "reference": "dee0c6e5b4c07ce851b462530088e64b255ac9c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/68cce71402305a015f8c1589bfada1280dc64fe7", - "reference": "68cce71402305a015f8c1589bfada1280dc64fe7", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/dee0c6e5b4c07ce851b462530088e64b255ac9c5", + "reference": "dee0c6e5b4c07ce851b462530088e64b255ac9c5", "shasum": "" }, "require": { "php": ">=8.1" }, - "suggest": { - "symfony/translation-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -11722,7 +12015,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.2.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.4.0" }, "funding": [ { @@ -11738,20 +12031,20 @@ "type": "tidelift" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2023-07-25T15:08:44+00:00" }, { "name": "symfony/uid", - "version": "v6.2.5", + "version": "v6.3.8", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "8ace895bded57d6496638c9b2d3b788e05b7395b" + "reference": "819fa5ac210fb7ddda4752b91a82f50be7493dd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/8ace895bded57d6496638c9b2d3b788e05b7395b", - "reference": "8ace895bded57d6496638c9b2d3b788e05b7395b", + "url": "https://api.github.com/repos/symfony/uid/zipball/819fa5ac210fb7ddda4752b91a82f50be7493dd9", + "reference": "819fa5ac210fb7ddda4752b91a82f50be7493dd9", "shasum": "" }, "require": { @@ -11796,7 +12089,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.2.5" + "source": "https://github.com/symfony/uid/tree/v6.3.8" }, "funding": [ { @@ -11812,33 +12105,33 @@ "type": "tidelift" } ], - "time": "2023-01-01T08:38:09+00:00" + "time": "2023-10-31T08:07:48+00:00" }, { "name": "symfony/validator", - "version": "v6.2.5", + "version": "v6.3.8", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "0ebfbe384790e61147e3d7f4aa0afbd6190198c4" + "reference": "f75b40e088d095db1e788b81605a76f4563cb80e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/0ebfbe384790e61147e3d7f4aa0afbd6190198c4", - "reference": "0ebfbe384790e61147e3d7f4aa0afbd6190198c4", + "url": "https://api.github.com/repos/symfony/validator/zipball/f75b40e088d095db1e788b81605a76f4563cb80e", + "reference": "f75b40e088d095db1e788b81605a76f4563cb80e", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^1.1|^2|^3" + "symfony/polyfill-php83": "^1.27", + "symfony/translation-contracts": "^2.5|^3" }, "conflict": { "doctrine/annotations": "<1.13", "doctrine/lexer": "<1.1", - "phpunit/phpunit": "<5.4.3", "symfony/dependency-injection": "<5.4", "symfony/expression-language": "<5.4", "symfony/http-kernel": "<5.4", @@ -11866,18 +12159,6 @@ "symfony/translation": "^5.4|^6.0", "symfony/yaml": "^5.4|^6.0" }, - "suggest": { - "egulias/email-validator": "Strict (RFC compliant) email validation", - "psr/cache-implementation": "For using the mapping cache.", - "symfony/config": "", - "symfony/expression-language": "For using the Expression validator and the ExpressionLanguageSyntax constraints", - "symfony/http-foundation": "", - "symfony/intl": "", - "symfony/property-access": "For accessing properties within comparison constraints", - "symfony/property-info": "To automatically add NotNull and Type constraints", - "symfony/translation": "For translating validation errors.", - "symfony/yaml": "" - }, "type": "library", "autoload": { "psr-4": { @@ -11904,7 +12185,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v6.2.5" + "source": "https://github.com/symfony/validator/tree/v6.3.8" }, "funding": [ { @@ -11920,42 +12201,38 @@ "type": "tidelift" } ], - "time": "2023-01-20T17:45:48+00:00" + "time": "2023-11-07T10:17:15+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.2.5", + "version": "v6.3.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "44b7b81749fd20c1bdf4946c041050e22bc8da27" + "reference": "81acabba9046550e89634876ca64bfcd3c06aa0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/44b7b81749fd20c1bdf4946c041050e22bc8da27", - "reference": "44b7b81749fd20c1bdf4946c041050e22bc8da27", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/81acabba9046550e89634876ca64bfcd3c06aa0a", + "reference": "81acabba9046550e89634876ca64bfcd3c06aa0a", "shasum": "" }, "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "phpunit/phpunit": "<5.4.3", "symfony/console": "<5.4" }, "require-dev": { "ext-iconv": "*", "symfony/console": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", "symfony/process": "^5.4|^6.0", "symfony/uid": "^5.4|^6.0", "twig/twig": "^2.13|^3.0.4" }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, "bin": [ "Resources/bin/var-dump-server" ], @@ -11992,7 +12269,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.2.5" + "source": "https://github.com/symfony/var-dumper/tree/v6.3.8" }, "funding": [ { @@ -12008,7 +12285,7 @@ "type": "tidelift" } ], - "time": "2023-01-20T17:45:48+00:00" + "time": "2023-11-08T10:42:36+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -12065,21 +12342,21 @@ }, { "name": "turbo124/beacon", - "version": "v1.3.6", + "version": "v1.5.2", "source": { "type": "git", "url": "https://github.com/turbo124/beacon.git", - "reference": "72b2b45988266da31d577d366d9769c6f90d4e2d" + "reference": "4f08b91d3f9326e42f664e667d84100dc8afe752" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/turbo124/beacon/zipball/72b2b45988266da31d577d366d9769c6f90d4e2d", - "reference": "72b2b45988266da31d577d366d9769c6f90d4e2d", + "url": "https://api.github.com/repos/turbo124/beacon/zipball/4f08b91d3f9326e42f664e667d84100dc8afe752", + "reference": "4f08b91d3f9326e42f664e667d84100dc8afe752", "shasum": "" }, "require": { "guzzlehttp/guzzle": "^7", - "illuminate/support": "^9.0", + "illuminate/support": "^9.0|^10.0", "php": "^8" }, "require-dev": { @@ -12121,9 +12398,9 @@ "turbo124" ], "support": { - "source": "https://github.com/turbo124/beacon/tree/v1.3.6" + "source": "https://github.com/turbo124/beacon/tree/v1.5.2" }, - "time": "2023-01-29T00:13:12+00:00" + "time": "2023-10-01T07:13:02+00:00" }, { "name": "turbo124/predis", @@ -12193,16 +12470,16 @@ }, { "name": "twilio/sdk", - "version": "6.44.2", + "version": "6.44.4", "source": { "type": "git", "url": "git@github.com:twilio/twilio-php.git", - "reference": "deec3203857387213825e2634c4eb4b56880877a" + "reference": "08aad5f377e2245b9cd7508e7762d95e7392fa4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twilio/twilio-php/zipball/deec3203857387213825e2634c4eb4b56880877a", - "reference": "deec3203857387213825e2634c4eb4b56880877a", + "url": "https://api.github.com/repos/twilio/twilio-php/zipball/08aad5f377e2245b9cd7508e7762d95e7392fa4d", + "reference": "08aad5f377e2245b9cd7508e7762d95e7392fa4d", "shasum": "" }, "require": { @@ -12210,7 +12487,7 @@ }, "require-dev": { "guzzlehttp/guzzle": "^6.3 || ^7.0", - "phpunit/phpunit": ">=7.0" + "phpunit/phpunit": ">=7.0 < 10" }, "suggest": { "guzzlehttp/guzzle": "An HTTP client to execute the API requests" @@ -12238,35 +12515,35 @@ "sms", "twilio" ], - "time": "2023-01-25T19:34:30+00:00" + "time": "2023-02-22T19:59:53+00:00" }, { "name": "vlucas/phpdotenv", - "version": "v5.5.0", + "version": "v5.6.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7" + "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", - "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.0.2", - "php": "^7.1.3 || ^8.0", - "phpoption/phpoption": "^1.8", - "symfony/polyfill-ctype": "^1.23", - "symfony/polyfill-mbstring": "^1.23.1", - "symfony/polyfill-php80": "^1.23.1" + "graham-campbell/result-type": "^1.1.2", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.2", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-filter": "*", - "phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25" + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" }, "suggest": { "ext-filter": "Required to use the boolean validator." @@ -12278,7 +12555,7 @@ "forward-command": true }, "branch-alias": { - "dev-master": "5.5-dev" + "dev-master": "5.6-dev" } }, "autoload": { @@ -12310,7 +12587,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.5.0" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0" }, "funding": [ { @@ -12322,7 +12599,7 @@ "type": "tidelift" } ], - "time": "2022-10-16T01:01:54+00:00" + "time": "2023-11-12T22:43:29+00:00" }, { "name": "voku/portable-ascii", @@ -12521,7 +12798,7 @@ "issues": "https://github.com/webpatser/laravel-countries/issues", "source": "https://github.com/webpatser/laravel-countries" }, - "time": "2022-03-29T15:40:48+00:00" + "time": "2023-02-08T11:09:34+00:00" }, { "name": "wepay/php-sdk", @@ -12744,36 +13021,36 @@ }, { "name": "barryvdh/laravel-debugbar", - "version": "v3.7.0", + "version": "v3.9.2", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "3372ed65e6d2039d663ed19aa699956f9d346271" + "reference": "bfd0131c146973cab164e50f5cdd8a67cc60cab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/3372ed65e6d2039d663ed19aa699956f9d346271", - "reference": "3372ed65e6d2039d663ed19aa699956f9d346271", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/bfd0131c146973cab164e50f5cdd8a67cc60cab1", + "reference": "bfd0131c146973cab164e50f5cdd8a67cc60cab1", "shasum": "" }, "require": { - "illuminate/routing": "^7|^8|^9", - "illuminate/session": "^7|^8|^9", - "illuminate/support": "^7|^8|^9", - "maximebf/debugbar": "^1.17.2", - "php": ">=7.2.5", - "symfony/finder": "^5|^6" + "illuminate/routing": "^9|^10", + "illuminate/session": "^9|^10", + "illuminate/support": "^9|^10", + "maximebf/debugbar": "^1.18.2", + "php": "^8.0", + "symfony/finder": "^6" }, "require-dev": { "mockery/mockery": "^1.3.3", - "orchestra/testbench-dusk": "^5|^6|^7", - "phpunit/phpunit": "^8.5|^9.0", + "orchestra/testbench-dusk": "^5|^6|^7|^8", + "phpunit/phpunit": "^8.5.30|^9.0", "squizlabs/php_codesniffer": "^3.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.6-dev" + "dev-master": "3.8-dev" }, "laravel": { "providers": [ @@ -12812,7 +13089,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.7.0" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.9.2" }, "funding": [ { @@ -12824,29 +13101,29 @@ "type": "github" } ], - "time": "2022-07-11T09:26:42+00:00" + "time": "2023-08-25T18:43:57+00:00" }, { "name": "beyondcode/laravel-query-detector", - "version": "1.6.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/beyondcode/laravel-query-detector.git", - "reference": "8261d80c71c97e994c1021fe5c3bd2a1c27106fc" + "reference": "722c45c07b96d88abd499c3ed7fd949798bede5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beyondcode/laravel-query-detector/zipball/8261d80c71c97e994c1021fe5c3bd2a1c27106fc", - "reference": "8261d80c71c97e994c1021fe5c3bd2a1c27106fc", + "url": "https://api.github.com/repos/beyondcode/laravel-query-detector/zipball/722c45c07b96d88abd499c3ed7fd949798bede5a", + "reference": "722c45c07b96d88abd499c3ed7fd949798bede5a", "shasum": "" }, "require": { - "illuminate/support": "^5.5 || ^6.0 || ^7.0 || ^8.0 || ^9.0", + "illuminate/support": "^5.5 || ^6.0 || ^7.0 || ^8.0 || ^9.0|^10.0", "php": "^7.1 || ^8.0" }, "require-dev": { "laravel/legacy-factories": "^1.0", - "orchestra/testbench": "^3.0 || ^4.0 || ^5.0 || ^6.0", + "orchestra/testbench": "^3.0 || ^4.0 || ^5.0 || ^6.0|^8.0", "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "type": "library", @@ -12882,22 +13159,22 @@ ], "support": { "issues": "https://github.com/beyondcode/laravel-query-detector/issues", - "source": "https://github.com/beyondcode/laravel-query-detector/tree/1.6.0" + "source": "https://github.com/beyondcode/laravel-query-detector/tree/1.8.0" }, - "time": "2022-02-12T16:23:40+00:00" + "time": "2023-11-15T08:04:32+00:00" }, { "name": "brianium/paratest", - "version": "v6.8.1", + "version": "v6.11.0", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "168c1cfdf79e5b19b57cb03060fc9a6a79c5f582" + "reference": "8083a421cee7dad847ee7c464529043ba30de380" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/168c1cfdf79e5b19b57cb03060fc9a6a79c5f582", - "reference": "168c1cfdf79e5b19b57cb03060fc9a6a79c5f582", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/8083a421cee7dad847ee7c464529043ba30de380", + "reference": "8083a421cee7dad847ee7c464529043ba30de380", "shasum": "" }, "require": { @@ -12905,25 +13182,25 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-simplexml": "*", - "fidry/cpu-core-counter": "^0.4.1", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", "jean85/pretty-package-versions": "^2.0.5", "php": "^7.3 || ^8.0", - "phpunit/php-code-coverage": "^9.2.23", + "phpunit/php-code-coverage": "^9.2.25", "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-timer": "^5.0.3", - "phpunit/phpunit": "^9.5.28", - "sebastian/environment": "^5.1.4", - "symfony/console": "^5.4.16 || ^6.2.3", - "symfony/process": "^5.4.11 || ^6.2" + "phpunit/phpunit": "^9.6.4", + "sebastian/environment": "^5.1.5", + "symfony/console": "^5.4.28 || ^6.3.4 || ^7.0.0", + "symfony/process": "^5.4.28 || ^6.3.4 || ^7.0.0" }, "require-dev": { - "doctrine/coding-standard": "^10.0.0", + "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "infection/infection": "^0.26.16", - "squizlabs/php_codesniffer": "^3.7.1", - "symfony/filesystem": "^5.4.13 || ^6.2", - "vimeo/psalm": "^5.4" + "infection/infection": "^0.27.6", + "squizlabs/php_codesniffer": "^3.7.2", + "symfony/filesystem": "^5.4.25 || ^6.3.1 || ^7.0.0", + "vimeo/psalm": "^5.7.7" }, "bin": [ "bin/paratest", @@ -12964,7 +13241,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v6.8.1" + "source": "https://github.com/paratestphp/paratest/tree/v6.11.0" }, "funding": [ { @@ -12976,7 +13253,7 @@ "type": "paypal" } ], - "time": "2023-01-17T10:08:49+00:00" + "time": "2023-10-31T09:13:57+00:00" }, { "name": "composer/package-versions-deprecated", @@ -13053,16 +13330,16 @@ }, { "name": "composer/pcre", - "version": "3.1.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2" + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", - "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", + "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", "shasum": "" }, "require": { @@ -13104,7 +13381,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.0" + "source": "https://github.com/composer/pcre/tree/3.1.1" }, "funding": [ { @@ -13120,20 +13397,20 @@ "type": "tidelift" } ], - "time": "2022-11-17T09:50:14+00:00" + "time": "2023-10-11T07:11:09+00:00" }, { "name": "composer/semver", - "version": "3.3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", "shasum": "" }, "require": { @@ -13183,9 +13460,9 @@ "versioning" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.2" + "source": "https://github.com/composer/semver/tree/3.4.0" }, "funding": [ { @@ -13201,7 +13478,7 @@ "type": "tidelift" } ], - "time": "2022-04-01T19:23:25+00:00" + "time": "2023-08-31T09:50:34+00:00" }, { "name": "composer/xdebug-handler", @@ -13387,16 +13664,16 @@ }, { "name": "doctrine/annotations", - "version": "1.14.2", + "version": "1.14.3", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "ad785217c1e9555a7d6c6c8c9f406395a5e2882b" + "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/ad785217c1e9555a7d6c6c8c9f406395a5e2882b", - "reference": "ad785217c1e9555a7d6c6c8c9f406395a5e2882b", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", + "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", "shasum": "" }, "require": { @@ -13457,9 +13734,9 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.14.2" + "source": "https://github.com/doctrine/annotations/tree/1.14.3" }, - "time": "2022-12-15T06:48:22+00:00" + "time": "2023-02-01T09:20:38+00:00" }, { "name": "doctrine/instantiator", @@ -13634,16 +13911,16 @@ }, { "name": "fidry/cpu-core-counter", - "version": "0.4.1", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "79261cc280aded96d098e1b0e0ba0c4881b432c2" + "reference": "85193c0b0cb5c47894b5eaec906e946f054e7077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/79261cc280aded96d098e1b0e0ba0c4881b432c2", - "reference": "79261cc280aded96d098e1b0e0ba0c4881b432c2", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/85193c0b0cb5c47894b5eaec906e946f054e7077", + "reference": "85193c0b0cb5c47894b5eaec906e946f054e7077", "shasum": "" }, "require": { @@ -13651,13 +13928,13 @@ }, "require-dev": { "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", "phpstan/extension-installer": "^1.2.0", "phpstan/phpstan": "^1.9.2", "phpstan/phpstan-deprecation-rules": "^1.0.0", "phpstan/phpstan-phpunit": "^1.2.2", "phpstan/phpstan-strict-rules": "^1.4.4", - "phpunit/phpunit": "^9.5.26 || ^8.5.31", - "theofidry/php-cs-fixer-config": "^1.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", "webmozarts/strict-phpunit": "^7.5" }, "type": "library", @@ -13683,7 +13960,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/0.4.1" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.0.0" }, "funding": [ { @@ -13691,20 +13968,20 @@ "type": "github" } ], - "time": "2022-12-16T22:01:02+00:00" + "time": "2023-09-17T21:38:23+00:00" }, { "name": "filp/whoops", - "version": "2.14.6", + "version": "2.15.4", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "f7948baaa0330277c729714910336383286305da" + "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/f7948baaa0330277c729714910336383286305da", - "reference": "f7948baaa0330277c729714910336383286305da", + "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546", + "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546", "shasum": "" }, "require": { @@ -13754,7 +14031,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.6" + "source": "https://github.com/filp/whoops/tree/2.15.4" }, "funding": [ { @@ -13762,7 +14039,7 @@ "type": "github" } ], - "time": "2022-11-02T16:23:29+00:00" + "time": "2023-11-03T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -13817,24 +14094,24 @@ }, { "name": "laracasts/cypress", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/laracasts/cypress.git", - "reference": "9a9e5d25a51d2cbb410393e6a0d9883aa3304bf5" + "reference": "dd4e61188d4edaf65ffa18851a5df38d0fa0619a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laracasts/cypress/zipball/9a9e5d25a51d2cbb410393e6a0d9883aa3304bf5", - "reference": "9a9e5d25a51d2cbb410393e6a0d9883aa3304bf5", + "url": "https://api.github.com/repos/laracasts/cypress/zipball/dd4e61188d4edaf65ffa18851a5df38d0fa0619a", + "reference": "dd4e61188d4edaf65ffa18851a5df38d0fa0619a", "shasum": "" }, "require": { - "illuminate/support": "^6.0|^7.0|^8.0|^9.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", "php": "^8.0" }, "require-dev": { - "orchestra/testbench": "^6.0|^7.0", + "orchestra/testbench": "^6.0|^7.0|^8.0", "phpunit/phpunit": "^8.0|^9.5.10", "spatie/laravel-ray": "^1.29" }, @@ -13870,9 +14147,9 @@ ], "support": { "issues": "https://github.com/laracasts/cypress/issues", - "source": "https://github.com/laracasts/cypress/tree/3.0.0" + "source": "https://github.com/laracasts/cypress/tree/3.0.1" }, - "time": "2022-06-27T13:49:35+00:00" + "time": "2023-02-16T20:00:16+00:00" }, { "name": "laravel/dusk", @@ -13949,25 +14226,25 @@ }, { "name": "maximebf/debugbar", - "version": "v1.18.1", + "version": "v1.19.1", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "ba0af68dd4316834701ecb30a00ce9604ced3ee9" + "reference": "03dd40a1826f4d585ef93ef83afa2a9874a00523" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/ba0af68dd4316834701ecb30a00ce9604ced3ee9", - "reference": "ba0af68dd4316834701ecb30a00ce9604ced3ee9", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/03dd40a1826f4d585ef93ef83afa2a9874a00523", + "reference": "03dd40a1826f4d585ef93ef83afa2a9874a00523", "shasum": "" }, "require": { "php": "^7.1|^8", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^2.6|^3|^4|^5|^6" + "symfony/var-dumper": "^4|^5|^6" }, "require-dev": { - "phpunit/phpunit": "^7.5.20 || ^9.4.2", + "phpunit/phpunit": ">=7.5.20 <10.0", "twig/twig": "^1.38|^2.7|^3.0" }, "suggest": { @@ -14009,44 +14286,46 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.18.1" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.19.1" }, - "time": "2022-03-31T14:55:54+00:00" + "time": "2023-10-12T08:10:52+00:00" }, { "name": "mockery/mockery", - "version": "1.5.1", + "version": "1.6.6", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "e92dcc83d5a51851baf5f5591d32cb2b16e3684e" + "reference": "b8e0bb7d8c604046539c1115994632c74dcb361e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/e92dcc83d5a51851baf5f5591d32cb2b16e3684e", - "reference": "e92dcc83d5a51851baf5f5591d32cb2b16e3684e", + "url": "https://api.github.com/repos/mockery/mockery/zipball/b8e0bb7d8c604046539c1115994632c74dcb361e", + "reference": "b8e0bb7d8c604046539c1115994632c74dcb361e", "shasum": "" }, "require": { "hamcrest/hamcrest-php": "^2.0.1", "lib-pcre": ">=7.0", - "php": "^7.3 || ^8.0" + "php": ">=7.3" }, "conflict": { "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.3" + "phpunit/phpunit": "^8.5 || ^9.6.10", + "psalm/plugin-phpunit": "^0.18.4", + "symplify/easy-coding-standard": "^11.5.0", + "vimeo/psalm": "^4.30" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, "autoload": { - "psr-0": { - "Mockery": "library/" + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" } }, "notification-url": "https://packagist.org/downloads/", @@ -14057,12 +14336,20 @@ { "name": "Pádraic Brady", "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" + "homepage": "https://github.com/padraic", + "role": "Author" }, { "name": "Dave Marshall", "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" } ], "description": "Mockery is a simple yet flexible PHP mock object framework", @@ -14080,23 +14367,26 @@ "testing" ], "support": { + "docs": "https://docs.mockery.io/", "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.5.1" + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" }, - "time": "2022-09-07T15:32:08+00:00" + "time": "2023-08-09T00:03:52+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -14134,7 +14424,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" }, "funding": [ { @@ -14142,20 +14432,20 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "netresearch/jsonmapper", - "version": "v4.1.0", + "version": "v4.2.0", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f" + "reference": "f60565f8c0566a31acf06884cdaa591867ecc956" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/cfa81ea1d35294d64adb9c68aa4cb9e92400e53f", - "reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/f60565f8c0566a31acf06884cdaa591867ecc956", + "reference": "f60565f8c0566a31acf06884cdaa591867ecc956", "shasum": "" }, "require": { @@ -14191,9 +14481,9 @@ "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.1.0" + "source": "https://github.com/cweiske/jsonmapper/tree/v4.2.0" }, - "time": "2022-12-08T20:46:14+00:00" + "time": "2023-04-09T17:37:40+00:00" }, { "name": "nunomaduro/collision", @@ -14449,37 +14739,38 @@ }, { "name": "php-webdriver/webdriver", - "version": "1.13.1", + "version": "1.15.1", "source": { "type": "git", "url": "https://github.com/php-webdriver/php-webdriver.git", - "reference": "6dfe5f814b796c1b5748850aa19f781b9274c36c" + "reference": "cd52d9342c5aa738c2e75a67e47a1b6df97154e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/6dfe5f814b796c1b5748850aa19f781b9274c36c", - "reference": "6dfe5f814b796c1b5748850aa19f781b9274c36c", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/cd52d9342c5aa738c2e75a67e47a1b6df97154e8", + "reference": "cd52d9342c5aa738c2e75a67e47a1b6df97154e8", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", "ext-zip": "*", - "php": "^5.6 || ~7.0 || ^8.0", + "php": "^7.3 || ^8.0", "symfony/polyfill-mbstring": "^1.12", - "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0 || ^6.0" + "symfony/process": "^5.0 || ^6.0 || ^7.0" }, "replace": { "facebook/webdriver": "*" }, "require-dev": { - "ondram/ci-detector": "^2.1 || ^3.5 || ^4.0", + "ergebnis/composer-normalize": "^2.20.0", + "ondram/ci-detector": "^4.0", "php-coveralls/php-coveralls": "^2.4", - "php-mock/php-mock-phpunit": "^1.1 || ^2.0", + "php-mock/php-mock-phpunit": "^2.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9", + "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.5", - "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0 || ^6.0" + "symfony/var-dumper": "^5.0 || ^6.0" }, "suggest": { "ext-SimpleXML": "For Firefox profile creation" @@ -14508,9 +14799,9 @@ ], "support": { "issues": "https://github.com/php-webdriver/php-webdriver/issues", - "source": "https://github.com/php-webdriver/php-webdriver/tree/1.13.1" + "source": "https://github.com/php-webdriver/php-webdriver/tree/1.15.1" }, - "time": "2022-10-11T11:49:44+00:00" + "time": "2023-10-20T12:21:20+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -14624,24 +14915,27 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.6.2", + "version": "1.7.3", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d" + "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/48f445a408c131e38cab1c235aa6d2bb7a0bb20d", - "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", + "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.0", "php": "^7.4 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.13" }, "require-dev": { "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.8", "phpstan/phpstan-phpunit": "^1.1", @@ -14673,29 +14967,76 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.2" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.3" }, - "time": "2022-10-14T12:47:21+00:00" + "time": "2023-08-12T11:01:26+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "9.2.24", + "name": "phpstan/phpdoc-parser", + "version": "1.24.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed" + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "12f01d214f1c73b9c91fdb3b1c415e4c70652083" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2cf940ebc6355a9d430462811b5aaa308b174bed", - "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/12f01d214f1c73b9c91fdb3b1c415e4c70652083", + "reference": "12f01d214f1c73b9c91fdb3b1c415e4c70652083", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.3" + }, + "time": "2023-11-18T20:15:32+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.29", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", + "nikic/php-parser": "^4.15", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -14710,8 +15051,8 @@ "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { @@ -14744,7 +15085,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.24" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" }, "funding": [ { @@ -14752,7 +15094,7 @@ "type": "github" } ], - "time": "2023-01-26T08:26:55+00:00" + "time": "2023-09-19T04:57:46+00:00" }, { "name": "phpunit/php-file-iterator", @@ -14997,16 +15339,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.28", + "version": "9.6.13", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e" + "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e", - "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be", + "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be", "shasum": "" }, "require": { @@ -15021,7 +15363,7 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-code-coverage": "^9.2.28", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -15039,8 +15381,8 @@ "sebastian/version": "^3.0.2" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "bin": [ "phpunit" @@ -15048,7 +15390,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { @@ -15079,7 +15421,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13" }, "funding": [ { @@ -15095,7 +15438,7 @@ "type": "tidelift" } ], - "time": "2023-01-14T12:32:24+00:00" + "time": "2023-09-19T05:39:22+00:00" }, { "name": "sebastian/cli-parser", @@ -15397,16 +15740,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", "shasum": "" }, "require": { @@ -15451,7 +15794,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" }, "funding": [ { @@ -15459,20 +15802,20 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2023-05-07T05:35:17+00:00" }, { "name": "sebastian/environment", - "version": "5.1.4", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { @@ -15514,7 +15857,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -15522,7 +15865,7 @@ "type": "github" } ], - "time": "2022-04-03T09:37:03+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", @@ -15603,16 +15946,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "5.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "bde739e7565280bda77be70044ac1047bc007e34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", "shasum": "" }, "require": { @@ -15655,7 +15998,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" }, "funding": [ { @@ -15663,7 +16006,7 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2023-08-02T09:26:13+00:00" }, { "name": "sebastian/lines-of-code", @@ -15836,16 +16179,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { @@ -15884,10 +16227,10 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { @@ -15895,7 +16238,7 @@ "type": "github" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { "name": "sebastian/resource-operations", @@ -15954,16 +16297,16 @@ }, { "name": "sebastian/type", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { @@ -15998,7 +16341,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -16006,7 +16349,7 @@ "type": "github" } ], - "time": "2022-09-12T14:47:03+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", @@ -16063,16 +16406,16 @@ }, { "name": "spatie/backtrace", - "version": "1.2.1", + "version": "1.5.3", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "4ee7d41aa5268107906ea8a4d9ceccde136dbd5b" + "reference": "483f76a82964a0431aa836b6ed0edde0c248e3ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/4ee7d41aa5268107906ea8a4d9ceccde136dbd5b", - "reference": "4ee7d41aa5268107906ea8a4d9ceccde136dbd5b", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/483f76a82964a0431aa836b6ed0edde0c248e3ab", + "reference": "483f76a82964a0431aa836b6ed0edde0c248e3ab", "shasum": "" }, "require": { @@ -16081,6 +16424,7 @@ "require-dev": { "ext-json": "*", "phpunit/phpunit": "^9.3", + "spatie/phpunit-snapshot-assertions": "^4.2", "symfony/var-dumper": "^5.1" }, "type": "library", @@ -16108,8 +16452,7 @@ "spatie" ], "support": { - "issues": "https://github.com/spatie/backtrace/issues", - "source": "https://github.com/spatie/backtrace/tree/1.2.1" + "source": "https://github.com/spatie/backtrace/tree/1.5.3" }, "funding": [ { @@ -16121,43 +16464,44 @@ "type": "other" } ], - "time": "2021-11-09T10:57:15+00:00" + "time": "2023-06-28T12:59:17+00:00" }, { "name": "spatie/flare-client-php", - "version": "1.3.5", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "3e5dd5ac4928f3d2d036bd02de5eb83fd0ef1f42" + "reference": "5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/3e5dd5ac4928f3d2d036bd02de5eb83fd0ef1f42", - "reference": "3e5dd5ac4928f3d2d036bd02de5eb83fd0ef1f42", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec", + "reference": "5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec", "shasum": "" }, "require": { - "illuminate/pipeline": "^8.0|^9.0|^10.0", + "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0", + "nesbot/carbon": "^2.62.1", "php": "^8.0", - "spatie/backtrace": "^1.2", - "symfony/http-foundation": "^5.0|^6.0", - "symfony/mime": "^5.2|^6.0", - "symfony/process": "^5.2|^6.0", - "symfony/var-dumper": "^5.2|^6.0" + "spatie/backtrace": "^1.5.2", + "symfony/http-foundation": "^5.2|^6.0|^7.0", + "symfony/mime": "^5.2|^6.0|^7.0", + "symfony/process": "^5.2|^6.0|^7.0", + "symfony/var-dumper": "^5.2|^6.0|^7.0" }, "require-dev": { - "dms/phpunit-arraysubset-asserts": "^0.3.0", - "pestphp/pest": "^1.20", + "dms/phpunit-arraysubset-asserts": "^0.5.0", + "pestphp/pest": "^1.20|^2.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "spatie/phpunit-snapshot-assertions": "^4.0" + "spatie/phpunit-snapshot-assertions": "^4.0|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.1.x-dev" + "dev-main": "1.3.x-dev" } }, "autoload": { @@ -16182,7 +16526,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.3.5" + "source": "https://github.com/spatie/flare-client-php/tree/1.4.3" }, "funding": [ { @@ -16190,42 +16534,51 @@ "type": "github" } ], - "time": "2023-01-23T15:58:46+00:00" + "time": "2023-10-17T15:54:07+00:00" }, { "name": "spatie/ignition", - "version": "1.4.3", + "version": "1.11.3", "source": { "type": "git", "url": "https://github.com/spatie/ignition.git", - "reference": "2cf3833220cfe8fcf639544f8d7067b6469a00b0" + "reference": "3d886de644ff7a5b42e4d27c1e1f67c8b5f00044" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ignition/zipball/2cf3833220cfe8fcf639544f8d7067b6469a00b0", - "reference": "2cf3833220cfe8fcf639544f8d7067b6469a00b0", + "url": "https://api.github.com/repos/spatie/ignition/zipball/3d886de644ff7a5b42e4d27c1e1f67c8b5f00044", + "reference": "3d886de644ff7a5b42e4d27c1e1f67c8b5f00044", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", "php": "^8.0", - "spatie/flare-client-php": "^1.1", - "symfony/console": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" + "spatie/backtrace": "^1.5.3", + "spatie/flare-client-php": "^1.4.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "require-dev": { + "illuminate/cache": "^9.52|^10.0|^11.0", "mockery/mockery": "^1.4", - "pestphp/pest": "^1.20", + "pestphp/pest": "^1.20|^2.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "symfony/process": "^5.4|^6.0" + "psr/simple-cache-implementation": "*", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "vlucas/phpdotenv": "^5.5" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "simple-cache-implementation": "To cache solutions from OpenAI" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.2.x-dev" + "dev-main": "1.5.x-dev" } }, "autoload": { @@ -16264,7 +16617,7 @@ "type": "github" } ], - "time": "2023-01-23T15:28:32+00:00" + "time": "2023-10-18T14:09:40+00:00" }, { "name": "spatie/laravel-ignition", @@ -16419,16 +16772,16 @@ }, { "name": "symfony/yaml", - "version": "v5.4.19", + "version": "v5.4.31", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "71c05db20cb9b54d381a28255f17580e2b7e36a5" + "reference": "f387675d7f5fc4231f7554baa70681f222f73563" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/71c05db20cb9b54d381a28255f17580e2b7e36a5", - "reference": "71c05db20cb9b54d381a28255f17580e2b7e36a5", + "url": "https://api.github.com/repos/symfony/yaml/zipball/f387675d7f5fc4231f7554baa70681f222f73563", + "reference": "f387675d7f5fc4231f7554baa70681f222f73563", "shasum": "" }, "require": { @@ -16474,7 +16827,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.19" + "source": "https://github.com/symfony/yaml/tree/v5.4.31" }, "funding": [ { @@ -16490,7 +16843,7 @@ "type": "tidelift" } ], - "time": "2023-01-10T18:51:14+00:00" + "time": "2023-11-03T14:41:28+00:00" }, { "name": "theseer/tokenizer", @@ -16791,5 +17144,5 @@ "platform-dev": { "php": "^8.1" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/database/migrations/2023_11_26_082959_add_bank_integration_id.php b/database/migrations/2023_11_26_082959_add_bank_integration_id.php new file mode 100644 index 000000000000..4d7ba978ec08 --- /dev/null +++ b/database/migrations/2023_11_26_082959_add_bank_integration_id.php @@ -0,0 +1,38 @@ +string('integration_type')->nullable(); + }); + + // migrate old account to be used with yodlee + BankIntegration::query()->whereNull('integration_type')->cursor()->each(function ($bank_integration) { + $bank_integration->integration_type = BankIntegration::INTEGRATION_TYPE_YODLEE; + $bank_integration->save(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('bank_integration', function (Blueprint $table) { + $table->dropColumn('integration_id'); + }); + } +}; diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 312446a4580f..490236f79ed8 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,49 +1,49 @@ { - "/js/app.js": "/js/app.js?id=7b6124b74168ccb1cc7da22f7a2bc9ed", - "/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=b6723e0b8ea33f1f50617fa5f289a9d3", - "/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=faf4828cc6b3b73b69c53d3046661884", - "/js/clients/payments/forte-credit-card-payment.js": "/js/clients/payments/forte-credit-card-payment.js?id=f42dd0caddb3603e71db061924c4b172", - "/js/clients/payments/forte-ach-payment.js": "/js/clients/payments/forte-ach-payment.js?id=b8173c7c0dee76bf9ae6312a963ae0e4", - "/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=207f218c44553470287f35f33a7eb154", - "/js/clients/payments/stripe-klarna.js": "/js/clients/payments/stripe-klarna.js?id=7268f9282c6bb3b04d19d11a7b0c1681", - "/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=404b7ee18e420de0e73f5402b7e39122", - "/js/clients/purchase_orders/action-selectors.js": "/js/clients/purchase_orders/action-selectors.js?id=2f0c4e3bab30a98e33ac768255113174", - "/js/clients/purchase_orders/accept.js": "/js/clients/purchase_orders/accept.js?id=9bb483a89a887f753e49c0b635d6276a", - "/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=752e2bb6390f1a422e31868cf2a2bf67", - "/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js?id=4fc5dec1bc4fc21b9e32b1b490c3e7ae", - "/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=018ecad3a1bcc1ecc47f76754a573ff2", - "/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=7cb96275b3eb4901054564c654fb60e3", - "/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=3a4c5cfac7dd4c9218be55945c3c8e85", - "/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=1e58e219878ce3f3ee4d313346ad5f68", - "/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=6e7c8ab039a239727317ae8622de10db", - "/js/setup/setup.js": "/js/setup/setup.js?id=8cab3339ef48418e1fb2e7a9259d51ca", - "/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=cf50b5ba1fcd1d184bf0c10d710672c8", - "/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=682de6347049b32c9488f39c78a68ace", - "/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=d3c404bb646f1aeaf2382a8c57ab8e1a", - "/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=e1c0599d6f7dc163b549a6df0b3490b4", - "/js/clients/payments/braintree-credit-card.js": "/js/clients/payments/braintree-credit-card.js?id=8b036822abaa4ceb379008fc14208dc2", - "/js/clients/payments/braintree-paypal.js": "/js/clients/payments/braintree-paypal.js?id=de0b1d0c6da7ff509bef3aee8d09e7f8", - "/js/clients/payments/wepay-credit-card.js": "/js/clients/payments/wepay-credit-card.js?id=92ef8632637d335cd0e4bc29a05b7df8", - "/js/clients/payment_methods/wepay-bank-account.js": "/js/clients/payment_methods/wepay-bank-account.js?id=af85b3f6d53c55b5d0e3a80ef58ce0de", - "/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=3869bc6d80acc83f81d9afe8efaae728", - "/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js?id=7cd5a1d95d33ada211ce185ad6e4bb33", - "/js/clients/payments/eway-credit-card.js": "/js/clients/payments/eway-credit-card.js?id=27274d334aed0824ce4654fa22132f7f", - "/js/clients/payment_methods/braintree-ach.js": "/js/clients/payment_methods/braintree-ach.js?id=f85ebb6a77002afd350086d1274b6af5", - "/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=238e7001420a22b001856193689a1e70", - "/js/clients/statements/view.js": "/js/clients/statements/view.js?id=13e043123f1e58409394458a70461d63", - "/js/clients/payments/razorpay-aio.js": "/js/clients/payments/razorpay-aio.js?id=494f58d2fd8984792833ba7d3055de08", - "/js/clients/payments/stripe-sepa.js": "/js/clients/payments/stripe-sepa.js?id=77d4e397d193196e482af80737bff64a", - "/js/clients/payment_methods/authorize-checkout-card.js": "/js/clients/payment_methods/authorize-checkout-card.js?id=659c4287fb8ef1c458071c206c4d965d", - "/js/clients/payments/stripe-giropay.js": "/js/clients/payments/stripe-giropay.js?id=852a9abf5f3a29f5d7d2f989cbeab374", - "/js/clients/payments/stripe-acss.js": "/js/clients/payments/stripe-acss.js?id=447c587a5eeb0c1de3091c8358db7ad7", - "/js/clients/payments/stripe-bancontact.js": "/js/clients/payments/stripe-bancontact.js?id=f694d3f9f01e4550cb5a3eb6cb43c12d", - "/js/clients/payments/stripe-becs.js": "/js/clients/payments/stripe-becs.js?id=97ea3555a8504662eda5fce9c9115e5a", - "/js/clients/payments/stripe-eps.js": "/js/clients/payments/stripe-eps.js?id=749cba1332a29baa444b37cee2ade2d7", - "/js/clients/payments/stripe-ideal.js": "/js/clients/payments/stripe-ideal.js?id=34cf4ee3f189427fb69d0df8f5a4b766", - "/js/clients/payments/stripe-przelewy24.js": "/js/clients/payments/stripe-przelewy24.js?id=448b197a1d94b4408e130b5b8b1c2e53", - "/js/clients/payments/stripe-browserpay.js": "/js/clients/payments/stripe-browserpay.js?id=7015e43eb5f9f9f2f45f54b41b5780a0", - "/js/clients/payments/stripe-fpx.js": "/js/clients/payments/stripe-fpx.js?id=243c2929386b10c6a0c49ca3bcabfb2d", - "/css/app.css": "/css/app.css?id=0cb847167b91d8db2ca50d30e0d691ae", - "/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ada60afcedcb7c", - "/vendor/clipboard.min.js": "/vendor/clipboard.min.js?id=15f52a1ee547f2bdd46e56747332ca2d" + "/js/app.js": "/js/app.js", + "/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js", + "/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js", + "/js/clients/payments/forte-credit-card-payment.js": "/js/clients/payments/forte-credit-card-payment.js", + "/js/clients/payments/forte-ach-payment.js": "/js/clients/payments/forte-ach-payment.js", + "/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js", + "/js/clients/payments/stripe-klarna.js": "/js/clients/payments/stripe-klarna.js", + "/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js", + "/js/clients/purchase_orders/action-selectors.js": "/js/clients/purchase_orders/action-selectors.js", + "/js/clients/purchase_orders/accept.js": "/js/clients/purchase_orders/accept.js", + "/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js", + "/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js", + "/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js", + "/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js", + "/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js", + "/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js", + "/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js", + "/js/setup/setup.js": "/js/setup/setup.js", + "/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js", + "/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js", + "/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js", + "/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js", + "/js/clients/payments/braintree-credit-card.js": "/js/clients/payments/braintree-credit-card.js", + "/js/clients/payments/braintree-paypal.js": "/js/clients/payments/braintree-paypal.js", + "/js/clients/payments/wepay-credit-card.js": "/js/clients/payments/wepay-credit-card.js", + "/js/clients/payment_methods/wepay-bank-account.js": "/js/clients/payment_methods/wepay-bank-account.js", + "/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js", + "/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js", + "/js/clients/payments/eway-credit-card.js": "/js/clients/payments/eway-credit-card.js", + "/js/clients/payment_methods/braintree-ach.js": "/js/clients/payment_methods/braintree-ach.js", + "/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js", + "/js/clients/statements/view.js": "/js/clients/statements/view.js", + "/js/clients/payments/razorpay-aio.js": "/js/clients/payments/razorpay-aio.js", + "/js/clients/payments/stripe-sepa.js": "/js/clients/payments/stripe-sepa.js", + "/js/clients/payment_methods/authorize-checkout-card.js": "/js/clients/payment_methods/authorize-checkout-card.js", + "/js/clients/payments/stripe-giropay.js": "/js/clients/payments/stripe-giropay.js", + "/js/clients/payments/stripe-acss.js": "/js/clients/payments/stripe-acss.js", + "/js/clients/payments/stripe-bancontact.js": "/js/clients/payments/stripe-bancontact.js", + "/js/clients/payments/stripe-becs.js": "/js/clients/payments/stripe-becs.js", + "/js/clients/payments/stripe-eps.js": "/js/clients/payments/stripe-eps.js", + "/js/clients/payments/stripe-ideal.js": "/js/clients/payments/stripe-ideal.js", + "/js/clients/payments/stripe-przelewy24.js": "/js/clients/payments/stripe-przelewy24.js", + "/js/clients/payments/stripe-browserpay.js": "/js/clients/payments/stripe-browserpay.js", + "/js/clients/payments/stripe-fpx.js": "/js/clients/payments/stripe-fpx.js", + "/css/app.css": "/css/app.css", + "/css/card-js.min.css": "/css/card-js.min.css", + "/vendor/clipboard.min.js": "/vendor/clipboard.min.js" } From ef48bd150c4eac1193b08d4ec614db472c237202 Mon Sep 17 00:00:00 2001 From: paulwer Date: Fri, 1 Dec 2023 12:22:04 +0100 Subject: [PATCH 03/69] fix for seeders --- app/Observers/ProductObserver.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/Observers/ProductObserver.php b/app/Observers/ProductObserver.php index cf759b798151..fbd85c18d49f 100644 --- a/app/Observers/ProductObserver.php +++ b/app/Observers/ProductObserver.php @@ -11,7 +11,9 @@ namespace App\Observers; +use App\Jobs\Util\WebhookHandler; use App\Models\Product; +use App\Models\Webhook; class ProductObserver { @@ -24,8 +26,8 @@ class ProductObserver public function created(Product $product) { $subscriptions = Webhook::where('company_id', $product->company->id) - ->where('event_id', Webhook::EVENT_CREATE_PRODUCT) - ->exists(); + ->where('event_id', Webhook::EVENT_CREATE_PRODUCT) + ->exists(); if ($subscriptions) { WebhookHandler::dispatch(Webhook::EVENT_CREATE_PRODUCT, $product, $product->company)->delay(now()->addSeconds(2)); @@ -41,8 +43,8 @@ class ProductObserver public function updated(Product $product) { $subscriptions = Webhook::where('company_id', $product->company->id) - ->where('event_id', Webhook::EVENT_UPDATE_PRODUCT) - ->exists(); + ->where('event_id', Webhook::EVENT_UPDATE_PRODUCT) + ->exists(); if ($subscriptions) { WebhookHandler::dispatch(Webhook::EVENT_UPDATE_PRODUCT, $product, $product->company)->delay(now()->addSeconds(2)); @@ -58,8 +60,8 @@ class ProductObserver public function deleted(Product $product) { $subscriptions = Webhook::where('company_id', $product->company->id) - ->where('event_id', Webhook::EVENT_DELETE_PRODUCT) - ->exists(); + ->where('event_id', Webhook::EVENT_DELETE_PRODUCT) + ->exists(); if ($subscriptions) { WebhookHandler::dispatch(Webhook::EVENT_DELETE_PRODUCT, $product, $product->company)->delay(now()->addSeconds(2)); From b54b626332a7ab50275db5082db0b8aff1a7ada9 Mon Sep 17 00:00:00 2001 From: paulwer Date: Fri, 1 Dec 2023 14:30:33 +0100 Subject: [PATCH 04/69] wip: changes --- app/Helpers/Bank/Nordigen/Nordigen.php | 144 ++-- .../Transformer/AccountTransformer.php | 145 ++-- .../Transformer/ExpenseTransformer.php | 4 +- .../Transformer/IncomeTransformer.php | 19 +- .../Controllers/Bank/NordigenController.php | 15 +- .../Controllers/Bank/YodleeController.php | 239 ++++--- .../Controllers/BankIntegrationController.php | 140 ++-- app/Jobs/Bank/MatchBankTransactions.php | 186 +++--- .../Bank/ProcessBankTransactionsNordigen.php | 166 +++++ ....php => ProcessBankTransactionsYodlee.php} | 54 +- app/Jobs/Ninja/BankTransactionSync.php | 38 +- app/Models/Account.php | 114 ++-- app/Models/BankIntegration.php | 1 - database/factories/BankIntegrationFactory.php | 4 +- ...3_11_26_082959_add_bank_integration_id.php | 18 +- public/mix-manifest.json | 94 +-- tests/Feature/Bank/YodleeApiTest.php | 624 +++++++++--------- 17 files changed, 1172 insertions(+), 833 deletions(-) create mode 100644 app/Jobs/Bank/ProcessBankTransactionsNordigen.php rename app/Jobs/Bank/{ProcessBankTransactions.php => ProcessBankTransactionsYodlee.php} (71%) diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index 35f18aad5e72..18518847f5c7 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -7,9 +7,11 @@ * @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com) * * @license https://www.elastic.co/licensing/elastic-license + * + * Documentation of Api-Usage: https://developer.gocardless.com/bank-account-data/overview */ -namespace App\Helpers\Bank\Yodlee; +namespace App\Helpers\Bank\Nordigen; use App\Exceptions\NordigenApiException; use App\Helpers\Bank\Nordigen\Transformer\AccountTransformer; @@ -48,79 +50,143 @@ $requisitionId = $session["requisition_id"]; class Nordigen { - public bool $test_mode = false; + public bool $test_mode = false; // https://developer.gocardless.com/bank-account-data/sandbox + + public string $sandbox_institutionId = "SANDBOXFINANCE_SFIN0000"; protected \Nordigen\NordigenPHP\API\NordigenClient $client; - protected string $secret_id; - - protected string $secret_key; - - public function __construct() + public function __construct(string $client_id, string $client_secret) { - $this->secret_id = config('ninja.nordigen.secret_id'); - $this->secret_key = config('ninja.nordigen.secret_key'); + $this->client = new \Nordigen\NordigenPHP\API\NordigenClient($client_id, $client_secret); - $this->client = new \Nordigen\NordigenPHP\API\NordigenClient($this->secret_id, $this->secret_key); } + // metadata-section for frontend public function getInstitutions() { + + if ($this->test_mode) + return (array) $this->client->institution->getInstitution($this->sandbox_institutionId); + return $this->client->institution->getInstitutions(); } - public function getValidAccounts() + // requisition-section + public function createRequisition(string $redirect, string $initutionId) + { + if ($this->test_mode && $initutionId != $this->sandbox_institutionId) + throw new \Exception('invalid institutionId while in test-mode'); + + return $this->client->requisition->createRequisition($redirect, $initutionId); + } + + public function getRequisition(string $requisitionId) + { + return $this->client->requisition->getRequisition($requisitionId); + } + + public function cleanupRequisitions() + { + $requisitions = $this->client->requisition->getRequisitions(); + + foreach ($requisitions as $requisition) { + // filter to expired OR older than 7 days created and no accounts + if ($requisition->status == "EXPIRED" || (sizeOf($requisition->accounts) != 0 && strtotime($requisition->created) > (new \DateTime())->modify('-7 days'))) + continue; + + $this->client->requisition->deleteRequisition($requisition->id); + } + } + + // account-section: these methods should be used to get data of connected accounts + public function getAccounts() { // get all valid requisitions $requisitions = $this->client->requisition->getRequisitions(); // fetch all valid accounts for activated requisitions - $accounts = []; + $nordigen_accountIds = []; foreach ($requisitions as $requisition) { - foreach ($requisition->accounts as $account) { - $account = $account = $this->client->account($account); - - array_push($accounts, $account); + foreach ($requisition->accounts as $accountId) { + array_push($nordigen_accountIds, $accountId); } } - return $accounts; + $nordigen_accountIds = array_unique($nordigen_accountIds); + + $nordigen_accounts = []; + foreach ($nordigen_accountIds as $accountId) { + $nordigen_account = $this->getAccount($accountId); + + array_push($nordigen_accounts, $nordigen_account); + } + + + return $nordigen_accounts; } - public function cleanup() + public function getAccount(string $account_id) { + + $out = new \stdClass(); + + $out->data = $this->client->account($account_id)->getAccountDetails(); + $out->metadata = $this->client->account($account_id)->getAccountMetaData(); + $out->balances = $this->client->account($account_id)->getAccountBalances(); + $out->institution = $this->client->institution->getInstitution($out->metadata["institution_id"]); + + $it = new AccountTransformer(); + return $it->transform($out); + + } + + public function isAccountActive(string $account_id) + { + + try { + $account = $this->client->account($account_id)->getAccountMetaData(); + + if ($account["status"] != "READY") + return false; + + return true; + } catch (\Exception $e) { + // TODO: check for not-found exception + return false; + } + + } + + /** + * this method will remove all according requisitions => this can result in removing multiple accounts, if a user reuses a requisition + */ + public function deleteAccount(string $account_id) + { + + // get all valid requisitions $requisitions = $this->client->requisition->getRequisitions(); - // TODO: filter to older than 2 days created AND (no accounts or invalid) - + // fetch all valid accounts for activated requisitions foreach ($requisitions as $requisition) { - $this->client->requisition->deleteRequisition($requisition->id); + foreach ($requisition->accounts as $accountId) { + + if ($accountId) { + $this->client->requisition->deleteRequisition($accountId); + } + + } } + } - // account-section: these methods should be used to get data of connected accounts - - public function getAccountMetaData(string $account_id) + public function getTransactions(string $accountId, string $dateFrom = null) { - return $this->client->account($account_id)->getAccountMetaData(); - } - public function getAccountDetails(string $account_id) - { - return $this->client->account($account_id)->getAccountDetails(); - } + return $this->client->account($accountId)->getAccountTransactions($dateFrom); - public function getAccountBalances(string $account_id) - { - return $this->client->account($account_id)->getAccountBalances(); } - - public function getAccountTransactions(string $account_id) - { - return $this->client->account($account_id)->getAccountTransactions(); - } - } diff --git a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php index e2b90656fb6f..48c43559d2a2 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php @@ -9,55 +9,79 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\Helpers\Bank\Yodlee\Transformer; +namespace App\Helpers\Bank\Nordigen\Transformer; use App\Helpers\Bank\AccountTransformerInterface; - + /** [0] => stdClass Object ( - [CONTAINER] => bank - [providerAccountId] => 11308693 - [accountName] => My CD - 8878 - [accountStatus] => ACTIVE - [accountNumber] => xxxx8878 - [aggregationSource] => USER - [isAsset] => 1 - [balance] => stdClass Object + [data] => stdClass Object ( - [currency] => USD - [amount] => 49778.07 + [resourceId] => XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + [iban] => DE0286055592XXXXXXXXXX + [currency] => EUR + [ownerName] => Max Mustermann + [product] => GiroKomfort + [bic] => WELADE8LXXX + [usage] => PRIV + ) + [metadata] => stdClass Object + ( + [id] => XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + [created] => 2022-12-05T18:41:53.986028Z + [last_accessed] => 2023-10-29T08:35:34.003611Z + [iban] => DE0286055592XXXXXXXXXX + [institution_id] => STADT_KREISSPARKASSE_LEIPZIG_WELADE8LXXX + [status] => READY + [owner_name] => Max Mustermann + ) + [balances] => stdClass Object + ( + [balances]: [ + { + [balanceAmount]: { + [amount] => 9825.64 + [currency] => EUR + }, + [balanceType] => closingBooked + [referenceDate] => 2023-12-01 + }, + { + [balanceAmount[: { + [amount] => 10325.64 + [currency] => EUR + }, + [balanceType] => interimAvailable + [creditLimitIncluded]: true, + [referenceDate] => 2023-12-01 + } + ] + ) + [institution] => stdClass Object + ( + [id] => STADT_KREISSPARKASSE_LEIPZIG_WELADE8LXXX + [name] => Stadt- und Kreissparkasse Leipzig + [bic] => WELADE8LXXX + [transaction_total_days] => 360 + [countries] => [ + "DE" + ], + [logo] => https://storage.googleapis.com/gc-prd-institution_icons-production/DE/PNG/sparkasse.png + [supported_payments] => { + [single-payment] => [ + "SCT", + "ISCT" + ] + }, + [supported_features] => [ + "card_accounts", + "payments", + "pending_transactions" + ], + [identification_codes] => [] ) - [id] => 12331861 - [includeInNetWorth] => 1 - [providerId] => 18769 - [providerName] => Dag Site Captcha - [isManual] => - [currentBalance] => stdClass Object - ( - [currency] => USD - [amount] => 49778.07 - ) - - [accountType] => CD - [displayedName] => LORETTA - [createdDate] => 2022-07-28T06:55:33Z - [lastUpdated] => 2022-07-28T06:56:09Z - [dataset] => Array - ( - [0] => stdClass Object - ( - [name] => BASIC_AGG_DATA - [additionalStatus] => AVAILABLE_DATA_RETRIEVED - [updateEligibility] => ALLOW_UPDATE - [lastUpdated] => 2022-07-28T06:55:50Z - [lastUpdateAttempt] => 2022-07-28T06:55:50Z - ) - - ) - -) ) */ @@ -65,16 +89,15 @@ use App\Helpers\Bank\AccountTransformerInterface; class AccountTransformer implements AccountTransformerInterface { - public function transform($yodlee_account) + public function transform($nordigen_account) { $data = []; - if(!property_exists($yodlee_account, 'account')) + if (!property_exists($nordigen_account, 'data') || !property_exists($nordigen_account, 'metadata') || !property_exists($nordigen_account, 'balances') || !property_exists($nordigen_account, 'institution')) return $data; - foreach($yodlee_account->account as $account) - { + foreach ($nordigen_account->account as $account) { $data[] = $this->transformAccount($account); } @@ -83,20 +106,28 @@ class AccountTransformer implements AccountTransformerInterface public function transformAccount($account) { - + + $used_balance = $account->balances[0]; + // prefer entry with closingBooked + foreach ($account->balances as $entry) { + if ($entry->balanceType === 'closingBooked') { // available: closingBooked, interimAvailable + $used_balance = $entry; + break; + } + } + return [ - 'id' => $account->id, + 'id' => $account->data->id, 'account_type' => $account->CONTAINER, - // 'account_name' => $account->accountName, - 'account_name' => property_exists($account, 'accountName') ? $account->accountName : $account->nickname, - 'account_status' => $account->accountStatus, - 'account_number' => property_exists($account, 'accountNumber') ? '**** ' . substr($account?->accountNumber, -7) : '', - 'provider_account_id' => $account->providerAccountId, - 'provider_id' => $account->providerId, - 'provider_name' => $account->providerName, - 'nickname' => property_exists($account, 'nickname') ? $account->nickname : '', - 'current_balance' => property_exists($account, 'currentBalance') ? $account->currentBalance->amount : 0, - 'account_currency' => property_exists($account, 'currency') ? $account->currentBalance->currency : '', + 'account_name' => $account->data->iban, + 'account_status' => $account->metadata->status, + 'account_number' => '**** ' . substr($account->data->iban, -7), + 'provider_account_id' => $account->data->iban, + 'provider_id' => $account->institution_id, + 'provider_name' => $account->institution->name, + 'nickname' => property_exists($account->data, 'owner_name') ? $account->data->owner_name : '', + 'current_balance' => (int) property_exists($used_balance, 'balanceAmount') ? $used_balance->balanceAmount->amount : 0, + 'account_currency' => property_exists($used_balance, 'balanceAmount') ? $used_balance->balanceAmount->currency : '', ]; } } diff --git a/app/Helpers/Bank/Nordigen/Transformer/ExpenseTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/ExpenseTransformer.php index 6274bb2ed911..ade514d8fdea 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/ExpenseTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/ExpenseTransformer.php @@ -9,8 +9,8 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\Helpers\Bank\Yodlee\Transformer; - +namespace App\Helpers\Bank\Nordigen\Transformer; + /** "date": "string", "sourceId": "string", diff --git a/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php index fc272f0eb1bd..3de6c3e7680f 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php @@ -9,7 +9,7 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\Helpers\Bank\Yodlee\Transformer; +namespace App\Helpers\Bank\Nordigen\Transformer; use App\Helpers\Bank\BankRevenueInterface; use App\Utils\Traits\AppSetup; @@ -74,7 +74,7 @@ use Illuminate\Support\Facades\Cache; "holdingDescription": "string", "isin": "string", "status": "POSTED" - + ( [CONTAINER] => bank [id] => 103953585 @@ -96,7 +96,7 @@ use Illuminate\Support\Facades\Cache; [original] => CHEROKEE NATION TAX TA TAHLEQUAH OK ) -[isManual] => +[isManual] => [sourceType] => AGGREGATED [date] => 2022-08-03 [transactionDate] => 2022-08-03 @@ -122,11 +122,10 @@ class IncomeTransformer implements BankRevenueInterface $data = []; - if(!property_exists($transaction, 'transaction')) + if (!property_exists($transaction, 'transaction')) return $data; - foreach($transaction->transaction as $transaction) - { + foreach ($transaction->transaction as $transaction) { $data[] = $this->transformTransaction($transaction); } @@ -154,7 +153,7 @@ class IncomeTransformer implements BankRevenueInterface { //CREDIT / DEBIT - if(property_exists($transaction, 'highLevelCategoryId') && $transaction->highLevelCategoryId == 10000012) + if (property_exists($transaction, 'highLevelCategoryId') && $transaction->highLevelCategoryId == 10000012) return 'CREDIT'; return 'DEBIT'; @@ -166,15 +165,15 @@ class IncomeTransformer implements BankRevenueInterface $currencies = Cache::get('currencies'); - if (! $currencies) { + if (!$currencies) { $this->buildCache(true); } - $currency = $currencies->filter(function ($item) use($code){ + $currency = $currencies->filter(function ($item) use ($code) { return $item->code == $code; })->first(); - if($currency) + if ($currency) return $currency->id; return 1; diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index fe5e0d2a456c..5e077ec63a87 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -14,13 +14,14 @@ namespace App\Http\Controllers\Bank; use App\Helpers\Bank\Nordigen\Nordigen; use App\Http\Controllers\BaseController; use App\Http\Requests\Yodlee\YodleeAuthRequest; -use App\Jobs\Bank\ProcessBankTransactions; +use App\Jobs\Bank\ProcessBankTransactionsNordigen; use App\Models\BankIntegration; use Illuminate\Http\Request; -class YodleeController extends BaseController +class NordigenController extends BaseController { + // TODO!!!!! public function auth(YodleeAuthRequest $request) { @@ -35,21 +36,21 @@ class YodleeController extends BaseController //ensure user is enterprise!! - if ($company->account->bank_integration_account_id) { + if ($company->account->bank_integration_nordigen_client_id && $company->account->bank_integration_nordigen_client_id) { $flow = 'edit'; - $token = $company->account->bank_integration_account_id; + $token = $company->account->bank_integration_nordigen_client_id; } else { $flow = 'add'; - $response = $yodlee->createUser($company); + $response = $nordigen->createUser($company); $token = $response->user->loginName; - $company->account->bank_integration_account_id = $token; + $company->account->bank_integration_nordigen_client_id = $token; $company->push(); @@ -107,7 +108,7 @@ class YodleeController extends BaseController $company->account->bank_integrations->each(function ($bank_integration) use ($company) { - ProcessBankTransactions::dispatch($company->account->bank_integration_account_id, $bank_integration); + ProcessBankTransactionsNordigen::dispatch($company->account, $bank_integration); }); diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index ad651f4704ed..13cb76c75bfe 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -14,7 +14,7 @@ namespace App\Http\Controllers\Bank; use App\Helpers\Bank\Yodlee\Yodlee; use App\Http\Controllers\BaseController; use App\Http\Requests\Yodlee\YodleeAuthRequest; -use App\Jobs\Bank\ProcessBankTransactions; +use App\Jobs\Bank\ProcessBankTransactionsYodlee; use App\Models\BankIntegration; use Illuminate\Http\Request; @@ -24,7 +24,7 @@ class YodleeController extends BaseController public function auth(YodleeAuthRequest $request) { - // create a user at this point + // create a user at this point // use the one time token here to pull in the actual user // store the user_account_id on the accounts table @@ -35,14 +35,13 @@ class YodleeController extends BaseController //ensure user is enterprise!! - if($company->account->bank_integration_account_id){ + if ($company->account->bank_integration_yodlee_account_id) { $flow = 'edit'; - $token = $company->account->bank_integration_account_id; + $token = $company->account->bank_integration_yodlee_account_id; - } - else{ + } else { $flow = 'add'; @@ -50,15 +49,15 @@ class YodleeController extends BaseController $token = $response->user->loginName; - $company->account->bank_integration_account_id = $token; + $company->account->bank_integration_yodlee_account_id = $token; $company->push(); - + } - + $yodlee = new Yodlee($token); - if($request->has('window_closed') && $request->input("window_closed") == "true") + if ($request->has('window_closed') && $request->input("window_closed") == "true") $this->getAccounts($company, $token); $data = [ @@ -79,13 +78,11 @@ class YodleeController extends BaseController { $yodlee = new Yodlee($token); - $accounts = $yodlee->getAccounts(); + $accounts = $yodlee->getAccounts(); - foreach($accounts as $account) - { + foreach ($accounts as $account) { - if(!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $company->id)->exists()) - { + if (!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $company->id)->exists()) { $bank_integration = new BankIntegration(); $bank_integration->company_id = $company->id; $bank_integration->account_id = $company->account_id; @@ -101,23 +98,23 @@ class YodleeController extends BaseController $bank_integration->balance = $account['current_balance']; $bank_integration->currency = $account['account_currency']; $bank_integration->from_date = now()->subYear(); - + $bank_integration->save(); } } - $company->account->bank_integrations->each(function ($bank_integration) use ($company){ - - ProcessBankTransactions::dispatch($company->account->bank_integration_account_id, $bank_integration); + $company->account->bank_integrations->each(function ($bank_integration) use ($company) { + + ProcessBankTransactionsYodlee::dispatch($company->account, $bank_integration); }); } - /** + /** * Process Yodlee Refresh Webhook. * * @@ -152,70 +149,70 @@ class YodleeController extends BaseController * ) */ - /* - { - "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" - } - ] - } - ] + /* + { + "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 refreshWebhook(Request $request) { -//we should ignore this one + //we should ignore this one nlog("yodlee refresh"); nlog($request->all()); return response()->json(['message' => 'Success'], 200); - + // // return response()->json(['message' => 'Unauthorized'], 403); } - -/* -{ - "event":{ - "notificationId":"63c73475-4db5-49ef-8553-8303337ca7c3", - "info":"LATEST_BALANCE_UPDATES", - "loginName":"user1", - "data":{ - "providerAccountId":658552, - "latestBalanceEvent":[ - { - "accountId":12345, - "status":"SUCCESS" - }, - { - "accountId":12346, - "status":"FAILED" - } - ] - } - } -} -*/ + + /* + { + "event":{ + "notificationId":"63c73475-4db5-49ef-8553-8303337ca7c3", + "info":"LATEST_BALANCE_UPDATES", + "loginName":"user1", + "data":{ + "providerAccountId":658552, + "latestBalanceEvent":[ + { + "accountId":12345, + "status":"SUCCESS" + }, + { + "accountId":12346, + "status":"FAILED" + } + ] + } + } + } + */ public function balanceWebhook(Request $request) { @@ -223,79 +220,79 @@ class YodleeController extends BaseController nlog($request->all()); return response()->json(['message' => 'Success'], 200); - + // // return response()->json(['message' => 'Unauthorized'], 403); } - -/* -{ - "event":{ - "data":[ - { - "autoRefresh":{ - "additionalStatus":"SCHEDULED", - "status":"ENABLED" - }, - "accountIds":[ - 1112645899, - 1112645898 - ], - "loginName":"YSL1555332811628", - "providerAccountId":11381459 - } - ], - "notificationTime":"2019-06-14T04:49:39Z", - "notificationId":"4e672150-156048777", - "info":"AUTO_REFRESH_UPDATES" - } -} -*/ + + /* + { + "event":{ + "data":[ + { + "autoRefresh":{ + "additionalStatus":"SCHEDULED", + "status":"ENABLED" + }, + "accountIds":[ + 1112645899, + 1112645898 + ], + "loginName":"YSL1555332811628", + "providerAccountId":11381459 + } + ], + "notificationTime":"2019-06-14T04:49:39Z", + "notificationId":"4e672150-156048777", + "info":"AUTO_REFRESH_UPDATES" + } + } + */ public function refreshUpdatesWebhook(Request $request) { -//notifies a user if there are problems with yodlee accessing the data + //notifies a user if there are problems with yodlee accessing the data nlog("update refresh"); nlog($request->all()); return response()->json(['message' => 'Success'], 200); - + // // return response()->json(['message' => 'Unauthorized'], 403); } -/* -"event": { - "notificationId": "64b7ed1a-1530523285", - "info": "DATA_UPDATES.USER_DATA", - "data": { - "userCount": 1, - "fromDate": "2017-11-10T10:18:44Z", - "toDate": "2017-11-10T11:18:43Z", - "userData": [{ - "user": { - "loginName": "YSL1484052178554" - }, - "links": [{ - "methodType": "GET", - "rel": "getUserData", - "href": "dataExtracts/userData?fromDate=2017-11-10T10:18:44Z&toDate=2017-11-10T11:18:43Z&loginName=YSL1484052178554" + /* + "event": { + "notificationId": "64b7ed1a-1530523285", + "info": "DATA_UPDATES.USER_DATA", + "data": { + "userCount": 1, + "fromDate": "2017-11-10T10:18:44Z", + "toDate": "2017-11-10T11:18:43Z", + "userData": [{ + "user": { + "loginName": "YSL1484052178554" + }, + "links": [{ + "methodType": "GET", + "rel": "getUserData", + "href": "dataExtracts/userData?fromDate=2017-11-10T10:18:44Z&toDate=2017-11-10T11:18:43Z&loginName=YSL1484052178554" + }] }] - }] + } } -} -*/ + */ public function dataUpdatesWebhook(Request $request) { -//this is the main hook we use for notifications + //this is the main hook we use for notifications nlog("data refresh"); nlog($request->all()); return response()->json(['message' => 'Success'], 200); - + // // return response()->json(['message' => 'Unauthorized'], 403); diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 552aa02fe6d7..e9b890893c3e 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -14,6 +14,7 @@ namespace App\Http\Controllers; use App\Factory\BankIntegrationFactory; use App\Filters\BankIntegrationFilters; use App\Helpers\Bank\Yodlee\Yodlee; +use App\Helpers\Bank\Nordigen\Nordigen; use App\Http\Requests\BankIntegration\AdminBankIntegrationRequest; use App\Http\Requests\BankIntegration\BulkBankIntegrationRequest; use App\Http\Requests\BankIntegration\CreateBankIntegrationRequest; @@ -22,7 +23,9 @@ use App\Http\Requests\BankIntegration\EditBankIntegrationRequest; use App\Http\Requests\BankIntegration\ShowBankIntegrationRequest; use App\Http\Requests\BankIntegration\StoreBankIntegrationRequest; use App\Http\Requests\BankIntegration\UpdateBankIntegrationRequest; -use App\Jobs\Bank\ProcessBankTransactions; +use App\Jobs\Bank\ProcessBankTransactionsYodlee; +use App\Jobs\Bank\ProcessBankTransactionsNordigen; +use App\Models\Account; use App\Models\BankIntegration; use App\Repositories\BankIntegrationRepository; use App\Services\Bank\BankMatchingService; @@ -471,7 +474,7 @@ class BankIntegrationController extends BaseController $action = request()->input('action'); $ids = request()->input('ids'); - + $bank_integrations = BankIntegration::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get(); $bank_integrations->each(function ($bank_integration, $key) use ($action) { @@ -521,23 +524,45 @@ class BankIntegrationController extends BaseController */ public function refreshAccounts(AdminBankIntegrationRequest $request) { - // As yodlee is the first integration we don't need to perform switches yet, however - // if we add additional providers we can reuse this class + $account = auth()->user()->account; - $bank_account_id = auth()->user()->account->bank_integration_account_id; + $this->refreshAccountsYodlee($account); - if(!$bank_account_id) + $this->refreshAccountsNordigen($account); + + if (Cache::get("throttle_polling:{$account->key}")) + return response()->json(BankIntegration::query()->company(), 200); + + // Processing transactions for each bank account + $account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->each(function ($bank_integration) use ($account) { + + ProcessBankTransactionsYodlee::dispatch($account, $bank_integration); + + }); + + $account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function ($bank_integration) use ($account) { + + ProcessBankTransactionsNordigen::dispatch($account, $bank_integration); + + }); + + Cache::put("throttle_polling:{$account->key}", true, 300); + + return response()->json(BankIntegration::query()->company(), 200); + } + private function refreshAccountsYodlee(Account $account) + { + + if (!$account->bank_integration_yodlee_account_id) return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); - $yodlee = new Yodlee($bank_account_id); + $yodlee = new Yodlee($account->bank_integration_yodlee_account_id); - $accounts = $yodlee->getAccounts(); + $accounts = $yodlee->getAccounts(); - foreach($accounts as $account) - { + foreach ($accounts as $account) { - if(!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', auth()->user()->company()->id)->exists()) - { + if (!BankIntegration::where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('bank_account_id', $account['id'])->where('company_id', auth()->user()->company()->id)->exists()) { $bank_integration = new BankIntegration(); $bank_integration->company_id = auth()->user()->company()->id; $bank_integration->account_id = auth()->user()->account_id; @@ -552,26 +577,44 @@ class BankIntegrationController extends BaseController $bank_integration->nickname = $account['nickname']; $bank_integration->balance = $account['current_balance']; $bank_integration->currency = $account['account_currency']; - + $bank_integration->save(); - } + } + } + private function refreshAccountsNordigen(Account $account) + { - $account = auth()->user()->account; - - if(Cache::get("throttle_polling:{$account->key}")) - return response()->json(BankIntegration::query()->company(), 200); + if (!$account->bank_integration_nordigen_client_id || !$account->bank_integration_nordigen_client_secret) + return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); - $account->bank_integrations->each(function ($bank_integration) use ($account){ - - ProcessBankTransactions::dispatch($account->bank_integration_account_id, $bank_integration); + $nordigen = new Nordigen($account->bank_integration_nordigen_client_id, $account->bank_integration_nordigen_client_secret); - }); + $accounts = $nordigen->getAccounts(); - Cache::put("throttle_polling:{$account->key}", true, 300); + foreach ($accounts as $account) { - return response()->json(BankIntegration::query()->company(), 200); + if (!BankIntegration::where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('bank_account_id', $account['id'])->where('company_id', auth()->user()->company()->id)->exists()) { + $bank_integration = new BankIntegration(); + $bank_integration->company_id = auth()->user()->company()->id; + $bank_integration->account_id = auth()->user()->account_id; + $bank_integration->user_id = auth()->user()->id; + $bank_integration->bank_account_id = $account['id']; + $bank_integration->bank_account_type = $account['account_type']; + $bank_integration->bank_account_name = $account['account_name']; + $bank_integration->bank_account_status = $account['account_status']; + $bank_integration->bank_account_number = $account['account_number']; + $bank_integration->provider_id = $account['provider_id']; + $bank_integration->provider_name = $account['provider_name']; + $bank_integration->nickname = $account['nickname']; + $bank_integration->balance = $account['current_balance']; + $bank_integration->currency = $account['account_currency']; + + $bank_integration->save(); + } + + } } /** @@ -614,20 +657,36 @@ class BankIntegrationController extends BaseController public function removeAccount(AdminBankIntegrationRequest $request, $acc_id) { - $bank_account_id = auth()->user()->account->bank_integration_account_id; + $account = auth()->user()->account; - if(!$bank_account_id) + $bank_integration = BankIntegration::withTrashed()->where('bank_account_id', $acc_id)->company()->firstOrFail(); + + 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->bank_integration_repo->delete($bank_integration); + + return $this->itemResponse($bank_integration->fresh()); + } + + private function removeAccountYodlee(Account $account, BankIntegration $bank_integration) + { + if (!$account->bank_integration_yodlee_account_id) return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); - $bi = BankIntegration::withTrashed()->where('bank_account_id', $acc_id)->company()->firstOrFail(); + $yodlee = new Yodlee($account->bank_integration_yodlee_account_id); + $yodlee->deleteAccount($bank_integration->bank_account_id); + } - $yodlee = new Yodlee($bank_account_id); - $res = $yodlee->deleteAccount($acc_id); - - $this->bank_integration_repo->delete($bi); - - return $this->itemResponse($bi->fresh()); + private function removeAccountNordigen(Account $account, BankIntegration $bank_integration) + { + if (!$account->bank_integration_nordigen_client_id || !$account->bank_integration_nordigen_client_secret) + 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->deleteAccount($bank_integration->bank_account_id); } @@ -669,14 +728,21 @@ class BankIntegrationController extends BaseController */ public function getTransactions(AdminBankIntegrationRequest $request) { + // Yodlee + auth()->user()->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->each(function ($bank_integration) { - auth()->user()->account->bank_integrations->each(function ($bank_integration) { - - (new ProcessBankTransactions(auth()->user()->account->bank_integration_account_id, $bank_integration))->handle(); + (new ProcessBankTransactionsYodlee(auth()->user()->account, $bank_integration))->handle(); + + }); + + // Nordigen + auth()->user()->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function ($bank_integration) { + + (new ProcessBankTransactionsYodlee(auth()->user()->account, $bank_integration))->handle(); }); return response()->json(['message' => 'Fetching transactions....'], 200); } -} \ No newline at end of file +} diff --git a/app/Jobs/Bank/MatchBankTransactions.php b/app/Jobs/Bank/MatchBankTransactions.php index 708206c15a3f..f84022cf4fcf 100644 --- a/app/Jobs/Bank/MatchBankTransactions.php +++ b/app/Jobs/Bank/MatchBankTransactions.php @@ -59,7 +59,7 @@ class MatchBankTransactions implements ShouldQueue private $categories; private float $available_balance = 0; - + private float $applied_amount = 0; private array $attachable_invoices = []; @@ -86,6 +86,7 @@ class MatchBankTransactions implements ShouldQueue * * @return void */ + // TODO: what are these categories, and for what do we need them public function handle() { @@ -93,33 +94,31 @@ class MatchBankTransactions implements ShouldQueue $this->company = Company::find($this->company_id); - if($this->company->account->bank_integration_account_id) - $yodlee = new Yodlee($this->company->account->bank_integration_account_id); + if ($this->company->account->bank_integration_yodlee_account_id) + $yodlee = new Yodlee($this->company->account->bank_integration_yodlee_account_id); else $yodlee = false; $bank_categories = Cache::get('bank_categories'); - - if(!$bank_categories && $yodlee){ + + if (!$bank_categories && $yodlee) { $_categories = $yodlee->getTransactionCategories(); $this->categories = collect($_categories->transactionCategory); Cache::forever('bank_categories', $this->categories); - } - else { + } else { $this->categories = collect($bank_categories); } - foreach($this->input as $input) - { + foreach ($this->input as $input) { nlog($input); - if(array_key_exists('invoice_ids', $input) && strlen($input['invoice_ids']) >= 1) + if (array_key_exists('invoice_ids', $input) && strlen($input['invoice_ids']) >= 1) $this->matchInvoicePayment($input); - elseif(array_key_exists('payment_id', $input) && strlen($input['payment_id']) >= 1) + elseif (array_key_exists('payment_id', $input) && strlen($input['payment_id']) >= 1) $this->linkPayment($input); - elseif(array_key_exists('expense_id', $input) && strlen($input['expense_id']) >= 1) + elseif (array_key_exists('expense_id', $input) && strlen($input['expense_id']) >= 1) $this->linkExpense($input); - elseif((array_key_exists('vendor_id', $input) && strlen($input['vendor_id']) >= 1) || (array_key_exists('ninja_category_id', $input) && strlen($input['ninja_category_id']) >= 1)) + elseif ((array_key_exists('vendor_id', $input) && strlen($input['vendor_id']) >= 1) || (array_key_exists('ninja_category_id', $input) && strlen($input['ninja_category_id']) >= 1)) $this->matchExpense($input); } @@ -133,28 +132,27 @@ class MatchBankTransactions implements ShouldQueue $invoices = explode(",", $invoice_hashed_ids); - if(count($invoices) >= 1) - { + if (count($invoices) >= 1) { - foreach($invoices as $invoice){ + foreach ($invoices as $invoice) { - if(is_string($invoice) && strlen($invoice) > 1) + if (is_string($invoice) && strlen($invoice) > 1) $collection->push($this->decodePrimaryKey($invoice)); } - + } return $collection; } - private function checkPayable($invoices) :bool + private function checkPayable($invoices): bool { - foreach($invoices as $invoice){ + foreach ($invoices as $invoice) { $invoice->service()->markSent(); - - if(!$invoice->isPayable()) + + if (!$invoice->isPayable()) return false; } @@ -168,12 +166,12 @@ class MatchBankTransactions implements ShouldQueue $this->bt = BankTransaction::find($input['id']); - if(!$this->bt || $this->bt->status_id == BankTransaction::STATUS_CONVERTED) + if (!$this->bt || $this->bt->status_id == BankTransaction::STATUS_CONVERTED) return $this; $expense = Expense::withTrashed()->find($input['expense_id']); - if($expense && !$expense->transaction_id) { + if ($expense && !$expense->transaction_id) { $expense->transaction_id = $this->bt->id; $expense->save(); @@ -187,7 +185,7 @@ class MatchBankTransactions implements ShouldQueue $this->bts->push($this->bt->id); } - + return $this; } @@ -197,12 +195,12 @@ class MatchBankTransactions implements ShouldQueue $this->bt = BankTransaction::find($input['id']); - if(!$this->bt || $this->bt->status_id == BankTransaction::STATUS_CONVERTED) + if (!$this->bt || $this->bt->status_id == BankTransaction::STATUS_CONVERTED) return $this; $payment = Payment::withTrashed()->find($input['payment_id']); - - if($payment && !$payment->transaction_id) { + + if ($payment && !$payment->transaction_id) { $payment->transaction_id = $this->bt->id; $payment->save(); @@ -218,18 +216,18 @@ class MatchBankTransactions implements ShouldQueue return $this; } - private function matchInvoicePayment($input) :self - { + private function matchInvoicePayment($input): self + { $this->bt = BankTransaction::find($input['id']); - if(!$this->bt || $this->bt->status_id == BankTransaction::STATUS_CONVERTED) - return $this; + if (!$this->bt || $this->bt->status_id == BankTransaction::STATUS_CONVERTED) + return $this; $_invoices = Invoice::withTrashed()->find($this->getInvoices($input['invoice_ids'])); - + $amount = $this->bt->amount; - if($_invoices && $this->checkPayable($_invoices)){ + if ($_invoices && $this->checkPayable($_invoices)) { $this->createPayment($_invoices, $amount); @@ -240,13 +238,13 @@ class MatchBankTransactions implements ShouldQueue return $this; } - private function matchExpense($input) :self - { + private function matchExpense($input): self + { //if there is a category id, pull it from Yodlee and insert - or just reuse!! $this->bt = BankTransaction::find($input['id']); - if(!$this->bt || $this->bt->status_id == BankTransaction::STATUS_CONVERTED) - return $this; + if (!$this->bt || $this->bt->status_id == BankTransaction::STATUS_CONVERTED) + return $this; $expense = ExpenseFactory::create($this->bt->company_id, $this->bt->user_id); $expense->category_id = $this->resolveCategory($input); @@ -258,7 +256,7 @@ class MatchBankTransactions implements ShouldQueue $expense->transaction_reference = $this->bt->description; $expense->transaction_id = $this->bt->id; - if(array_key_exists('vendor_id', $input)) + if (array_key_exists('vendor_id', $input)) $expense->vendor_id = $input['vendor_id']; $expense->invoice_documents = $this->company->invoice_expense_documents; @@ -267,9 +265,9 @@ class MatchBankTransactions implements ShouldQueue $this->bt->expense_id = $expense->id; - if(array_key_exists('vendor_id', $input)) + if (array_key_exists('vendor_id', $input)) $this->bt->vendor_id = $input['vendor_id']; - + $this->bt->status_id = BankTransaction::STATUS_CONVERTED; $this->bt->save(); @@ -278,52 +276,48 @@ class MatchBankTransactions implements ShouldQueue return $this; } - private function createPayment($invoices, float $amount) :void + private function createPayment($invoices, float $amount): void { $this->available_balance = $amount; - \DB::connection(config('database.default'))->transaction(function () use($invoices) { + \DB::connection(config('database.default'))->transaction(function () use ($invoices) { + + $invoices->each(function ($invoice) use ($invoices) { - $invoices->each(function ($invoice) use ($invoices){ - $this->invoice = Invoice::withTrashed()->where('id', $invoice->id)->lockForUpdate()->first(); - $_amount = false; + $_amount = false; - if(floatval($this->invoice->balance) < floatval($this->available_balance) && $this->available_balance > 0) - { - $_amount = $this->invoice->balance; - $this->applied_amount += $this->invoice->balance; - $this->available_balance = $this->available_balance - $this->invoice->balance; - } - elseif(floatval($this->invoice->balance) >= floatval($this->available_balance) && $this->available_balance > 0) - { - $_amount = $this->available_balance; - $this->applied_amount += $this->available_balance; - $this->available_balance = 0; - } + if (floatval($this->invoice->balance) < floatval($this->available_balance) && $this->available_balance > 0) { + $_amount = $this->invoice->balance; + $this->applied_amount += $this->invoice->balance; + $this->available_balance = $this->available_balance - $this->invoice->balance; + } elseif (floatval($this->invoice->balance) >= floatval($this->available_balance) && $this->available_balance > 0) { + $_amount = $this->available_balance; + $this->applied_amount += $this->available_balance; + $this->available_balance = 0; + } - if($_amount) - { + if ($_amount) { - $this->attachable_invoices[] = ['id' => $this->invoice->id, 'amount' => $_amount]; + $this->attachable_invoices[] = ['id' => $this->invoice->id, 'amount' => $_amount]; - $this->invoice - ->service() - ->setExchangeRate() - ->updateBalance($_amount * -1) - ->updatePaidToDate($_amount) - ->setCalculatedStatus() - ->save(); - } + $this->invoice + ->service() + ->setExchangeRate() + ->updateBalance($_amount * -1) + ->updatePaidToDate($_amount) + ->setCalculatedStatus() + ->save(); + } - }); + }); }, 2); - if(!$this->invoice) + if (!$this->invoice) return; - + /* Create Payment */ $payment = PaymentFactory::create($this->invoice->company_id, $this->invoice->user_id); @@ -336,7 +330,7 @@ class MatchBankTransactions implements ShouldQueue $payment->currency_id = $this->bt->currency_id; $payment->is_manual = false; $payment->date = $this->bt->date ? Carbon::parse($this->bt->date) : now(); - + /* Bank Transfer! */ $payment_type_id = 1; @@ -344,15 +338,14 @@ class MatchBankTransactions implements ShouldQueue $payment->saveQuietly(); $payment->service()->applyNumber()->save(); - - if($payment->client->getSetting('send_email_on_mark_paid')) + + if ($payment->client->getSetting('send_email_on_mark_paid')) $payment->service()->sendEmail(); $this->setExchangeRate($payment); /* Create a payment relationship to the invoice entity */ - foreach($this->attachable_invoices as $attachable_invoice) - { + foreach ($this->attachable_invoices as $attachable_invoice) { $payment->invoices()->attach($attachable_invoice['id'], [ 'amount' => $attachable_invoice['amount'], @@ -365,24 +358,24 @@ class MatchBankTransactions implements ShouldQueue $this->invoice->next_send_date = null; $this->invoice - ->service() - ->applyNumber() - ->touchPdf() - ->save(); + ->service() + ->applyNumber() + ->touchPdf() + ->save(); $payment->ledger() - ->updatePaymentBalance($amount * -1); + ->updatePaymentBalance($amount * -1); $this->invoice - ->client - ->service() - ->updateBalanceAndPaidToDate($this->applied_amount*-1, $amount) - ->save(); + ->client + ->service() + ->updateBalanceAndPaidToDate($this->applied_amount * -1, $amount) + ->save(); $this->invoice = $this->invoice - ->service() - ->workFlow() - ->save(); + ->service() + ->workFlow() + ->save(); /* Update Invoice balance */ event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); @@ -394,24 +387,23 @@ class MatchBankTransactions implements ShouldQueue $this->bt->save(); } - private function resolveCategory($input) :?int + private function resolveCategory($input): ?int { - if(array_key_exists('ninja_category_id', $input) && (int)$input['ninja_category_id'] > 1){ + if (array_key_exists('ninja_category_id', $input) && (int) $input['ninja_category_id'] > 1) { $this->bt->ninja_category_id = $input['ninja_category_id']; $this->bt->save(); - return (int)$input['ninja_category_id']; + return (int) $input['ninja_category_id']; } $category = $this->categories->firstWhere('highLevelCategoryId', $this->bt->category_id); $ec = ExpenseCategory::where('company_id', $this->bt->company_id)->where('bank_category_id', $this->bt->category_id)->first(); - if($ec) + if ($ec) return $ec->id; - if($category) - { + if ($category) { $ec = ExpenseCategoryFactory::create($this->bt->company_id, $this->bt->user_id); $ec->bank_category_id = $this->bt->category_id; $ec->name = $category->highLevelCategoryName; @@ -419,7 +411,7 @@ class MatchBankTransactions implements ShouldQueue return $ec->id; } - + return null; } @@ -452,4 +444,4 @@ class MatchBankTransactions implements ShouldQueue -} \ No newline at end of file +} diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php new file mode 100644 index 000000000000..a71d64943858 --- /dev/null +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -0,0 +1,166 @@ +account = $account; + $this->bank_integration = $bank_integration; + $this->from_date = $bank_integration->from_date; + $this->company = $this->bank_integration->company; + + if ($this->bank_integration->integration_type != BankIntegration::INTEGRATION_TYPE_NORDIGEN) + throw new \Exception("Invalid BankIntegration Type"); + + } + + /** + * Execute the job. + * + * + * @return void + */ + public function handle() + { + + set_time_limit(0); + + //Loop through everything until we are up to date + $this->from_date = $this->from_date ?: '2021-01-01'; + + do { + + try { + $this->processTransactions(); + } catch (\Exception $e) { + nlog("{$this->account->bank_integration_nordigen_client_id} - exited abnormally => " . $e->getMessage()); + return; + } + + } + while ($this->stop_loop); + + BankMatchingService::dispatch($this->company->id, $this->company->db); + + } + + + private function processTransactions() + { + + $nordigen = new Nordigen($this->account->bank_integration_nordigen_client_id, $this->account->bank_integration_nordigen_client_secret); // TODO: maybe implement credentials + + if (!$nordigen->isAccountActive($this->bank_integration->bank_account_id)) { + $this->bank_integration->disabled_upstream = true; + $this->bank_integration->save(); + $this->stop_loop = false; + return; + } + + $data = [ + 'top' => 500, + 'fromDate' => $this->from_date, + 'accountId' => $this->bank_integration->bank_account_id, + 'skip' => $this->skip, + ]; + + //Get transaction count object + $transactions = $nordigen->getTransactions($this->bank_integration->bank_account_id, $this->from_date); + + //Get int count + $count = sizeof($transactions->transactions->booked); + + //if no transactions, update the from_date and move on + if (count($transactions) == 0) { + + $this->bank_integration->from_date = now()->subDays(2); + $this->bank_integration->disabled_upstream = false; + $this->bank_integration->save(); + $this->stop_loop = false; + return; + } + + //Harvest the company + + MultiDB::setDb($this->company->db); + + /*Get the user */ + $user_id = $this->company->owner()->id; + + /* Unguard the model to perform batch inserts */ + BankTransaction::unguard(); + + $now = now(); + + foreach ($transactions as $transaction) { + + if (BankTransaction::where('transaction_id', $transaction['transaction_id'])->where('company_id', $this->company->id)->withTrashed()->exists()) + continue; + + //this should be much faster to insert than using ::create() + $bt = \DB::table('bank_transactions')->insert( + array_merge($transaction, [ + 'company_id' => $this->company->id, + 'user_id' => $user_id, + 'bank_integration_id' => $this->bank_integration->id, + 'created_at' => $now, + 'updated_at' => $now, + ]) + ); + + } + + + $this->skip = $this->skip + 500; + + if ($count < 500) { + $this->stop_loop = false; + $this->bank_integration->from_date = now()->subDays(2); + $this->bank_integration->save(); + + } + + } +} diff --git a/app/Jobs/Bank/ProcessBankTransactions.php b/app/Jobs/Bank/ProcessBankTransactionsYodlee.php similarity index 71% rename from app/Jobs/Bank/ProcessBankTransactions.php rename to app/Jobs/Bank/ProcessBankTransactionsYodlee.php index 74d996f0fd14..e2d3acda54df 100644 --- a/app/Jobs/Bank/ProcessBankTransactions.php +++ b/app/Jobs/Bank/ProcessBankTransactionsYodlee.php @@ -11,8 +11,10 @@ namespace App\Jobs\Bank; +use App\Helpers\Bank\Yodlee\Nordigen; use App\Helpers\Bank\Yodlee\Yodlee; use App\Libraries\MultiDB; +use App\Models\Account; use App\Models\BankIntegration; use App\Models\BankTransaction; use App\Models\Company; @@ -24,11 +26,11 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Carbon; -class ProcessBankTransactions implements ShouldQueue +class ProcessBankTransactionsYodlee implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - private string $bank_integration_account_id; + private Account $account; private BankIntegration $bank_integration; @@ -43,13 +45,16 @@ class ProcessBankTransactions implements ShouldQueue /** * Create a new job instance. */ - public function __construct(string $bank_integration_account_id, BankIntegration $bank_integration) + public function __construct(Account $account, BankIntegration $bank_integration) { - $this->bank_integration_account_id = $bank_integration_account_id; + $this->account = $account; $this->bank_integration = $bank_integration; $this->from_date = $bank_integration->from_date; $this->company = $this->bank_integration->company; + if ($this->bank_integration->integration_type != BankIntegration::INTEGRATION_TYPE_YODLEE) + throw new \Exception("Invalid BankIntegration Type"); + } /** @@ -66,18 +71,17 @@ class ProcessBankTransactions implements ShouldQueue //Loop through everything until we are up to date $this->from_date = $this->from_date ?: '2021-01-01'; - do{ + do { try { $this->processTransactions(); - } - catch(\Exception $e) { - nlog("{$this->bank_integration_account_id} - exited abnormally => ". $e->getMessage()); + } catch (\Exception $e) { + nlog("{$this->account->bank_integration_yodlee_account_id} - exited abnormally => " . $e->getMessage()); return; } } - while($this->stop_loop); + while ($this->stop_loop); BankMatchingService::dispatch($this->company->id, $this->company->db); @@ -87,14 +91,13 @@ class ProcessBankTransactions implements ShouldQueue private function processTransactions() { - $yodlee = new Yodlee($this->bank_integration_account_id); + $yodlee = new Yodlee($this->account->bank_integration_yodlee_account_id); - if(!$yodlee->getAccount($this->bank_integration->bank_account_id)) - { - $this->bank_integration->disabled_upstream = true; - $this->bank_integration->save(); - $this->stop_loop = false; - return; + if (!$yodlee->getAccount($this->bank_integration->bank_account_id)) { + $this->bank_integration->disabled_upstream = true; + $this->bank_integration->save(); + $this->stop_loop = false; + return; } $data = [ @@ -111,10 +114,10 @@ class ProcessBankTransactions implements ShouldQueue $count = $transaction_count->transaction->TOTAL->count; //get transactions array - $transactions = $yodlee->getTransactions($data); + $transactions = $yodlee->getTransactions($data); //if no transactions, update the from_date and move on - if(count($transactions) == 0){ + if (count($transactions) == 0) { $this->bank_integration->from_date = now()->subDays(2); $this->bank_integration->disabled_upstream = false; @@ -129,21 +132,20 @@ class ProcessBankTransactions implements ShouldQueue /*Get the user */ $user_id = $this->company->owner()->id; - + /* Unguard the model to perform batch inserts */ BankTransaction::unguard(); $now = now(); - - foreach($transactions as $transaction) - { - if(BankTransaction::where('transaction_id', $transaction['transaction_id'])->where('company_id', $this->company->id)->withTrashed()->exists()) + foreach ($transactions as $transaction) { + + if (BankTransaction::where('transaction_id', $transaction['transaction_id'])->where('company_id', $this->company->id)->withTrashed()->exists()) continue; //this should be much faster to insert than using ::create() $bt = \DB::table('bank_transactions')->insert( - array_merge($transaction,[ + array_merge($transaction, [ 'company_id' => $this->company->id, 'user_id' => $user_id, 'bank_integration_id' => $this->bank_integration->id, @@ -157,7 +159,7 @@ class ProcessBankTransactions implements ShouldQueue $this->skip = $this->skip + 500; - if($count < 500){ + if ($count < 500) { $this->stop_loop = false; $this->bank_integration->from_date = now()->subDays(2); $this->bank_integration->save(); @@ -166,4 +168,4 @@ class ProcessBankTransactions implements ShouldQueue } -} \ No newline at end of file +} diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php index 370fef9e7b86..8840661ea706 100644 --- a/app/Jobs/Ninja/BankTransactionSync.php +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -11,9 +11,11 @@ namespace App\Jobs\Ninja; -use App\Jobs\Bank\ProcessBankTransactions; +use App\Jobs\Bank\ProcessBankTransactionsYodlee; +use App\Jobs\Bank\ProcessBankTransactionsNordigen; use App\Libraries\MultiDB; use App\Models\Account; +use App\Models\BankIntegration; use App\Utils\Ninja; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -46,28 +48,38 @@ class BankTransactionSync implements ShouldQueue { //multiDB environment, need to - foreach (MultiDB::$dbs as $db) - { + foreach (MultiDB::$dbs as $db) { MultiDB::setDB($db); - nlog("syncing transactions"); + nlog("syncing transactions - yodlee"); - $a = Account::with('bank_integrations')->whereNotNull('bank_integration_account_id')->cursor()->each(function ($account){ + Account::with('bank_integrations')->whereNotNull('bank_integration_yodlee_account_id')->cursor()->each(function ($account) { - // $queue = Ninja::isHosted() ? 'bank' : 'default'; + if ($account->isPaid() && $account->plan == 'enterprise') { - if($account->isPaid() && $account->plan == 'enterprise') - { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->andWhere('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { - $account->bank_integrations()->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account){ - - (new ProcessBankTransactions($account->bank_integration_account_id, $bank_integration))->handle(); + (new ProcessBankTransactionsYodlee($account, $bank_integration))->handle(); - }); + }); - } + } + + }); + + 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->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->andWhere('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { + + (new ProcessBankTransactionsNordigen($account, $bank_integration))->handle(); }); + + }); + + nlog("syncing transactions - done"); } } diff --git a/app/Models/Account.php b/app/Models/Account.php index 2dbd6373dfa2..bc36fad08282 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -60,6 +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', ]; /** @@ -156,7 +158,7 @@ class Account extends BaseModel public function getPlan() { - if(Carbon::parse($this->plan_expires)->lt(now())) + if (Carbon::parse($this->plan_expires)->lt(now())) return ''; return $this->plan ?: ''; @@ -165,7 +167,7 @@ class Account extends BaseModel public function hasFeature($feature) { $plan_details = $this->getPlanDetails(); - $self_host = ! Ninja::isNinja(); + $self_host = !Ninja::isNinja(); switch ($feature) { @@ -187,35 +189,35 @@ class Account extends BaseModel case self::FEATURE_API: case self::FEATURE_CLIENT_PORTAL_PASSWORD: case self::FEATURE_CUSTOM_URL: - return $self_host || ! empty($plan_details); + return $self_host || !empty($plan_details); // Pro; No trial allowed, unless they're trialing enterprise with an active pro plan case self::FEATURE_MORE_CLIENTS: - return $self_host || ! empty($plan_details) && (! $plan_details['trial'] || ! empty($this->getPlanDetails(false, false))); + return $self_host || !empty($plan_details) && (!$plan_details['trial'] || !empty($this->getPlanDetails(false, false))); // White Label case self::FEATURE_WHITE_LABEL: - if (! $self_host && $plan_details && ! $plan_details['expires']) { + if (!$self_host && $plan_details && !$plan_details['expires']) { return false; } - // Fallthrough - // no break + // Fallthrough + // no break case self::FEATURE_REMOVE_CREATED_BY: - return ! empty($plan_details); // A plan is required even for self-hosted users + return !empty($plan_details); // A plan is required even for self-hosted users // Enterprise; No Trial allowed; grandfathered for old pro users - case self::FEATURE_USERS:// Grandfathered for old Pro users + case self::FEATURE_USERS: // Grandfathered for old Pro users if ($plan_details && $plan_details['trial']) { // Do they have a non-trial plan? $plan_details = $this->getPlanDetails(false, false); } - return $self_host || ! empty($plan_details) && ($plan_details['plan'] == self::PLAN_ENTERPRISE); + return $self_host || !empty($plan_details) && ($plan_details['plan'] == self::PLAN_ENTERPRISE); // Enterprise; No Trial allowed case self::FEATURE_DOCUMENTS: case self::FEATURE_USER_PERMISSIONS: - return $self_host || ! empty($plan_details) && $plan_details['plan'] == self::PLAN_ENTERPRISE && ! $plan_details['trial']; + return $self_host || !empty($plan_details) && $plan_details['plan'] == self::PLAN_ENTERPRISE && !$plan_details['trial']; default: return false; @@ -224,16 +226,16 @@ class Account extends BaseModel public function isPaid() { - return Ninja::isNinja() ? ($this->isPaidHostedClient() && ! $this->isTrial()) : $this->hasFeature(self::FEATURE_WHITE_LABEL); + return Ninja::isNinja() ? ($this->isPaidHostedClient() && !$this->isTrial()) : $this->hasFeature(self::FEATURE_WHITE_LABEL); } public function isPaidHostedClient() { - if (! Ninja::isNinja()) { + if (!Ninja::isNinja()) { return false; } - if($this->plan_expires && Carbon::parse($this->plan_expires)->lt(now())) + if ($this->plan_expires && Carbon::parse($this->plan_expires)->lt(now())) return false; return $this->plan == 'pro' || $this->plan == 'enterprise'; @@ -241,11 +243,11 @@ class Account extends BaseModel public function isFreeHostedClient() { - if (! Ninja::isNinja()) { + if (!Ninja::isNinja()) { return false; } - if($this->plan_expires && Carbon::parse($this->plan_expires)->lt(now())) + if ($this->plan_expires && Carbon::parse($this->plan_expires)->lt(now())) return true; return $this->plan == 'free' || is_null($this->plan) || empty($this->plan); @@ -253,7 +255,7 @@ class Account extends BaseModel public function isEnterpriseClient() { - if (! Ninja::isNinja()) { + if (!Ninja::isNinja()) { return false; } @@ -262,7 +264,7 @@ class Account extends BaseModel public function isTrial() { - if (! Ninja::isNinja()) { + if (!Ninja::isNinja()) { return false; } @@ -273,7 +275,7 @@ class Account extends BaseModel public function startTrial($plan) { - if (! Ninja::isNinja()) { + if (!Ninja::isNinja()) { return; } @@ -292,22 +294,22 @@ class Account extends BaseModel $price = $this->plan_price; $trial_plan = $this->trial_plan; - if ((! $plan || $plan == self::PLAN_FREE) && (! $trial_plan || ! $include_trial)) { + if ((!$plan || $plan == self::PLAN_FREE) && (!$trial_plan || !$include_trial)) { return null; } $trial_active = false; //14 day trial - $duration = 60*60*24*14; + $duration = 60 * 60 * 24 * 14; if ($trial_plan && $include_trial) { $trial_started = $this->trial_started; $trial_expires = Carbon::parse($this->trial_started)->addSeconds($duration); - if($trial_expires->greaterThan(now())){ + if ($trial_expires->greaterThan(now())) { $trial_active = true; - } + } } @@ -324,23 +326,23 @@ class Account extends BaseModel } } - if (! $include_inactive && ! $plan_active && ! $trial_active) { + if (!$include_inactive && !$plan_active && !$trial_active) { return null; } // Should we show plan details or trial details? - if (($plan && ! $trial_plan) || ! $include_trial) { + if (($plan && !$trial_plan) || !$include_trial) { $use_plan = true; - } elseif (! $plan && $trial_plan) { + } elseif (!$plan && $trial_plan) { $use_plan = false; } else { // There is both a plan and a trial - if (! empty($plan_active) && empty($trial_active)) { + if (!empty($plan_active) && empty($trial_active)) { $use_plan = true; - } elseif (empty($plan_active) && ! empty($trial_active)) { + } elseif (empty($plan_active) && !empty($trial_active)) { $use_plan = false; - } elseif (! empty($plan_active) && ! empty($trial_active)) { + } elseif (!empty($plan_active) && !empty($trial_active)) { // Both are active; use whichever is a better plan if ($plan == self::PLAN_ENTERPRISE) { $use_plan = true; @@ -385,20 +387,19 @@ class Account extends BaseModel public function getDailyEmailLimit() { - if($this->is_flagged) + if ($this->is_flagged) return 0; - if(Carbon::createFromTimestamp($this->created_at)->diffInWeeks() == 0) + if (Carbon::createFromTimestamp($this->created_at)->diffInWeeks() == 0) return 20; - if(Carbon::createFromTimestamp($this->created_at)->diffInWeeks() <= 2 && !$this->payment_id) + if (Carbon::createFromTimestamp($this->created_at)->diffInWeeks() <= 2 && !$this->payment_id) return 20; - if($this->isPaid()){ + if ($this->isPaid()) { $limit = $this->paid_plan_email_quota; $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 50; - } - else{ + } else { $limit = $this->free_plan_email_quota; $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 10; } @@ -408,21 +409,21 @@ class Account extends BaseModel public function emailsSent() { - if(is_null(Cache::get($this->key))) + if (is_null(Cache::get($this->key))) return 0; return Cache::get($this->key); - } + } - public function emailQuotaExceeded() :bool + public function emailQuotaExceeded(): bool { - if(is_null(Cache::get($this->key))) + if (is_null(Cache::get($this->key))) return false; try { - if(Cache::get($this->key) > $this->getDailyEmailLimit()) { + if (Cache::get($this->key) > $this->getDailyEmailLimit()) { - if(is_null(Cache::get("throttle_notified:{$this->key}"))) { + if (is_null(Cache::get("throttle_notified:{$this->key}"))) { App::forgetInstance('translator'); $t = app('translator'); @@ -437,32 +438,31 @@ class Account extends BaseModel Cache::put("throttle_notified:{$this->key}", true, 60 * 24); - if(config('ninja.notification.slack')) + if (config('ninja.notification.slack')) $this->companies()->first()->notification(new EmailQuotaNotification($this))->ninja(); } return true; } - } - catch(\Exception $e){ + } catch (\Exception $e) { \Sentry\captureMessage("I encountered an error with email quotas for account {$this->key} - defaulting to SEND"); } return false; } - public function gmailCredentialNotification() :bool + public function gmailCredentialNotification(): bool { nlog("checking if gmail credential notification has already been sent"); - if(is_null(Cache::get($this->key))) + if (is_null(Cache::get($this->key))) return false; nlog("Sending notification"); - + try { - if(is_null(Cache::get("gmail_credentials_notified:{$this->key}"))) { + if (is_null(Cache::get("gmail_credentials_notified:{$this->key}"))) { App::forgetInstance('translator'); $t = app('translator'); @@ -477,14 +477,13 @@ class Account extends BaseModel Cache::put("gmail_credentials_notified:{$this->key}", true, 60 * 24); - if(config('ninja.notification.slack')) + if (config('ninja.notification.slack')) $this->companies()->first()->notification(new GmailCredentialNotification($this))->ninja(); } return true; - - } - catch(\Exception $e){ + + } catch (\Exception $e) { \Sentry\captureMessage("I encountered an error with sending with gmail for account {$this->key}"); } @@ -506,17 +505,18 @@ class Account extends BaseModel public function getTrialDays() { - if($this->payment_id) + if ($this->payment_id) return 0; $plan_expires = Carbon::parse($this->plan_expires); - if(!$this->payment_id && $plan_expires->gt(now())){ + if (!$this->payment_id && $plan_expires->gt(now())) { $diff = $plan_expires->diffInDays(); - - if($diff > 14); - return 0; + + if ($diff > 14) + ; + return 0; return $diff; } diff --git a/app/Models/BankIntegration.php b/app/Models/BankIntegration.php index 517382960e57..2d85f3c1e3a3 100644 --- a/app/Models/BankIntegration.php +++ b/app/Models/BankIntegration.php @@ -22,7 +22,6 @@ class BankIntegration extends BaseModel use Excludable; protected $fillable = [ - 'integration_type', 'bank_account_name', 'provider_name', 'bank_account_number', diff --git a/database/factories/BankIntegrationFactory.php b/database/factories/BankIntegrationFactory.php index ad588f872a84..9d04cdfc56f8 100644 --- a/database/factories/BankIntegrationFactory.php +++ b/database/factories/BankIntegrationFactory.php @@ -12,6 +12,7 @@ namespace Database\Factories; use App\Models\Account; +use App\Models\BankIntegration; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; @@ -25,6 +26,7 @@ class BankIntegrationFactory extends Factory public function definition() { return [ + 'integration_type' => BankIntegration::INTEGRATION_TYPE_NONE, 'provider_name' => $this->faker->company(), 'provider_id' => 1, 'bank_account_name' => $this->faker->catchPhrase(), @@ -38,4 +40,4 @@ class BankIntegrationFactory extends Factory 'is_deleted' => false, ]; } -} \ No newline at end of file +} diff --git a/database/migrations/2023_11_26_082959_add_bank_integration_id.php b/database/migrations/2023_11_26_082959_add_bank_integration_id.php index 4d7ba978ec08..8895b7729926 100644 --- a/database/migrations/2023_11_26_082959_add_bank_integration_id.php +++ b/database/migrations/2023_11_26_082959_add_bank_integration_id.php @@ -1,5 +1,6 @@ string('integration_type')->nullable(); + Schema::table('bank_integrations', function (Blueprint $table) { + $table->string('integration_type')->default(BankIntegration::INTEGRATION_TYPE_NONE); }); // migrate old account to be used with yodlee - BankIntegration::query()->whereNull('integration_type')->cursor()->each(function ($bank_integration) { + BankIntegration::query()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NONE)->whereNotNull('account_id')->cursor()->each(function ($bank_integration) { $bank_integration->integration_type = BankIntegration::INTEGRATION_TYPE_YODLEE; $bank_integration->save(); }); + + // MAYBE migration of account->bank_account_id etc + Schema::table('accounts', function (Blueprint $table) { + $table->renameColumn('bank_integration_account_id', 'bank_integration_yodlee_account_id'); + $table->string('bank_integration_nordigen_secret_id')->nullable(); + $table->string('bank_integration_nordigen_secret_key')->nullable(); + }); } /** @@ -31,8 +39,6 @@ return new class extends Migration { */ public function down() { - Schema::table('bank_integration', function (Blueprint $table) { - $table->dropColumn('integration_id'); - }); + // } }; diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 490236f79ed8..460e977d7617 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,49 +1,49 @@ { - "/js/app.js": "/js/app.js", - "/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js", - "/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js", - "/js/clients/payments/forte-credit-card-payment.js": "/js/clients/payments/forte-credit-card-payment.js", - "/js/clients/payments/forte-ach-payment.js": "/js/clients/payments/forte-ach-payment.js", - "/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js", - "/js/clients/payments/stripe-klarna.js": "/js/clients/payments/stripe-klarna.js", - "/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js", - "/js/clients/purchase_orders/action-selectors.js": "/js/clients/purchase_orders/action-selectors.js", - "/js/clients/purchase_orders/accept.js": "/js/clients/purchase_orders/accept.js", - "/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js", - "/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js", - "/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js", - "/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js", - "/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js", - "/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js", - "/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js", - "/js/setup/setup.js": "/js/setup/setup.js", - "/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js", - "/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js", - "/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js", - "/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js", - "/js/clients/payments/braintree-credit-card.js": "/js/clients/payments/braintree-credit-card.js", - "/js/clients/payments/braintree-paypal.js": "/js/clients/payments/braintree-paypal.js", - "/js/clients/payments/wepay-credit-card.js": "/js/clients/payments/wepay-credit-card.js", - "/js/clients/payment_methods/wepay-bank-account.js": "/js/clients/payment_methods/wepay-bank-account.js", - "/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js", - "/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js", - "/js/clients/payments/eway-credit-card.js": "/js/clients/payments/eway-credit-card.js", - "/js/clients/payment_methods/braintree-ach.js": "/js/clients/payment_methods/braintree-ach.js", - "/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js", - "/js/clients/statements/view.js": "/js/clients/statements/view.js", - "/js/clients/payments/razorpay-aio.js": "/js/clients/payments/razorpay-aio.js", - "/js/clients/payments/stripe-sepa.js": "/js/clients/payments/stripe-sepa.js", - "/js/clients/payment_methods/authorize-checkout-card.js": "/js/clients/payment_methods/authorize-checkout-card.js", - "/js/clients/payments/stripe-giropay.js": "/js/clients/payments/stripe-giropay.js", - "/js/clients/payments/stripe-acss.js": "/js/clients/payments/stripe-acss.js", - "/js/clients/payments/stripe-bancontact.js": "/js/clients/payments/stripe-bancontact.js", - "/js/clients/payments/stripe-becs.js": "/js/clients/payments/stripe-becs.js", - "/js/clients/payments/stripe-eps.js": "/js/clients/payments/stripe-eps.js", - "/js/clients/payments/stripe-ideal.js": "/js/clients/payments/stripe-ideal.js", - "/js/clients/payments/stripe-przelewy24.js": "/js/clients/payments/stripe-przelewy24.js", - "/js/clients/payments/stripe-browserpay.js": "/js/clients/payments/stripe-browserpay.js", - "/js/clients/payments/stripe-fpx.js": "/js/clients/payments/stripe-fpx.js", - "/css/app.css": "/css/app.css", - "/css/card-js.min.css": "/css/card-js.min.css", - "/vendor/clipboard.min.js": "/vendor/clipboard.min.js" + "/js/app.js": "/js/app.js?id=7b6124b74168ccb1cc7da22f7a2bc9ed", + "/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=b6723e0b8ea33f1f50617fa5f289a9d3", + "/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=faf4828cc6b3b73b69c53d3046661884", + "/js/clients/payments/forte-credit-card-payment.js": "/js/clients/payments/forte-credit-card-payment.js?id=1ecc2e5ed666e5c6fae7830b5ab5c77a", + "/js/clients/payments/forte-ach-payment.js": "/js/clients/payments/forte-ach-payment.js?id=04cadfa45e77d49e8253b9ffbc000767", + "/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=0c520b9a787b6b9031300330e060a7f5", + "/js/clients/payments/stripe-klarna.js": "/js/clients/payments/stripe-klarna.js?id=2529dac592a6c34028addedf1198bcf2", + "/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=f2b6ebf3c1da387c6268d6e0a28b8c65", + "/js/clients/purchase_orders/action-selectors.js": "/js/clients/purchase_orders/action-selectors.js?id=f8e554acde01ad91784e1046ef4ecdb4", + "/js/clients/purchase_orders/accept.js": "/js/clients/purchase_orders/accept.js?id=9bb483a89a887f753e49c0b635d6276a", + "/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=752e2bb6390f1a422e31868cf2a2bf67", + "/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js?id=6b3381f59d2ef53cdd85a2435f54c2c3", + "/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=018ecad3a1bcc1ecc47f76754a573ff2", + "/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=eea8dc5452e299f2e4148f5a0e168613", + "/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=07a94a1d7649b1bb2f6fdfe35b0cf4a1", + "/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=1e58e219878ce3f3ee4d313346ad5f68", + "/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=6e7c8ab039a239727317ae8622de10db", + "/js/setup/setup.js": "/js/setup/setup.js?id=cba079b7c249f2aa73731e1fa952d646", + "/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=cf50b5ba1fcd1d184bf0c10d710672c8", + "/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=682de6347049b32c9488f39c78a68ace", + "/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=ecfc8b8db2b8aec42ca295b5e6c75974", + "/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=e1c0599d6f7dc163b549a6df0b3490b4", + "/js/clients/payments/braintree-credit-card.js": "/js/clients/payments/braintree-credit-card.js?id=e051c84bfaf6b63a4971181e3ece6ecb", + "/js/clients/payments/braintree-paypal.js": "/js/clients/payments/braintree-paypal.js?id=6ff0f8ea53b30fe242706586399e61e8", + "/js/clients/payments/wepay-credit-card.js": "/js/clients/payments/wepay-credit-card.js?id=a7c2aef52dfdb7e6bef25abbf5373917", + "/js/clients/payment_methods/wepay-bank-account.js": "/js/clients/payment_methods/wepay-bank-account.js?id=be64a69a5fdf374ba3af7030db1d5155", + "/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=3869bc6d80acc83f81d9afe8efaae728", + "/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js?id=dcebf12d3742e39c47676e2439426e6e", + "/js/clients/payments/eway-credit-card.js": "/js/clients/payments/eway-credit-card.js?id=27274d334aed0824ce4654fa22132f7f", + "/js/clients/payment_methods/braintree-ach.js": "/js/clients/payment_methods/braintree-ach.js?id=93f6f8c0a45cd46cd4d4c123f05ae9e7", + "/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=238e7001420a22b001856193689a1e70", + "/js/clients/statements/view.js": "/js/clients/statements/view.js?id=632aa120ab205dcc5807606a45844b4a", + "/js/clients/payments/razorpay-aio.js": "/js/clients/payments/razorpay-aio.js?id=df93901708dc49a732cbe0a11c8e6404", + "/js/clients/payments/stripe-sepa.js": "/js/clients/payments/stripe-sepa.js?id=77d4e397d193196e482af80737bff64a", + "/js/clients/payment_methods/authorize-checkout-card.js": "/js/clients/payment_methods/authorize-checkout-card.js?id=e58bdaeadf150e9fe8fa75c8540ae6c2", + "/js/clients/payments/stripe-giropay.js": "/js/clients/payments/stripe-giropay.js?id=9839796e7c08d6f4f372c03a8a5543f6", + "/js/clients/payments/stripe-acss.js": "/js/clients/payments/stripe-acss.js?id=4c3c5ee61948e8f49b174e1c1fae084c", + "/js/clients/payments/stripe-bancontact.js": "/js/clients/payments/stripe-bancontact.js?id=dfcd1f2f7080177c4dcbc58432bf4167", + "/js/clients/payments/stripe-becs.js": "/js/clients/payments/stripe-becs.js?id=c7ad959f7b79be68618d2937943aef95", + "/js/clients/payments/stripe-eps.js": "/js/clients/payments/stripe-eps.js?id=749cba1332a29baa444b37cee2ade2d7", + "/js/clients/payments/stripe-ideal.js": "/js/clients/payments/stripe-ideal.js?id=f0e2e00fa779a20967a2ea9489bf4fcb", + "/js/clients/payments/stripe-przelewy24.js": "/js/clients/payments/stripe-przelewy24.js?id=448b197a1d94b4408e130b5b8b1c2e53", + "/js/clients/payments/stripe-browserpay.js": "/js/clients/payments/stripe-browserpay.js?id=d0658f7d90db9869fe79a84851f91234", + "/js/clients/payments/stripe-fpx.js": "/js/clients/payments/stripe-fpx.js?id=62317369167d31654d18ecdb75ca5a45", + "/css/app.css": "/css/app.css?id=0cb847167b91d8db2ca50d30e0d691ae", + "/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ada60afcedcb7c", + "/vendor/clipboard.min.js": "/vendor/clipboard.min.js?id=15f52a1ee547f2bdd46e56747332ca2d" } diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index e586c71519e3..6133e51230b4 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -17,7 +17,7 @@ use App\Factory\BankTransactionFactory; use App\Helpers\Bank\Yodlee\Yodlee; use App\Helpers\Invoice\InvoiceSum; use App\Jobs\Bank\MatchBankTransactions; -use App\Jobs\Bank\ProcessBankTransactions; +use App\Jobs\Bank\ProcessBankTransactionsYodlee; use App\Models\BankIntegration; use App\Models\BankTransaction; use App\Models\Expense; @@ -38,10 +38,10 @@ class YodleeApiTest extends TestCase parent::setUp(); // if(!config('ninja.yodlee.client_id')) - $this->markTestSkipped('Skip test no Yodlee API credentials found'); + $this->markTestSkipped('Skip test no Yodlee API credentials found'); $this->makeTestData(); - + } public function testExpenseGenerationFromBankFeed() @@ -72,13 +72,13 @@ class YodleeApiTest extends TestCase $expense = Expense::where('transaction_reference', 'Fuel')->first(); $this->assertNotNull($expense); - $this->assertEquals(10, (int)$expense->amount); + $this->assertEquals(10, (int) $expense->amount); } public function testIncomeMatchingAndPaymentGeneration() { - $this->account->bank_integration_account_id = 'sbMem62e1e69547bfb2'; + $this->account->bank_integration_yodlee_account_id = 'sbMem62e1e69547bfb2'; $this->account->save(); $invoice = Invoice::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id]); @@ -117,7 +117,7 @@ class YodleeApiTest extends TestCase $bt->date = now()->format('Y-m-d'); $bt->transaction_id = 123456; $bt->save(); - + $data['transactions'][] = [ 'id' => $bt->id, 'invoice_ids' => $invoice->hashed_id @@ -130,7 +130,7 @@ class YodleeApiTest extends TestCase $this->assertNotNull($payment); - $this->assertEquals(10, (int)$payment->amount); + $this->assertEquals(10, (int) $payment->amount); $this->assertEquals(4, $payment->status_id); $this->assertEquals(1, $payment->invoices()->count()); @@ -148,7 +148,7 @@ class YodleeApiTest extends TestCase $transactions = $yodlee->getTransactionCategories(); - $this->assertTrue(property_exists($transactions,'transactionCategory')); + $this->assertTrue(property_exists($transactions, 'transactionCategory')); $t = collect($transactions->transactionCategory); @@ -160,17 +160,17 @@ class YodleeApiTest extends TestCase } -// public function testFunctionalMatching() + // public function testFunctionalMatching() // { -// $yodlee = new Yodlee('sbMem62e1e69547bfb1'); + // $yodlee = new Yodlee('sbMem62e1e69547bfb1'); -// $accounts = $yodlee->getAccounts(); + // $accounts = $yodlee->getAccounts(); -// foreach($accounts as $account) + // foreach($accounts as $account) // { -// if(!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $this->company->id)->exists()) + // if(!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $this->company->id)->exists()) // { // $bank_integration = new BankIntegration(); // $bank_integration->company_id = $this->company->id; @@ -186,42 +186,42 @@ class YodleeApiTest extends TestCase // $bank_integration->nickname = $account['nickname']; // $bank_integration->balance = $account['current_balance']; // $bank_integration->currency = $account['account_currency']; - -// $bank_integration->save(); -// ProcessBankTransactions::dispatchSync('sbMem62e1e69547bfb1', $bank_integration); + // $bank_integration->save(); -// } + // ProcessBankTransactionsYodlee::dispatchSync('sbMem62e1e69547bfb1', $bank_integration); + + // } // } -// $this->assertGreaterThan(0, BankIntegration::count()); + // $this->assertGreaterThan(0, BankIntegration::count()); // $this->assertGreaterThan(0, BankTransaction::count()); -// $this->invoice->company_id = $this->company->id; + // $this->invoice->company_id = $this->company->id; // $this->invoice->number = "XXXXXX8501"; // $this->invoice->save(); -// BankService::dispatchSync($this->company->id, $this->company->db); - -// $bt = BankTransaction::where('invoice_ids', $this->invoice->hashed_id)->first(); + // BankService::dispatchSync($this->company->id, $this->company->db); -// nlog(BankTransaction::where('company_id', $this->company->id)->pluck('invoice_ids')); + // $bt = BankTransaction::where('invoice_ids', $this->invoice->hashed_id)->first(); -// $this->assertNotNull($bt); + // nlog(BankTransaction::where('company_id', $this->company->id)->pluck('invoice_ids')); -// $this->assertEquals(BankTransaction::STATUS_MATCHED, $bt->status_id); + // $this->assertNotNull($bt); -// } + // $this->assertEquals(BankTransaction::STATUS_MATCHED, $bt->status_id); + + // } public function testDataMatching() { $transaction = collect([ - (object)[ + (object) [ 'description' => 'tinkertonkton' ], - (object)[ + (object) [ 'description' => 'spud' ], ]); @@ -242,10 +242,10 @@ class YodleeApiTest extends TestCase $transaction = collect([ - (object)[ + (object) [ 'description' => 'tinker and spice' ], - (object)[ + (object) [ 'description' => 'spud with water' ], ]); @@ -258,7 +258,7 @@ class YodleeApiTest extends TestCase $invoice = $transaction->first(function ($value, $key) { return str_contains($value->description, 'tinker'); - + }); $this->assertNotNull($invoice); @@ -286,179 +286,179 @@ class YodleeApiTest extends TestCase $this->assertNotNull($access_token); } -/** + /** - [transactionCategory] => Array - ( - [0] => stdClass Object - ( - [id] => 1 - [source] => SYSTEM - [classification] => PERSONAL - [category] => Uncategorized - [type] => UNCATEGORIZE - [highLevelCategoryId] => 10000017 - [highLevelCategoryName] => Uncategorized - [defaultCategoryName] => Uncategorized - [defaultHighLevelCategoryName] => Uncategorized - ) + [transactionCategory] => Array + ( + [0] => stdClass Object + ( + [id] => 1 + [source] => SYSTEM + [classification] => PERSONAL + [category] => Uncategorized + [type] => UNCATEGORIZE + [highLevelCategoryId] => 10000017 + [highLevelCategoryName] => Uncategorized + [defaultCategoryName] => Uncategorized + [defaultHighLevelCategoryName] => Uncategorized + ) - [1] => stdClass Object - ( - [id] => 2 - [source] => SYSTEM - [classification] => PERSONAL - [category] => Automotive/Fuel - [type] => EXPENSE - [detailCategory] => Array - ( - [0] => stdClass Object - ( - [id] => 1041 - [name] => Registration/Licensing - ) + [1] => stdClass Object + ( + [id] => 2 + [source] => SYSTEM + [classification] => PERSONAL + [category] => Automotive/Fuel + [type] => EXPENSE + [detailCategory] => Array + ( + [0] => stdClass Object + ( + [id] => 1041 + [name] => Registration/Licensing + ) - [1] => stdClass Object - ( - [id] => 1145 - [name] => Automotive - ) + [1] => stdClass Object + ( + [id] => 1145 + [name] => Automotive + ) - [2] => stdClass Object - ( - [id] => 1218 - [name] => Auto Fees/Penalties - ) + [2] => stdClass Object + ( + [id] => 1218 + [name] => Auto Fees/Penalties + ) - [3] => stdClass Object - ( - [id] => 1260 - [name] => Car Appraisers - ) + [3] => stdClass Object + ( + [id] => 1260 + [name] => Car Appraisers + ) - [4] => stdClass Object - ( - [id] => 1261 - [name] => Car Dealers - ) + [4] => stdClass Object + ( + [id] => 1261 + [name] => Car Dealers + ) - [5] => stdClass Object - ( - [id] => 1262 - [name] => Car Dealers and Leasing - ) + [5] => stdClass Object + ( + [id] => 1262 + [name] => Car Dealers and Leasing + ) - [6] => stdClass Object - ( - [id] => 1263 - [name] => Car Parts and Accessories - ) + [6] => stdClass Object + ( + [id] => 1263 + [name] => Car Parts and Accessories + ) - [7] => stdClass Object - ( - [id] => 1264 - [name] => Car Wash and Detail - ) + [7] => stdClass Object + ( + [id] => 1264 + [name] => Car Wash and Detail + ) - [8] => stdClass Object - ( - [id] => 1265 - [name] => Classic and Antique Car - ) + [8] => stdClass Object + ( + [id] => 1265 + [name] => Classic and Antique Car + ) - [9] => stdClass Object - ( - [id] => 1267 - [name] => Maintenance and Repair - ) + [9] => stdClass Object + ( + [id] => 1267 + [name] => Maintenance and Repair + ) - [10] => stdClass Object - ( - [id] => 1268 - [name] => Motorcycles/Mopeds/Scooters - ) + [10] => stdClass Object + ( + [id] => 1268 + [name] => Motorcycles/Mopeds/Scooters + ) - [11] => stdClass Object - ( - [id] => 1269 - [name] => Oil and Lube - ) + [11] => stdClass Object + ( + [id] => 1269 + [name] => Oil and Lube + ) - [12] => stdClass Object - ( - [id] => 1270 - [name] => Motorcycle Repair - ) + [12] => stdClass Object + ( + [id] => 1270 + [name] => Motorcycle Repair + ) - [13] => stdClass Object - ( - [id] => 1271 - [name] => RVs and Motor Homes - ) + [13] => stdClass Object + ( + [id] => 1271 + [name] => RVs and Motor Homes + ) - [14] => stdClass Object - ( - [id] => 1272 - [name] => Motorcycle Sales - ) + [14] => stdClass Object + ( + [id] => 1272 + [name] => Motorcycle Sales + ) - [15] => stdClass Object - ( - [id] => 1273 - [name] => Salvage Yards - ) + [15] => stdClass Object + ( + [id] => 1273 + [name] => Salvage Yards + ) - [16] => stdClass Object - ( - [id] => 1274 - [name] => Smog Check - ) + [16] => stdClass Object + ( + [id] => 1274 + [name] => Smog Check + ) - [17] => stdClass Object - ( - [id] => 1275 - [name] => Tires - ) + [17] => stdClass Object + ( + [id] => 1275 + [name] => Tires + ) - [18] => stdClass Object - ( - [id] => 1276 - [name] => Towing - ) + [18] => stdClass Object + ( + [id] => 1276 + [name] => Towing + ) - [19] => stdClass Object - ( - [id] => 1277 - [name] => Transmissions - ) + [19] => stdClass Object + ( + [id] => 1277 + [name] => Transmissions + ) - [20] => stdClass Object - ( - [id] => 1278 - [name] => Used Cars - ) + [20] => stdClass Object + ( + [id] => 1278 + [name] => Used Cars + ) - [21] => stdClass Object - ( - [id] => 1240 - [name] => e-Charging - ) + [21] => stdClass Object + ( + [id] => 1240 + [name] => e-Charging + ) - [22] => stdClass Object - ( - [id] => 1266 - [name] => Gas Stations - ) + [22] => stdClass Object + ( + [id] => 1266 + [name] => Gas Stations + ) - ) + ) - [highLevelCategoryId] => 10000003 - [highLevelCategoryName] => Automotive Expenses - [defaultCategoryName] => Automotive Expenses - [defaultHighLevelCategoryName] => Automotive Expenses - ) + [highLevelCategoryId] => 10000003 + [highLevelCategoryName] => Automotive Expenses + [defaultCategoryName] => Automotive Expenses + [defaultHighLevelCategoryName] => Automotive Expenses + ) -*/ + */ public function testGetCategories() @@ -467,113 +467,113 @@ class YodleeApiTest extends TestCase $yodlee = new Yodlee('sbMem62e1e69547bfb2'); $transactions = $yodlee->getTransactionCategories(); - + $this->assertIsArray($transactions->transactionCategory); } -/** -[2022-08-05 01:29:45] local.INFO: stdClass Object -( - [account] => Array - ( - [0] => stdClass Object - ( - [CONTAINER] => bank - [providerAccountId] => 11308693 - [accountName] => My CD - 8878 - [accountStatus] => ACTIVE - [accountNumber] => xxxx8878 - [aggregationSource] => USER - [isAsset] => 1 - [balance] => stdClass Object - ( - [currency] => USD - [amount] => 49778.07 - ) + /** + [2022-08-05 01:29:45] local.INFO: stdClass Object + ( + [account] => Array + ( + [0] => stdClass Object + ( + [CONTAINER] => bank + [providerAccountId] => 11308693 + [accountName] => My CD - 8878 + [accountStatus] => ACTIVE + [accountNumber] => xxxx8878 + [aggregationSource] => USER + [isAsset] => 1 + [balance] => stdClass Object + ( + [currency] => USD + [amount] => 49778.07 + ) - [id] => 12331861 - [includeInNetWorth] => 1 - [providerId] => 18769 - [providerName] => Dag Site Captcha - [isManual] => - [currentBalance] => stdClass Object - ( - [currency] => USD - [amount] => 49778.07 - ) + [id] => 12331861 + [includeInNetWorth] => 1 + [providerId] => 18769 + [providerName] => Dag Site Captcha + [isManual] => + [currentBalance] => stdClass Object + ( + [currency] => USD + [amount] => 49778.07 + ) - [accountType] => CD - [displayedName] => LORETTA - [createdDate] => 2022-07-28T06:55:33Z - [lastUpdated] => 2022-07-28T06:56:09Z - [dataset] => Array - ( - [0] => stdClass Object - ( - [name] => BASIC_AGG_DATA - [additionalStatus] => AVAILABLE_DATA_RETRIEVED - [updateEligibility] => ALLOW_UPDATE - [lastUpdated] => 2022-07-28T06:55:50Z - [lastUpdateAttempt] => 2022-07-28T06:55:50Z - ) + [accountType] => CD + [displayedName] => LORETTA + [createdDate] => 2022-07-28T06:55:33Z + [lastUpdated] => 2022-07-28T06:56:09Z + [dataset] => Array + ( + [0] => stdClass Object + ( + [name] => BASIC_AGG_DATA + [additionalStatus] => AVAILABLE_DATA_RETRIEVED + [updateEligibility] => ALLOW_UPDATE + [lastUpdated] => 2022-07-28T06:55:50Z + [lastUpdateAttempt] => 2022-07-28T06:55:50Z + ) - ) + ) - ) - [1] => stdClass Object - ( - [CONTAINER] => bank - [providerAccountId] => 11308693 - [accountName] => Joint Savings - 7159 - [accountStatus] => ACTIVE - [accountNumber] => xxxx7159 - [aggregationSource] => USER - [isAsset] => 1 - [balance] => stdClass Object - ( - [currency] => USD - [amount] => 186277.45 - ) + ) + [1] => stdClass Object + ( + [CONTAINER] => bank + [providerAccountId] => 11308693 + [accountName] => Joint Savings - 7159 + [accountStatus] => ACTIVE + [accountNumber] => xxxx7159 + [aggregationSource] => USER + [isAsset] => 1 + [balance] => stdClass Object + ( + [currency] => USD + [amount] => 186277.45 + ) - [id] => 12331860 - [includeInNetWorth] => 1 - [providerId] => 18769 - [providerName] => Dag Site Captcha - [isManual] => - [availableBalance] => stdClass Object - ( - [currency] => USD - [amount] => 186277.45 - ) + [id] => 12331860 + [includeInNetWorth] => 1 + [providerId] => 18769 + [providerName] => Dag Site Captcha + [isManual] => + [availableBalance] => stdClass Object + ( + [currency] => USD + [amount] => 186277.45 + ) - [currentBalance] => stdClass Object - ( - [currency] => USD - [amount] => 186277.45 - ) + [currentBalance] => stdClass Object + ( + [currency] => USD + [amount] => 186277.45 + ) - [accountType] => SAVINGS - [displayedName] => LYDIA - [createdDate] => 2022-07-28T06:55:33Z - [classification] => PERSONAL - [lastUpdated] => 2022-07-28T06:56:09Z - [dataset] => Array - ( - [0] => stdClass Object - ( - [name] => BASIC_AGG_DATA - [additionalStatus] => AVAILABLE_DATA_RETRIEVED - [updateEligibility] => ALLOW_UPDATE - [lastUpdated] => 2022-07-28T06:55:50Z - [lastUpdateAttempt] => 2022-07-28T06:55:50Z - ) + [accountType] => SAVINGS + [displayedName] => LYDIA + [createdDate] => 2022-07-28T06:55:33Z + [classification] => PERSONAL + [lastUpdated] => 2022-07-28T06:56:09Z + [dataset] => Array + ( + [0] => stdClass Object + ( + [name] => BASIC_AGG_DATA + [additionalStatus] => AVAILABLE_DATA_RETRIEVED + [updateEligibility] => ALLOW_UPDATE + [lastUpdated] => 2022-07-28T06:55:50Z + [lastUpdateAttempt] => 2022-07-28T06:55:50Z + ) - ) + ) - ) -*/ + ) + */ public function testGetAccounts() { @@ -585,51 +585,51 @@ class YodleeApiTest extends TestCase } -/** -[2022-08-05 01:36:34] local.INFO: stdClass Object -( - [transaction] => Array - ( - [0] => stdClass Object - ( - [CONTAINER] => bank - [id] => 103953585 - [amount] => stdClass Object - ( - [amount] => 480.66 - [currency] => USD - ) + /** + [2022-08-05 01:36:34] local.INFO: stdClass Object + ( + [transaction] => Array + ( + [0] => stdClass Object + ( + [CONTAINER] => bank + [id] => 103953585 + [amount] => stdClass Object + ( + [amount] => 480.66 + [currency] => USD + ) - [categoryType] => UNCATEGORIZE - [categoryId] => 1 - [category] => Uncategorized - [categorySource] => SYSTEM - [highLevelCategoryId] => 10000017 - [createdDate] => 2022-08-04T21:50:17Z - [lastUpdated] => 2022-08-04T21:50:17Z - [description] => stdClass Object - ( - [original] => CHEROKEE NATION TAX TA TAHLEQUAH OK - ) + [categoryType] => UNCATEGORIZE + [categoryId] => 1 + [category] => Uncategorized + [categorySource] => SYSTEM + [highLevelCategoryId] => 10000017 + [createdDate] => 2022-08-04T21:50:17Z + [lastUpdated] => 2022-08-04T21:50:17Z + [description] => stdClass Object + ( + [original] => CHEROKEE NATION TAX TA TAHLEQUAH OK + ) - [isManual] => - [sourceType] => AGGREGATED - [date] => 2022-08-03 - [transactionDate] => 2022-08-03 - [postDate] => 2022-08-03 - [status] => POSTED - [accountId] => 12331794 - [runningBalance] => stdClass Object - ( - [amount] => 480.66 - [currency] => USD - ) + [isManual] => + [sourceType] => AGGREGATED + [date] => 2022-08-03 + [transactionDate] => 2022-08-03 + [postDate] => 2022-08-03 + [status] => POSTED + [accountId] => 12331794 + [runningBalance] => stdClass Object + ( + [amount] => 480.66 + [currency] => USD + ) - [checkNumber] => 998 - ) + [checkNumber] => 998 + ) - - */ + + */ public function testGetTransactions() { @@ -654,7 +654,7 @@ class YodleeApiTest extends TestCase 'fromDate' => '2000-10-10', /// YYYY-MM-DD ]; - $accounts = $yodlee->getTransactions($data); + $accounts = $yodlee->getTransactions($data); $this->assertIsArray($accounts); From 71ae70ef83b5d21ea8d3edb58217139569d6e2fb Mon Sep 17 00:00:00 2001 From: paulwer Date: Fri, 1 Dec 2023 14:56:11 +0100 Subject: [PATCH 05/69] adding controler method for institutions (wip) --- .../Controllers/Bank/NordigenController.php | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 5e077ec63a87..25f081942228 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -115,6 +115,80 @@ class NordigenController extends BaseController } + /** + * 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 institutions(Request $request) + { + $account = auth()->user()->account; + + if (!$account->bank_integration_nordigen_client_id || !$account->bank_integration_nordigen_client_secret) + 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); + return response()->json($nordigen->getInstitutions()); + } /** * Process Yodlee Refresh Webhook. * From 67f94b7fb9066bf0ccd593fb3b25c18cdaf18e24 Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 4 Dec 2023 08:14:52 +0100 Subject: [PATCH 06/69] 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 18518847f5c7..ca62fd3af719 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 25f081942228..920519b375a2 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 e9b890893c3e..fcc5586e0977 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 000000000000..85ea8bf3b1f7 --- /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 a71d64943858..21b70875d8a8 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 8840661ea706..19c1e9e43b6c 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 bc36fad08282..71adb711d49a 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 bd443d6d7353..7e3a78cec209 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']); From db407f6925fbec61fac74a7ad216cb6890107466 Mon Sep 17 00:00:00 2001 From: paulwer Date: Tue, 5 Dec 2023 06:56:52 +0100 Subject: [PATCH 07/69] feat: get AccountData --- app/Helpers/Bank/Nordigen/Nordigen.php | 19 +- .../Transformer/AccountTransformer.php | 48 ++-- .../Controllers/Bank/NordigenController.php | 265 ++++-------------- 3 files changed, 79 insertions(+), 253 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index ca62fd3af719..83b22def5923 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -49,12 +49,12 @@ class Nordigen } // requisition-section - public function createRequisition(string $redirect, string $initutionId) + public function createRequisition(string $redirect, string $initutionId, string $nAccountId) { if ($this->test_mode && $initutionId != $this->sandbox_institutionId) throw new \Exception('invalid institutionId while in test-mode'); - return $this->client->requisition->createRequisition($redirect, $initutionId); + return $this->client->requisition->createRequisition($redirect, $initutionId, null, $nAccountId); // we dont reuse existing requisitions, to prevent double usage of them. see: deleteAccount } public function getRequisition(string $requisitionId) @@ -80,18 +80,20 @@ class Nordigen { // get all valid requisitions - $requisitions = $this->client->requisition->getRequisitions(); + $requisitions = $this->client->requisition->getRequisitions(); // no pagination used?! // fetch all valid accounts for activated requisitions $nordigen_accountIds = []; - foreach ($requisitions as $requisition) { - foreach ($requisition->accounts as $accountId) { + foreach ($requisitions["results"] as $requisition) { + foreach ($requisition["accounts"] as $accountId) { array_push($nordigen_accountIds, $accountId); } } $nordigen_accountIds = array_unique($nordigen_accountIds); + Log::info($nordigen_accountIds); + $nordigen_accounts = []; foreach ($nordigen_accountIds as $accountId) { $nordigen_account = $this->getAccount($accountId); @@ -99,6 +101,7 @@ class Nordigen array_push($nordigen_accounts, $nordigen_account); } + Log::info($nordigen_accounts); return $nordigen_accounts; @@ -109,11 +112,13 @@ class Nordigen $out = new \stdClass(); - $out->data = $this->client->account($account_id)->getAccountDetails(); + $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(); + $out->balances = $this->client->account($account_id)->getAccountBalances()["balances"]; $out->institution = $this->client->institution->getInstitution($out->metadata["institution_id"]); + Log::info($out->data); + $it = new AccountTransformer(); return $it->transform($out); diff --git a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php index 48c43559d2a2..250bc2ceca81 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php @@ -36,9 +36,7 @@ use App\Helpers\Bank\AccountTransformerInterface; [status] => READY [owner_name] => Max Mustermann ) - [balances] => stdClass Object - ( - [balances]: [ + [balances] => [ { [balanceAmount]: { [amount] => 9825.64 @@ -57,7 +55,6 @@ use App\Helpers\Bank\AccountTransformerInterface; [referenceDate] => 2023-12-01 } ] - ) [institution] => stdClass Object ( [id] => STADT_KREISSPARKASSE_LEIPZIG_WELADE8LXXX @@ -92,43 +89,32 @@ class AccountTransformer implements AccountTransformerInterface public function transform($nordigen_account) { - $data = []; - if (!property_exists($nordigen_account, 'data') || !property_exists($nordigen_account, 'metadata') || !property_exists($nordigen_account, 'balances') || !property_exists($nordigen_account, 'institution')) - return $data; + throw new \Exception('invalid dataset'); - foreach ($nordigen_account->account as $account) { - $data[] = $this->transformAccount($account); - } - - return $data; - } - - public function transformAccount($account) - { - - $used_balance = $account->balances[0]; + $used_balance = $nordigen_account->balances[0]; // prefer entry with closingBooked - foreach ($account->balances as $entry) { - if ($entry->balanceType === 'closingBooked') { // available: closingBooked, interimAvailable + foreach ($nordigen_account->balances as $entry) { + if ($entry["balanceType"] === 'closingBooked') { // available: closingBooked, interimAvailable $used_balance = $entry; break; } } return [ - 'id' => $account->data->id, - 'account_type' => $account->CONTAINER, - 'account_name' => $account->data->iban, - 'account_status' => $account->metadata->status, - 'account_number' => '**** ' . substr($account->data->iban, -7), - 'provider_account_id' => $account->data->iban, - 'provider_id' => $account->institution_id, - 'provider_name' => $account->institution->name, - 'nickname' => property_exists($account->data, 'owner_name') ? $account->data->owner_name : '', - 'current_balance' => (int) property_exists($used_balance, 'balanceAmount') ? $used_balance->balanceAmount->amount : 0, - 'account_currency' => property_exists($used_balance, 'balanceAmount') ? $used_balance->balanceAmount->currency : '', + 'id' => $nordigen_account->metadata["id"], + 'account_type' => "bank_account", // TODO: not creditCard + 'account_name' => $nordigen_account->data["iban"], + 'account_status' => $nordigen_account->metadata["status"], + 'account_number' => '**** ' . substr($nordigen_account->data["iban"], -7), + 'provider_account_id' => $nordigen_account->data["iban"], + 'provider_id' => $nordigen_account->institution["id"], + 'provider_name' => $nordigen_account->institution["name"], + 'nickname' => $nordigen_account->data?["ownerName"] ? $nordigen_account->data["ownerName"] : '', + 'current_balance' => (int) $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 920519b375a2..d99a43ee2f49 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -16,6 +16,7 @@ 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\Account; use App\Models\BankIntegration; use Illuminate\Http\Request; @@ -76,43 +77,50 @@ class NordigenController extends BaseController } - private function getAccounts($company, $token) + private function getAccounts(Account $account) { - $nordigen = new Nordigen($token); + if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) + return response()->json(['message' => 'Not yet authenticated with Nordigen Bank Integration service'], 400); + + $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); $accounts = $nordigen->getAccounts(); - foreach ($accounts as $account) { + foreach ($account->companies() as $company) { - if (!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $company->id)->exists()) { - $bank_integration = new BankIntegration(); - $bank_integration->company_id = $company->id; - $bank_integration->account_id = $company->account_id; - $bank_integration->user_id = $company->owner()->id; - $bank_integration->bank_account_id = $account['id']; - $bank_integration->bank_account_type = $account['account_type']; - $bank_integration->bank_account_name = $account['account_name']; - $bank_integration->bank_account_status = $account['account_status']; - $bank_integration->bank_account_number = $account['account_number']; - $bank_integration->provider_id = $account['provider_id']; - $bank_integration->provider_name = $account['provider_name']; - $bank_integration->nickname = $account['nickname']; - $bank_integration->balance = $account['current_balance']; - $bank_integration->currency = $account['account_currency']; - $bank_integration->from_date = now()->subYear(); + foreach ($accounts as $account) { + + if (!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $company->id)->exists()) { + $bank_integration = new BankIntegration(); + $bank_integration->company_id = $company->id; + $bank_integration->account_id = $company->account_id; + $bank_integration->user_id = $company->owner()->id; + $bank_integration->bank_account_id = $account['id']; + $bank_integration->bank_account_type = $account['account_type']; + $bank_integration->bank_account_name = $account['account_name']; + $bank_integration->bank_account_status = $account['account_status']; + $bank_integration->bank_account_number = $account['account_number']; + $bank_integration->provider_id = $account['provider_id']; + $bank_integration->provider_name = $account['provider_name']; + $bank_integration->nickname = $account['nickname']; + $bank_integration->balance = $account['current_balance']; + $bank_integration->currency = $account['account_currency']; + $bank_integration->from_date = now()->subYear(); + + $bank_integration->save(); + } - $bank_integration->save(); } + + $company->account->bank_integrations->each(function ($bank_integration) use ($company) { + + ProcessBankTransactionsNordigen::dispatch($company->account, $bank_integration); + + }); + } - - $company->account->bank_integrations->each(function ($bank_integration) use ($company) { - - ProcessBankTransactionsNordigen::dispatch($company->account, $bank_integration); - - }); - } @@ -185,7 +193,7 @@ class NordigenController extends BaseController $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); + return response()->json(['message' => 'Not yet authenticated with Nordigen Bank Integration service'], 400); $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); return response()->json($nordigen->getInstitutions()); @@ -259,12 +267,7 @@ class NordigenController extends BaseController { $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...']); + return $this->getAccounts($account); } /** Creates a new requisition (oAuth like connection of bank-account) @@ -336,7 +339,7 @@ class NordigenController extends BaseController $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); + return response()->json(['message' => 'Not yet authenticated with Nordigen Bank Integration service'], 400); // TODO: should be moved to CreateNortigenRequisitionRequest // $this->validate($request, [ @@ -348,7 +351,11 @@ class NordigenController extends BaseController $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'])]); + return response()->json([ + 'result' => $nordigen->createRequisition($data['redirect'], $data['institutionId'], [ + "account_id" => $account->id, + ]) + ]); } /** @@ -417,190 +424,18 @@ class NordigenController extends BaseController }*/ public function confirm(Request $request) { - // TODO: use custom-token-auth from reference of request - } - /** - * Process Yodlee Refresh Webhook. - * - * - * @OA\Post( - * path="/api/v1/yodlee/refresh", - * operationId="yodleeRefreshWebhook", - * tags={"yodlee"}, - * summary="Processing webhooks from Yodlee", - * description="Notifies the system when a data point can be refreshed", - * @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 refreshWebhook(Request $request) - { - //we should ignore this one - nlog("yodlee refresh"); - nlog($request->all()); + // TODO: should be moved to ConfirmNortigenRequisitionRequest + // $this->validate($request, [ + // 'account_id' => 'required|string|max:100', + // ]); - return response()->json(['message' => 'Success'], 200); + $data = $request->all(); - // + $account = Account::where('id', $data["ref"])->first(); - // return response()->json(['message' => 'Unauthorized'], 403); - } + return $this->getAccounts($account); - /* - { - "event":{ - "notificationId":"63c73475-4db5-49ef-8553-8303337ca7c3", - "info":"LATEST_BALANCE_UPDATES", - "loginName":"user1", - "data":{ - "providerAccountId":658552, - "latestBalanceEvent":[ - { - "accountId":12345, - "status":"SUCCESS" - }, - { - "accountId":12346, - "status":"FAILED" - } - ] - } - } - } - */ - public function balanceWebhook(Request $request) - { - - nlog("yodlee refresh"); - nlog($request->all()); - - return response()->json(['message' => 'Success'], 200); - - // - - // return response()->json(['message' => 'Unauthorized'], 403); - } - - /* - { - "event":{ - "data":[ - { - "autoRefresh":{ - "additionalStatus":"SCHEDULED", - "status":"ENABLED" - }, - "accountIds":[ - 1112645899, - 1112645898 - ], - "loginName":"YSL1555332811628", - "providerAccountId":11381459 - } - ], - "notificationTime":"2019-06-14T04:49:39Z", - "notificationId":"4e672150-156048777", - "info":"AUTO_REFRESH_UPDATES" - } - } - */ - public function refreshUpdatesWebhook(Request $request) - { - //notifies a user if there are problems with yodlee accessing the data - nlog("update refresh"); - nlog($request->all()); - - return response()->json(['message' => 'Success'], 200); - - // - - // return response()->json(['message' => 'Unauthorized'], 403); - } - - - /* - "event": { - "notificationId": "64b7ed1a-1530523285", - "info": "DATA_UPDATES.USER_DATA", - "data": { - "userCount": 1, - "fromDate": "2017-11-10T10:18:44Z", - "toDate": "2017-11-10T11:18:43Z", - "userData": [{ - "user": { - "loginName": "YSL1484052178554" - }, - "links": [{ - "methodType": "GET", - "rel": "getUserData", - "href": "dataExtracts/userData?fromDate=2017-11-10T10:18:44Z&toDate=2017-11-10T11:18:43Z&loginName=YSL1484052178554" - }] - }] - } - } - */ - public function dataUpdatesWebhook(Request $request) - { - //this is the main hook we use for notifications - - nlog("data refresh"); - nlog($request->all()); - - return response()->json(['message' => 'Success'], 200); - - // - - // return response()->json(['message' => 'Unauthorized'], 403); } } From 5616da03c5f91b77ad914617b2883487621a1e7e Mon Sep 17 00:00:00 2001 From: paulwer Date: Tue, 5 Dec 2023 07:17:07 +0100 Subject: [PATCH 08/69] feat: refresh & getAccounts --- app/Helpers/Bank/Nordigen/Nordigen.php | 9 --------- .../Bank/Nordigen/Transformer/AccountTransformer.php | 6 +++--- app/Http/Controllers/Bank/NordigenController.php | 9 +++++++-- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index 83b22def5923..a83bd5031067 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -31,9 +31,6 @@ class Nordigen public function __construct(string $secret_id, string $secret_key) { - 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 @@ -92,8 +89,6 @@ class Nordigen $nordigen_accountIds = array_unique($nordigen_accountIds); - Log::info($nordigen_accountIds); - $nordigen_accounts = []; foreach ($nordigen_accountIds as $accountId) { $nordigen_account = $this->getAccount($accountId); @@ -101,8 +96,6 @@ class Nordigen array_push($nordigen_accounts, $nordigen_account); } - Log::info($nordigen_accounts); - return $nordigen_accounts; } @@ -117,8 +110,6 @@ class Nordigen $out->balances = $this->client->account($account_id)->getAccountBalances()["balances"]; $out->institution = $this->client->institution->getInstitution($out->metadata["institution_id"]); - Log::info($out->data); - $it = new AccountTransformer(); return $it->transform($out); diff --git a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php index 250bc2ceca81..eccd5a1f5740 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php @@ -102,15 +102,15 @@ class AccountTransformer implements AccountTransformerInterface } return [ - 'id' => $nordigen_account->metadata["id"], - 'account_type' => "bank_account", // TODO: not creditCard + 'id' => $nordigen_account->metadata["id"], // TODO: maybe add prefix for unique id between yodlee and nordigen? + 'account_type' => "bank_account", // TODO: not creditCard, which type should be used here?! 'account_name' => $nordigen_account->data["iban"], 'account_status' => $nordigen_account->metadata["status"], 'account_number' => '**** ' . substr($nordigen_account->data["iban"], -7), 'provider_account_id' => $nordigen_account->data["iban"], 'provider_id' => $nordigen_account->institution["id"], 'provider_name' => $nordigen_account->institution["name"], - 'nickname' => $nordigen_account->data?["ownerName"] ? $nordigen_account->data["ownerName"] : '', + 'nickname' => $nordigen_account->data["ownerName"] ? $nordigen_account->data["ownerName"] : '', 'current_balance' => (int) $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 d99a43ee2f49..a8eb96d5d025 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -19,6 +19,7 @@ use App\Jobs\Bank\ProcessBankTransactionsNordigen; use App\Models\Account; use App\Models\BankIntegration; use Illuminate\Http\Request; +use Log; class NordigenController extends BaseController { @@ -86,12 +87,16 @@ class NordigenController extends BaseController $accounts = $nordigen->getAccounts(); - foreach ($account->companies() as $company) { + $account->companies()->each(function ($company) use ($accounts) { foreach ($accounts as $account) { if (!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $company->id)->exists()) { + + Log::info("Creating new BankIntegration"); + $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; $bank_integration->user_id = $company->owner()->id; @@ -119,7 +124,7 @@ class NordigenController extends BaseController }); - } + }); } From ed688afa20503ef822e23936d8ca5afe87d3e77f Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 6 Dec 2023 06:17:56 +0100 Subject: [PATCH 09/69] using nullable as default value for integration_type --- app/Models/BankIntegration.php | 2 -- database/factories/BankIntegrationFactory.php | 2 +- ...3_11_26_082959_add_bank_integration_id.php | 33 +++++++++++++++++-- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/app/Models/BankIntegration.php b/app/Models/BankIntegration.php index 2d85f3c1e3a3..a1d16afb6769 100644 --- a/app/Models/BankIntegration.php +++ b/app/Models/BankIntegration.php @@ -36,8 +36,6 @@ class BankIntegration extends BaseModel protected $dates = [ ]; - const INTEGRATION_TYPE_NONE = null; - const INTEGRATION_TYPE_YODLEE = 'YODLEE'; const INTEGRATION_TYPE_NORDIGEN = 'NORDIGEN'; diff --git a/database/factories/BankIntegrationFactory.php b/database/factories/BankIntegrationFactory.php index 9d04cdfc56f8..9a739d049f6f 100644 --- a/database/factories/BankIntegrationFactory.php +++ b/database/factories/BankIntegrationFactory.php @@ -26,7 +26,7 @@ class BankIntegrationFactory extends Factory public function definition() { return [ - 'integration_type' => BankIntegration::INTEGRATION_TYPE_NONE, + 'integration_type' => null, 'provider_name' => $this->faker->company(), 'provider_id' => 1, 'bank_account_name' => $this->faker->catchPhrase(), diff --git a/database/migrations/2023_11_26_082959_add_bank_integration_id.php b/database/migrations/2023_11_26_082959_add_bank_integration_id.php index 8895b7729926..1e0fa0d8d6b9 100644 --- a/database/migrations/2023_11_26_082959_add_bank_integration_id.php +++ b/database/migrations/2023_11_26_082959_add_bank_integration_id.php @@ -15,11 +15,11 @@ return new class extends Migration { public function up() { Schema::table('bank_integrations', function (Blueprint $table) { - $table->string('integration_type')->default(BankIntegration::INTEGRATION_TYPE_NONE); + $table->string('integration_type')->nullable(); }); // migrate old account to be used with yodlee - BankIntegration::query()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NONE)->whereNotNull('account_id')->cursor()->each(function ($bank_integration) { + BankIntegration::query()->whereNull('integration_type')->whereNotNull('account_id')->cursor()->each(function ($bank_integration) { $bank_integration->integration_type = BankIntegration::INTEGRATION_TYPE_YODLEE; $bank_integration->save(); }); @@ -30,6 +30,35 @@ return new class extends Migration { $table->string('bank_integration_nordigen_secret_id')->nullable(); $table->string('bank_integration_nordigen_secret_key')->nullable(); }); + + // TODO: assign requisitions, to determine, which requisitions belong to which account and which can be leaned up, when necessary + Schema::create('bank_integration_nordigen_requisitions', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('account_id'); + $table->unsignedInteger('company_id'); + $table->unsignedInteger('user_id'); + + $table->text('provider_name'); //providerName ie Chase + $table->bigInteger('provider_id'); //id of the bank + $table->bigInteger('bank_account_id'); //id + $table->text('bank_account_name')->nullable(); //accountName + $table->text('bank_account_number')->nullable(); //accountNumber + $table->text('bank_account_status')->nullable(); //accountStatus + $table->text('bank_account_type')->nullable(); //CONTAINER + $table->decimal('balance', 20, 6)->default(0); //currentBalance.amount + $table->text('currency')->nullable(); //currentBalance.currency + $table->text('nickname')->default(''); //accountName + $table->date('from_date')->nullable(); + + $table->boolean('is_deleted')->default(0); + + $table->timestamps(6); + $table->softDeletes('deleted_at', 6); + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade'); + $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade')->onUpdate('cascade'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade'); + }); } /** From 9f062cc4f85debe2c0caafa4c1a97ffdd5273017 Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 6 Dec 2023 08:27:18 +0100 Subject: [PATCH 10/69] revert new table, some changes for endpoints, fixes --- app/Helpers/Bank/Nordigen/Nordigen.php | 14 +- .../Controllers/Bank/NordigenController.php | 209 ++++++------------ ...p => CreateNordigenRequisitionRequest.php} | 5 +- .../Bank/ProcessBankTransactionsNordigen.php | 2 +- ...3_11_26_082959_add_bank_integration_id.php | 28 --- routes/api.php | 1 - 6 files changed, 77 insertions(+), 182 deletions(-) rename app/Http/Requests/Nordigen/{CreateNortigenRequisitionRequest.php => CreateNordigenRequisitionRequest.php} (82%) diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index a83bd5031067..e0718e01fb5a 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -16,9 +16,6 @@ namespace App\Helpers\Bank\Nordigen; use App\Exceptions\NordigenApiException; use App\Helpers\Bank\Nordigen\Transformer\AccountTransformer; use App\Helpers\Bank\Nordigen\Transformer\IncomeTransformer; -use Illuminate\Support\Facades\Http; -use Illuminate\Support\Str; -use Illuminate\Support\Facades\Log; class Nordigen { @@ -46,12 +43,12 @@ class Nordigen } // requisition-section - public function createRequisition(string $redirect, string $initutionId, string $nAccountId) + public function createRequisition(string $redirect, string $initutionId, string $reference) { if ($this->test_mode && $initutionId != $this->sandbox_institutionId) throw new \Exception('invalid institutionId while in test-mode'); - return $this->client->requisition->createRequisition($redirect, $initutionId, null, $nAccountId); // we dont reuse existing requisitions, to prevent double usage of them. see: deleteAccount + return $this->client->requisition->createRequisition($redirect, $initutionId, null, $reference); // we dont reuse existing requisitions, to prevent double usage of them. see: deleteAccount } public function getRequisition(string $requisitionId) @@ -59,6 +56,7 @@ class Nordigen return $this->client->requisition->getRequisition($requisitionId); } + // NOTE: this will only cleanup the requisitions from nordigen and not within the table: bank_integration_nordigen_requisitions public function cleanupRequisitions() { $requisitions = $this->client->requisition->getRequisitions(); @@ -73,7 +71,7 @@ class Nordigen } // account-section: these methods should be used to get data of connected accounts - public function getAccounts() + public function getAccounts(?array $requisitionIds) { // get all valid requisitions @@ -82,6 +80,10 @@ class Nordigen // fetch all valid accounts for activated requisitions $nordigen_accountIds = []; foreach ($requisitions["results"] as $requisition) { + // FILTER: for requisitionIds + if ($requisitionIds && !in_array($requisition["id"], $requisitionIds)) + continue; + foreach ($requisition["accounts"] as $accountId) { array_push($nordigen_accountIds, $accountId); } diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index a8eb96d5d025..88648adb0b09 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -13,13 +13,13 @@ 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\Nordigen\CreateNordigenRequisitionRequest; use App\Http\Requests\Yodlee\YodleeAuthRequest; use App\Jobs\Bank\ProcessBankTransactionsNordigen; -use App\Models\Account; use App\Models\BankIntegration; +use App\Models\Company; +use Cache; use Illuminate\Http\Request; -use Log; class NordigenController extends BaseController { @@ -77,58 +77,6 @@ class NordigenController extends BaseController return view('bank.yodlee.auth', $data); } - - private function getAccounts(Account $account) - { - if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) - return response()->json(['message' => 'Not yet authenticated with Nordigen Bank Integration service'], 400); - - $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); - - $accounts = $nordigen->getAccounts(); - - $account->companies()->each(function ($company) use ($accounts) { - - foreach ($accounts as $account) { - - if (!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $company->id)->exists()) { - - Log::info("Creating new BankIntegration"); - - $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; - $bank_integration->user_id = $company->owner()->id; - $bank_integration->bank_account_id = $account['id']; - $bank_integration->bank_account_type = $account['account_type']; - $bank_integration->bank_account_name = $account['account_name']; - $bank_integration->bank_account_status = $account['account_status']; - $bank_integration->bank_account_number = $account['account_number']; - $bank_integration->provider_id = $account['provider_id']; - $bank_integration->provider_name = $account['provider_name']; - $bank_integration->nickname = $account['nickname']; - $bank_integration->balance = $account['current_balance']; - $bank_integration->currency = $account['account_currency']; - $bank_integration->from_date = now()->subYear(); - - $bank_integration->save(); - } - - } - - - $company->account->bank_integrations->each(function ($bank_integration) use ($company) { - - ProcessBankTransactionsNordigen::dispatch($company->account, $bank_integration); - - }); - - }); - - } - - /** * Process Nordigen Institutions GETTER. * @@ -204,80 +152,9 @@ class NordigenController extends BaseController 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; - - return $this->getAccounts($account); - } - /** Creates a new requisition (oAuth like connection of bank-account) * - * @param CreateNortigenRequisitionRequest $request + * @param CreateNordigenRequisitionRequest $request * * @OA\Post( * path="/api/v1/nordigen/institutions", @@ -339,27 +216,25 @@ class NordigenController extends BaseController } } }*/ - public function connect(Request $request) // TODO: error, when using class CreateNortigenRequisitionRequest + public function connect(CreateNordigenRequisitionRequest $request) // TODO: error, when using class CreateNordigenRequisitionRequest { + $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 Nordigen 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(); + $context = Cache::get($data["context"]); + + if (!$context || $context->context != "nordigen") + return response()->json(['message' => 'Invalid context provided. Call /api/v1/one_time_token with context: \'nordigen\' first.'], 400); + $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'], [ - "account_id" => $account->id, - ]) + 'result' => $nordigen->createRequisition($data['redirect'], $data['institutionId'], $data["context"]) ]); } @@ -430,16 +305,62 @@ class NordigenController extends BaseController public function confirm(Request $request) { - // TODO: should be moved to ConfirmNortigenRequisitionRequest - // $this->validate($request, [ - // 'account_id' => 'required|string|max:100', - // ]); - $data = $request->all(); - $account = Account::where('id', $data["ref"])->first(); + $context = Cache::get($data["reference"]); - return $this->getAccounts($account); + if (!$context || $context->context != "nordigen") + return response()->json(['message' => 'Invalid context provided. Call /api/v1/one_time_token with context: \'nordigen\' first.'], 400); + + $company = Company::where('id', $context["company_key"])->first(); // TODO: get from one-time-token + + $account = $company->account; + + if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) + return response()->json(['message' => 'Not yet authenticated with Nordigen Bank Integration service'], 400); + + $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); + + $requisition = $nordigen->getRequisition($data["requisitionId"]); + + foreach ($requisition["accounts"] as $accountId) { + + $account = $nordigen->getAccount($accountId); + + if (!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $company->id)->exists()) { + + $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; + $bank_integration->user_id = $company->owner()->id; + $bank_integration->bank_account_id = $account['id']; + $bank_integration->bank_account_type = $account['account_type']; + $bank_integration->bank_account_name = $account['account_name']; + $bank_integration->bank_account_status = $account['account_status']; + $bank_integration->bank_account_number = $account['account_number']; + $bank_integration->provider_id = $account['provider_id']; + $bank_integration->provider_name = $account['provider_name']; + $bank_integration->nickname = $account['nickname']; + $bank_integration->balance = $account['current_balance']; + $bank_integration->currency = $account['account_currency']; + $bank_integration->from_date = now()->subYear(); + + $bank_integration->save(); + + } + + } + + + $company->account->bank_integrations->each(function ($bank_integration) use ($company) { + + ProcessBankTransactionsNordigen::dispatch($company->account, $bank_integration); + + }); + + // TODO: get current frontend-url from hash + response()->redirectTo(); } diff --git a/app/Http/Requests/Nordigen/CreateNortigenRequisitionRequest.php b/app/Http/Requests/Nordigen/CreateNordigenRequisitionRequest.php similarity index 82% rename from app/Http/Requests/Nordigen/CreateNortigenRequisitionRequest.php rename to app/Http/Requests/Nordigen/CreateNordigenRequisitionRequest.php index 85ea8bf3b1f7..008599f1425e 100644 --- a/app/Http/Requests/Nordigen/CreateNortigenRequisitionRequest.php +++ b/app/Http/Requests/Nordigen/CreateNordigenRequisitionRequest.php @@ -9,11 +9,11 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\Http\Requests\Nortigen; +namespace App\Http\Requests\Nordigen; use App\Http\Requests\Request; -class CreateNortigenRequisitionRequest extends Request +class CreateNordigenRequisitionRequest extends Request { /** * Determine if the user is authorized to make this request. @@ -35,6 +35,7 @@ class CreateNortigenRequisitionRequest extends Request return [ 'redirect' => 'required|string|max:100', 'institutionId' => 'required|string|max:100', + 'context' => 'required|string|max:1000', // One Time Token ]; } } diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index 21b70875d8a8..0ef3f9dfee96 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -23,7 +23,6 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Carbon; class ProcessBankTransactionsNordigen implements ShouldQueue { @@ -96,6 +95,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue $this->bank_integration->disabled_upstream = true; $this->bank_integration->save(); $this->stop_loop = false; + // @turbo124 @todo send email for expired account return; } diff --git a/database/migrations/2023_11_26_082959_add_bank_integration_id.php b/database/migrations/2023_11_26_082959_add_bank_integration_id.php index 1e0fa0d8d6b9..1c5de199a325 100644 --- a/database/migrations/2023_11_26_082959_add_bank_integration_id.php +++ b/database/migrations/2023_11_26_082959_add_bank_integration_id.php @@ -31,34 +31,6 @@ return new class extends Migration { $table->string('bank_integration_nordigen_secret_key')->nullable(); }); - // TODO: assign requisitions, to determine, which requisitions belong to which account and which can be leaned up, when necessary - Schema::create('bank_integration_nordigen_requisitions', function (Blueprint $table) { - $table->id(); - $table->unsignedInteger('account_id'); - $table->unsignedInteger('company_id'); - $table->unsignedInteger('user_id'); - - $table->text('provider_name'); //providerName ie Chase - $table->bigInteger('provider_id'); //id of the bank - $table->bigInteger('bank_account_id'); //id - $table->text('bank_account_name')->nullable(); //accountName - $table->text('bank_account_number')->nullable(); //accountNumber - $table->text('bank_account_status')->nullable(); //accountStatus - $table->text('bank_account_type')->nullable(); //CONTAINER - $table->decimal('balance', 20, 6)->default(0); //currentBalance.amount - $table->text('currency')->nullable(); //currentBalance.currency - $table->text('nickname')->default(''); //accountName - $table->date('from_date')->nullable(); - - $table->boolean('is_deleted')->default(0); - - $table->timestamps(6); - $table->softDeletes('deleted_at', 6); - - $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade'); - $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade')->onUpdate('cascade'); - $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade'); - }); } /** diff --git a/routes/api.php b/routes/api.php index 7e3a78cec209..3109cc001dd8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -378,7 +378,6 @@ Route::post('api/v1/yodlee/refresh_updates', [YodleeController::class, 'refreshU Route::post('api/v1/yodlee/balance', [YodleeController::class, 'balanceWebhook'])->middleware('throttle:100,1'); 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'); From e2d8ac37b51763f0d7278b6043423be672aa299e Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 6 Dec 2023 09:28:33 +0100 Subject: [PATCH 11/69] change account_type --- .../Transformer/AccountTransformer.php | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php index eccd5a1f5740..fcf81cee3352 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php @@ -83,35 +83,33 @@ use App\Helpers\Bank\AccountTransformerInterface; */ -class AccountTransformer implements AccountTransformerInterface -{ +class AccountTransformer implements AccountTransformerInterface { - public function transform($nordigen_account) - { + public function transform($nordigen_account) { - if (!property_exists($nordigen_account, 'data') || !property_exists($nordigen_account, 'metadata') || !property_exists($nordigen_account, 'balances') || !property_exists($nordigen_account, 'institution')) + if(!property_exists($nordigen_account, 'data') || !property_exists($nordigen_account, 'metadata') || !property_exists($nordigen_account, 'balances') || !property_exists($nordigen_account, 'institution')) throw new \Exception('invalid dataset'); $used_balance = $nordigen_account->balances[0]; // prefer entry with closingBooked - foreach ($nordigen_account->balances as $entry) { - if ($entry["balanceType"] === 'closingBooked') { // available: closingBooked, interimAvailable + foreach($nordigen_account->balances as $entry) { + if($entry["balanceType"] === 'closingBooked') { // available: closingBooked, interimAvailable $used_balance = $entry; break; } } return [ - 'id' => $nordigen_account->metadata["id"], // TODO: maybe add prefix for unique id between yodlee and nordigen? - 'account_type' => "bank_account", // TODO: not creditCard, which type should be used here?! + 'id' => $nordigen_account->metadata["id"], + 'account_type' => "bank", 'account_name' => $nordigen_account->data["iban"], 'account_status' => $nordigen_account->metadata["status"], - 'account_number' => '**** ' . substr($nordigen_account->data["iban"], -7), + 'account_number' => '**** '.substr($nordigen_account->data["iban"], -7), 'provider_account_id' => $nordigen_account->data["iban"], 'provider_id' => $nordigen_account->institution["id"], 'provider_name' => $nordigen_account->institution["name"], 'nickname' => $nordigen_account->data["ownerName"] ? $nordigen_account->data["ownerName"] : '', - 'current_balance' => (int) $used_balance ? $used_balance["balanceAmount"]["amount"] : 0, + 'current_balance' => (int)$used_balance ? $used_balance["balanceAmount"]["amount"] : 0, 'account_currency' => $used_balance ? $used_balance["balanceAmount"]["currency"] : '', ]; From 4d2425d96b16a7ff4af9ee279ee876fc8d7979fe Mon Sep 17 00:00:00 2001 From: paulwer Date: Fri, 8 Dec 2023 18:42:06 +0100 Subject: [PATCH 12/69] feat: connect account --- .../Controllers/Bank/NordigenController.php | 126 +++++++++++++++--- ... => ConfirmNordigenRequisitionRequest.php} | 6 +- .../ConnectNordigenRequisitionRequest.php | 41 ++++++ routes/api.php | 2 +- 4 files changed, 148 insertions(+), 27 deletions(-) rename app/Http/Requests/Nordigen/{CreateNordigenRequisitionRequest.php => ConfirmNordigenRequisitionRequest.php} (74%) create mode 100644 app/Http/Requests/Nordigen/ConnectNordigenRequisitionRequest.php diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 88648adb0b09..6b9e37ca7749 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -13,13 +13,15 @@ namespace App\Http\Controllers\Bank; use App\Helpers\Bank\Nordigen\Nordigen; use App\Http\Controllers\BaseController; -use App\Http\Requests\Nordigen\CreateNordigenRequisitionRequest; +use App\Http\Requests\Nordigen\ConfirmNordigenRequisitionRequest; +use App\Http\Requests\Nordigen\ConnectNordigenRequisitionRequest; use App\Http\Requests\Yodlee\YodleeAuthRequest; use App\Jobs\Bank\ProcessBankTransactionsNordigen; use App\Models\BankIntegration; use App\Models\Company; use Cache; use Illuminate\Http\Request; +use Log; class NordigenController extends BaseController { @@ -154,7 +156,7 @@ class NordigenController extends BaseController /** Creates a new requisition (oAuth like connection of bank-account) * - * @param CreateNordigenRequisitionRequest $request + * @param ConnectNordigenRequisitionRequest $request * * @OA\Post( * path="/api/v1/nordigen/institutions", @@ -216,31 +218,41 @@ class NordigenController extends BaseController } } }*/ - public function connect(CreateNordigenRequisitionRequest $request) // TODO: error, when using class CreateNordigenRequisitionRequest + public function connect(ConnectNordigenRequisitionRequest $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 Nordigen Bank Integration service'], 400); $data = $request->all(); - $context = Cache::get($data["context"]); + $context = Cache::get($data["hash"]); + Log::info($context); + if (!$context || $context["context"] != "nordigen" || array_key_exists("requisition", $context)) // TODO: check for requisition array key + return response()->json(['message' => 'Invalid context one_time_token. (not-found|invalid-context|already-used) Call /api/v1/one_time_token with context: \'nordigen\' first.'], 400); - if (!$context || $context->context != "nordigen") - return response()->json(['message' => 'Invalid context provided. Call /api/v1/one_time_token with context: \'nordigen\' first.'], 400); + Log::info(config('ninja.app_url') . '/api/v1/nordigen/confirm'); $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); + $requisition = $nordigen->createRequisition(config('ninja.app_url') . '/api/v1/nordigen/confirm', $data['institutionId'], $data["hash"]); + + // save cache + if (array_key_exists("redirectUri", $data)) + $context["redirectUri"] = $data["redirectUri"]; + $context["requisitionId"] = $requisition["id"]; + Cache::put($data["hash"], $context, 3600); return response()->json([ - 'result' => $nordigen->createRequisition($data['redirect'], $data['institutionId'], $data["context"]) + 'result' => $requisition, + 'redirectUri' => array_key_exists("redirectUri", $data) ? $data["redirectUri"] : null, ]); + } /** * Process Nordigen Institutions GETTER. - * + * @param ConfirmNordigenRequisitionRequest $request * * @OA\Post( * path="/api/v1/nordigen/institutions", @@ -302,32 +314,78 @@ class NordigenController extends BaseController } } }*/ - public function confirm(Request $request) + public function confirm(ConfirmNordigenRequisitionRequest $request) { $data = $request->all(); - $context = Cache::get($data["reference"]); + $context = Cache::get($data["ref"]); + if (!$context || $context["context"] != "nordigen" || !array_key_exists("requisitionId", $context)) { + if ($context && array_key_exists("redirectUri", $context)) + return response()->redirectTo($context["redirectUri"] . "?action=nordigen_connect&status=failed&reason=ref-invalid"); - if (!$context || $context->context != "nordigen") - return response()->json(['message' => 'Invalid context provided. Call /api/v1/one_time_token with context: \'nordigen\' first.'], 400); - - $company = Company::where('id', $context["company_key"])->first(); // TODO: get from one-time-token + return response()->json([ + 'status' => 'failed', + 'reason' => 'ref-invalid', + ], 400); + } + $company = Company::where('company_key', $context["company_key"])->first(); $account = $company->account; - if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) - return response()->json(['message' => 'Not yet authenticated with Nordigen Bank Integration service'], 400); + if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) { + if (array_key_exists("redirectUri", $context)) + return response()->redirectTo($context["redirectUri"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid"); + return response()->json([ + 'status' => 'failed', + 'reason' => 'account-config-invalid', + ], 400); + } + + // fetch requisition $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); + $requisition = $nordigen->getRequisition($context["requisitionId"]); - $requisition = $nordigen->getRequisition($data["requisitionId"]); + // check validity of requisition + if (!$requisition) { + if (array_key_exists("redirectUri", $context)) + return response()->redirectTo($context["redirectUri"] . "?action=nordigen_connect&status=failed&reason=requisition-not-found"); + return response()->json([ + 'status' => 'failed', + 'reason' => 'requisition-not-found', + ], 400); + } + if ($requisition["status"] != "LN") { + if (array_key_exists("redirectUri", $context)) + return response()->redirectTo($context["redirectUri"] . "?action=nordigen_connect&status=failed&reason=requisition-invalid-status"); + + return response()->json([ + 'status' => 'failed', + 'reason' => 'requisition-invalid-status', + ], 400); + } + if (sizeof($requisition["accounts"]) == 0) { + if (array_key_exists("redirectUri", $context)) + return response()->redirectTo($context["redirectUri"] . "?action=nordigen_connect&status=failed&reason=requisition-no-accounts"); + + return response()->json([ + 'status' => 'failed', + 'reason' => 'requisition-no-accounts', + ], 400); + } + + + // connect new accounts + $bank_integration_ids = []; foreach ($requisition["accounts"] as $accountId) { $account = $nordigen->getAccount($accountId); - if (!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $company->id)->exists()) { + $existing_bank_integration = BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $company->id)->first(); + + if (!$existing_bank_integration) { $bank_integration = new BankIntegration(); $bank_integration->integration_type = BankIntegration::INTEGRATION_TYPE_NORDIGEN; @@ -344,23 +402,47 @@ class NordigenController extends BaseController $bank_integration->nickname = $account['nickname']; $bank_integration->balance = $account['current_balance']; $bank_integration->currency = $account['account_currency']; + $bank_integration->disabled_upstream = false; + $bank_integration->auto_sync = true; $bank_integration->from_date = now()->subYear(); $bank_integration->save(); + array_push($bank_integration_ids, $bank_integration->id); + + } else { + + // resetting metadata for account status + $existing_bank_integration->balance = $account['current_balance']; + $existing_bank_integration->bank_account_status = $account['account_status']; + $existing_bank_integration->disabled_upstream = false; + $existing_bank_integration->auto_sync = true; + + $existing_bank_integration->save(); + + array_push($bank_integration_ids, $existing_bank_integration->id); } } - + // perform update in background $company->account->bank_integrations->each(function ($bank_integration) use ($company) { ProcessBankTransactionsNordigen::dispatch($company->account, $bank_integration); }); - // TODO: get current frontend-url from hash - response()->redirectTo(); + // prevent rerun of this method with same ref + Cache::delete($data["ref"]); + + // Successfull Response + if (array_key_exists("redirectUri", $context)) + return response()->redirectTo($context["redirectUri"] . "?action=nordigen_connect&status=success&bank_integrations=" . implode(',', $bank_integration_ids)); + + return response()->json([ + 'status' => 'success', + 'bank_integrations' => $bank_integration_ids, + ]); } diff --git a/app/Http/Requests/Nordigen/CreateNordigenRequisitionRequest.php b/app/Http/Requests/Nordigen/ConfirmNordigenRequisitionRequest.php similarity index 74% rename from app/Http/Requests/Nordigen/CreateNordigenRequisitionRequest.php rename to app/Http/Requests/Nordigen/ConfirmNordigenRequisitionRequest.php index 008599f1425e..b4e0ac5d85d3 100644 --- a/app/Http/Requests/Nordigen/CreateNordigenRequisitionRequest.php +++ b/app/Http/Requests/Nordigen/ConfirmNordigenRequisitionRequest.php @@ -13,7 +13,7 @@ namespace App\Http\Requests\Nordigen; use App\Http\Requests\Request; -class CreateNordigenRequisitionRequest extends Request +class ConfirmNordigenRequisitionRequest extends Request { /** * Determine if the user is authorized to make this request. @@ -33,9 +33,7 @@ class CreateNordigenRequisitionRequest extends Request public function rules() { return [ - 'redirect' => 'required|string|max:100', - 'institutionId' => 'required|string|max:100', - 'context' => 'required|string|max:1000', // One Time Token + 'ref' => 'required|string', // nordigen redirects only with the ref-property ]; } } diff --git a/app/Http/Requests/Nordigen/ConnectNordigenRequisitionRequest.php b/app/Http/Requests/Nordigen/ConnectNordigenRequisitionRequest.php new file mode 100644 index 000000000000..3584dbf51637 --- /dev/null +++ b/app/Http/Requests/Nordigen/ConnectNordigenRequisitionRequest.php @@ -0,0 +1,41 @@ + 'required|string', + 'hash' => 'required|string', // One Time Token + 'redirectUri' => 'string', // TODO: @turbo124 @todo validate, that this is a url without / at the end + ]; + } +} diff --git a/routes/api.php b/routes/api.php index 3109cc001dd8..c9202f65fb54 100644 --- a/routes/api.php +++ b/routes/api.php @@ -379,6 +379,6 @@ Route::post('api/v1/yodlee/balance', [YodleeController::class, 'balanceWebhook'] Route::get('api/v1/nordigen/institutions', [NordigenController::class, 'institutions'])->middleware('throttle:100,1')->middleware('token_auth')->name('nordigen_institutions'); 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::any('api/v1/nordigen/confirm', [NordigenController::class, 'confirm'])->middleware('throttle:100,1')->name('nordigen_callback'); Route::fallback([BaseController::class, 'notFound']); From 3393ba01aa771708ad25e5fba6ae2cc247e59ddd Mon Sep 17 00:00:00 2001 From: paulwer Date: Fri, 8 Dec 2023 18:46:12 +0100 Subject: [PATCH 13/69] renaming Request Dtos --- app/Http/Controllers/Bank/NordigenController.php | 12 ++++++------ ...isitionRequest.php => ConfirmNordigenRequest.php} | 2 +- ...isitionRequest.php => ConnectNordigenRequest.php} | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) rename app/Http/Requests/Nordigen/{ConfirmNordigenRequisitionRequest.php => ConfirmNordigenRequest.php} (93%) rename app/Http/Requests/Nordigen/{ConnectNordigenRequisitionRequest.php => ConnectNordigenRequest.php} (94%) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 6b9e37ca7749..0b2fdda3b11e 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -13,8 +13,8 @@ namespace App\Http\Controllers\Bank; use App\Helpers\Bank\Nordigen\Nordigen; use App\Http\Controllers\BaseController; -use App\Http\Requests\Nordigen\ConfirmNordigenRequisitionRequest; -use App\Http\Requests\Nordigen\ConnectNordigenRequisitionRequest; +use App\Http\Requests\Nordigen\ConfirmNordigenRequest; +use App\Http\Requests\Nordigen\ConnectNordigenRequest; use App\Http\Requests\Yodlee\YodleeAuthRequest; use App\Jobs\Bank\ProcessBankTransactionsNordigen; use App\Models\BankIntegration; @@ -156,7 +156,7 @@ class NordigenController extends BaseController /** Creates a new requisition (oAuth like connection of bank-account) * - * @param ConnectNordigenRequisitionRequest $request + * @param ConnectNordigenRequest $request * * @OA\Post( * path="/api/v1/nordigen/institutions", @@ -218,7 +218,7 @@ class NordigenController extends BaseController } } }*/ - public function connect(ConnectNordigenRequisitionRequest $request) + public function connect(ConnectNordigenRequest $request) { $account = auth()->user()->account; @@ -252,7 +252,7 @@ class NordigenController extends BaseController /** * Process Nordigen Institutions GETTER. - * @param ConfirmNordigenRequisitionRequest $request + * @param ConfirmNordigenRequest $request * * @OA\Post( * path="/api/v1/nordigen/institutions", @@ -314,7 +314,7 @@ class NordigenController extends BaseController } } }*/ - public function confirm(ConfirmNordigenRequisitionRequest $request) + public function confirm(ConfirmNordigenRequest $request) { $data = $request->all(); diff --git a/app/Http/Requests/Nordigen/ConfirmNordigenRequisitionRequest.php b/app/Http/Requests/Nordigen/ConfirmNordigenRequest.php similarity index 93% rename from app/Http/Requests/Nordigen/ConfirmNordigenRequisitionRequest.php rename to app/Http/Requests/Nordigen/ConfirmNordigenRequest.php index b4e0ac5d85d3..75b24e6e5b81 100644 --- a/app/Http/Requests/Nordigen/ConfirmNordigenRequisitionRequest.php +++ b/app/Http/Requests/Nordigen/ConfirmNordigenRequest.php @@ -13,7 +13,7 @@ namespace App\Http\Requests\Nordigen; use App\Http\Requests\Request; -class ConfirmNordigenRequisitionRequest extends Request +class ConfirmNordigenRequest extends Request { /** * Determine if the user is authorized to make this request. diff --git a/app/Http/Requests/Nordigen/ConnectNordigenRequisitionRequest.php b/app/Http/Requests/Nordigen/ConnectNordigenRequest.php similarity index 94% rename from app/Http/Requests/Nordigen/ConnectNordigenRequisitionRequest.php rename to app/Http/Requests/Nordigen/ConnectNordigenRequest.php index 3584dbf51637..7d36e3fba102 100644 --- a/app/Http/Requests/Nordigen/ConnectNordigenRequisitionRequest.php +++ b/app/Http/Requests/Nordigen/ConnectNordigenRequest.php @@ -13,7 +13,7 @@ namespace App\Http\Requests\Nordigen; use App\Http\Requests\Request; -class ConnectNordigenRequisitionRequest extends Request +class ConnectNordigenRequest extends Request { /** * Determine if the user is authorized to make this request. From 701e5a9fec700d36cf018608614b7e3db2550489 Mon Sep 17 00:00:00 2001 From: paulwer Date: Fri, 8 Dec 2023 18:48:48 +0100 Subject: [PATCH 14/69] renaming again --- app/Http/Controllers/Bank/NordigenController.php | 12 ++++++------ ...php => ConfirmNordigenBankIntegrationRequest.php} | 2 +- ...php => ConnectNordigenBankIntegrationRequest.php} | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) rename app/Http/Requests/Nordigen/{ConfirmNordigenRequest.php => ConfirmNordigenBankIntegrationRequest.php} (92%) rename app/Http/Requests/Nordigen/{ConnectNordigenRequest.php => ConnectNordigenBankIntegrationRequest.php} (93%) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 0b2fdda3b11e..5198d1a5b743 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -13,8 +13,8 @@ namespace App\Http\Controllers\Bank; use App\Helpers\Bank\Nordigen\Nordigen; use App\Http\Controllers\BaseController; -use App\Http\Requests\Nordigen\ConfirmNordigenRequest; -use App\Http\Requests\Nordigen\ConnectNordigenRequest; +use App\Http\Requests\Nordigen\ConfirmNordigenBankIntegrationRequest; +use App\Http\Requests\Nordigen\ConnectNordigenBankIntegrationRequest; use App\Http\Requests\Yodlee\YodleeAuthRequest; use App\Jobs\Bank\ProcessBankTransactionsNordigen; use App\Models\BankIntegration; @@ -156,7 +156,7 @@ class NordigenController extends BaseController /** Creates a new requisition (oAuth like connection of bank-account) * - * @param ConnectNordigenRequest $request + * @param ConnectNordigenBankIntegrationRequest $request * * @OA\Post( * path="/api/v1/nordigen/institutions", @@ -218,7 +218,7 @@ class NordigenController extends BaseController } } }*/ - public function connect(ConnectNordigenRequest $request) + public function connect(ConnectNordigenBankIntegrationRequest $request) { $account = auth()->user()->account; @@ -252,7 +252,7 @@ class NordigenController extends BaseController /** * Process Nordigen Institutions GETTER. - * @param ConfirmNordigenRequest $request + * @param ConfirmNordigenBankIntegrationRequest $request * * @OA\Post( * path="/api/v1/nordigen/institutions", @@ -314,7 +314,7 @@ class NordigenController extends BaseController } } }*/ - public function confirm(ConfirmNordigenRequest $request) + public function confirm(ConfirmNordigenBankIntegrationRequest $request) { $data = $request->all(); diff --git a/app/Http/Requests/Nordigen/ConfirmNordigenRequest.php b/app/Http/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php similarity index 92% rename from app/Http/Requests/Nordigen/ConfirmNordigenRequest.php rename to app/Http/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php index 75b24e6e5b81..879410bb8bbc 100644 --- a/app/Http/Requests/Nordigen/ConfirmNordigenRequest.php +++ b/app/Http/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php @@ -13,7 +13,7 @@ namespace App\Http\Requests\Nordigen; use App\Http\Requests\Request; -class ConfirmNordigenRequest extends Request +class ConfirmNordigenBankIntegrationRequest extends Request { /** * Determine if the user is authorized to make this request. diff --git a/app/Http/Requests/Nordigen/ConnectNordigenRequest.php b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php similarity index 93% rename from app/Http/Requests/Nordigen/ConnectNordigenRequest.php rename to app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php index 7d36e3fba102..de249b18e207 100644 --- a/app/Http/Requests/Nordigen/ConnectNordigenRequest.php +++ b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php @@ -13,7 +13,7 @@ namespace App\Http\Requests\Nordigen; use App\Http\Requests\Request; -class ConnectNordigenRequest extends Request +class ConnectNordigenBankIntegrationRequest extends Request { /** * Determine if the user is authorized to make this request. From e349f1515d189853d46919ba8d9623ec7d7ef268 Mon Sep 17 00:00:00 2001 From: paulwer Date: Fri, 8 Dec 2023 19:05:49 +0100 Subject: [PATCH 15/69] minor fixes --- .../Controllers/Bank/NordigenController.php | 82 ++++--------------- 1 file changed, 14 insertions(+), 68 deletions(-) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 5198d1a5b743..8bb976c3ea27 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -25,60 +25,6 @@ use Log; class NordigenController extends BaseController { - - // TODO!!!!! - public function auth(YodleeAuthRequest $request) - { - - // create a user at this point - // use the one time token here to pull in the actual user - // store the user_account_id on the accounts table - - $nordigen = new Nordigen(); - - $company = $request->getCompany(); - - - //ensure user is enterprise!! - - if ($company->account->bank_integration_nordigen_secret_id && $company->account->bank_integration_nordigen_secret_id) { - - $flow = 'edit'; - - $token = $company->account->bank_integration_nordigen_secret_id; - - } else { - - $flow = 'add'; - - $response = $nordigen->createUser($company); - - $token = $response->user->loginName; - - $company->account->bank_integration_nordigen_secret_id = $token; - - $company->push(); - - } - - $yodlee = new Yodlee($token); - - if ($request->has('window_closed') && $request->input("window_closed") == "true") - $this->getAccounts($company, $token); - - $data = [ - 'access_token' => $yodlee->getAccessToken(), - 'fasttrack_url' => $yodlee->getFastTrackUrl(), - 'config_name' => config('ninja.yodlee.config_name'), - 'flow' => $flow, - 'company' => $company, - 'account' => $company->account, - 'completed' => $request->has('window_closed') ? true : false, - ]; - - return view('bank.yodlee.auth', $data); - - } /** * Process Nordigen Institutions GETTER. * @@ -379,11 +325,11 @@ class NordigenController extends BaseController // connect new accounts $bank_integration_ids = []; - foreach ($requisition["accounts"] as $accountId) { + foreach ($requisition["accounts"] as $nordigenAccountId) { - $account = $nordigen->getAccount($accountId); + $nordigen_account = $nordigen->getAccount($nordigenAccountId); - $existing_bank_integration = BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $company->id)->first(); + $existing_bank_integration = BankIntegration::where('bank_account_id', $nordigen_account['id'])->where('company_id', $company->id)->first(); if (!$existing_bank_integration) { @@ -392,16 +338,16 @@ class NordigenController extends BaseController $bank_integration->company_id = $company->id; $bank_integration->account_id = $company->account_id; $bank_integration->user_id = $company->owner()->id; - $bank_integration->bank_account_id = $account['id']; - $bank_integration->bank_account_type = $account['account_type']; - $bank_integration->bank_account_name = $account['account_name']; - $bank_integration->bank_account_status = $account['account_status']; - $bank_integration->bank_account_number = $account['account_number']; - $bank_integration->provider_id = $account['provider_id']; - $bank_integration->provider_name = $account['provider_name']; - $bank_integration->nickname = $account['nickname']; - $bank_integration->balance = $account['current_balance']; - $bank_integration->currency = $account['account_currency']; + $bank_integration->bank_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->provider_id = $nordigen_account['provider_id']; + $bank_integration->provider_name = $nordigen_account['provider_name']; + $bank_integration->nickname = $nordigen_account['nickname']; + $bank_integration->balance = $nordigen_account['current_balance']; + $bank_integration->currency = $nordigen_account['account_currency']; $bank_integration->disabled_upstream = false; $bank_integration->auto_sync = true; $bank_integration->from_date = now()->subYear(); @@ -426,7 +372,7 @@ class NordigenController extends BaseController } // perform update in background - $company->account->bank_integrations->each(function ($bank_integration) use ($company) { + $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function ($bank_integration) use ($company) { ProcessBankTransactionsNordigen::dispatch($company->account, $bank_integration); From c138f2f21107faf7c1767c78c8b795e475a753dc Mon Sep 17 00:00:00 2001 From: paulwer Date: Sat, 9 Dec 2023 09:27:59 +0100 Subject: [PATCH 16/69] rewrite connect/confirm for complete flow usage with only redirects --- app/Helpers/Bank/Nordigen/Nordigen.php | 50 +---- .../Transformer/IncomeTransformer.php | 183 ++++++------------ .../Controllers/Bank/NordigenController.php | 112 ++++------- .../ConnectNordigenBankIntegrationRequest.php | 28 ++- routes/api.php | 2 +- 5 files changed, 124 insertions(+), 251 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index e0718e01fb5a..1e8e23ac0dfc 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -16,6 +16,8 @@ namespace App\Helpers\Bank\Nordigen; use App\Exceptions\NordigenApiException; use App\Helpers\Bank\Nordigen\Transformer\AccountTransformer; use App\Helpers\Bank\Nordigen\Transformer\IncomeTransformer; +use Log; +use Nordigen\NordigenPHP\Exceptions\NordigenExceptions\NordigenException; class Nordigen { @@ -31,6 +33,7 @@ class Nordigen $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 @@ -56,52 +59,7 @@ class Nordigen return $this->client->requisition->getRequisition($requisitionId); } - // NOTE: this will only cleanup the requisitions from nordigen and not within the table: bank_integration_nordigen_requisitions - public function cleanupRequisitions() - { - $requisitions = $this->client->requisition->getRequisitions(); - - foreach ($requisitions as $requisition) { - // filter to expired OR older than 7 days created and no accounts - if ($requisition->status == "EXPIRED" || (sizeOf($requisition->accounts) != 0 && strtotime($requisition->created) > (new \DateTime())->modify('-7 days'))) - continue; - - $this->client->requisition->deleteRequisition($requisition->id); - } - } - - // account-section: these methods should be used to get data of connected accounts - public function getAccounts(?array $requisitionIds) - { - - // get all valid requisitions - $requisitions = $this->client->requisition->getRequisitions(); // no pagination used?! - - // fetch all valid accounts for activated requisitions - $nordigen_accountIds = []; - foreach ($requisitions["results"] as $requisition) { - // FILTER: for requisitionIds - if ($requisitionIds && !in_array($requisition["id"], $requisitionIds)) - continue; - - foreach ($requisition["accounts"] as $accountId) { - array_push($nordigen_accountIds, $accountId); - } - } - - $nordigen_accountIds = array_unique($nordigen_accountIds); - - $nordigen_accounts = []; - foreach ($nordigen_accountIds as $accountId) { - $nordigen_account = $this->getAccount($accountId); - - array_push($nordigen_accounts, $nordigen_account); - } - - return $nordigen_accounts; - - } - + // TODO: return null on not found public function getAccount(string $account_id) { diff --git a/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php index 3de6c3e7680f..43476923a4ee 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php @@ -12,151 +12,77 @@ namespace App\Helpers\Bank\Nordigen\Transformer; use App\Helpers\Bank\BankRevenueInterface; +use App\Models\BankIntegration; use App\Utils\Traits\AppSetup; use Illuminate\Support\Facades\Cache; /** -"date": "string", -"sourceId": "string", -"symbol": "string", -"cusipNumber": "string", -"highLevelCategoryId": 0, -"detailCategoryId": 0, -"description": {}, -"memo": "string", -"settleDate": "string", -"type": "string", -"intermediary": [], -"baseType": "CREDIT", -"categorySource": "SYSTEM", -"principal": {}, -"lastUpdated": "string", -"interest": {}, -"price": {}, -"commission": {}, -"id": 0, -"merchantType": "string", -"amount": { -"amount": 0, -"convertedAmount": 0, -"currency": "USD", -"convertedCurrency": "USD" -}, -"checkNumber": "string", -"isPhysical": true, -"quantity": 0, -"valoren": "string", -"isManual": true, -"merchant": { -"website": "string", -"address": {}, -"contact": {}, -"categoryLabel": [], -"coordinates": {}, -"name": "string", -"id": "string", -"source": "YODLEE", -"logoURL": "string" -}, -"sedol": "string", -"transactionDate": "string", -"categoryType": "TRANSFER", -"accountId": 0, -"createdDate": "string", -"sourceType": "AGGREGATED", -"CONTAINER": "bank", -"postDate": "string", -"parentCategoryId": 0, -"subType": "OVERDRAFT_CHARGE", -"category": "string", -"runningBalance": {}, -"categoryId": 0, -"holdingDescription": "string", -"isin": "string", -"status": "POSTED" - -( -[CONTAINER] => bank -[id] => 103953585 -[amount] => stdClass Object - ( - [amount] => 480.66 - [currency] => USD - ) - -[categoryType] => UNCATEGORIZE -[categoryId] => 1 -[category] => Uncategorized -[categorySource] => SYSTEM -[highLevelCategoryId] => 10000017 -[createdDate] => 2022-08-04T21:50:17Z -[lastUpdated] => 2022-08-04T21:50:17Z -[description] => stdClass Object - ( - [original] => CHEROKEE NATION TAX TA TAHLEQUAH OK - ) - -[isManual] => -[sourceType] => AGGREGATED -[date] => 2022-08-03 -[transactionDate] => 2022-08-03 -[postDate] => 2022-08-03 -[status] => POSTED -[accountId] => 12331794 -[runningBalance] => stdClass Object - ( - [amount] => 480.66 - [currency] => USD - ) - -[checkNumber] => 998 -) +{ + "transactions": { + "booked": [ + { + "transactionId": "string", + "debtorName": "string", + "debtorAccount": { + "iban": "string" + }, + "transactionAmount": { + "currency": "string", + "amount": "328.18" + }, + "bankTransactionCode": "string", + "bookingDate": "date", + "valueDate": "date", + "remittanceInformationUnstructured": "string" + }, + { + "transactionId": "string", + "transactionAmount": { + "currency": "string", + "amount": "947.26" + }, + "bankTransactionCode": "string", + "bookingDate": "date", + "valueDate": "date", + "remittanceInformationUnstructured": "string" + } + ], + "pending": [ + { + "transactionAmount": { + "currency": "string", + "amount": "99.20" + }, + "valueDate": "date", + "remittanceInformationUnstructured": "string" + } + ] + } +} */ class IncomeTransformer implements BankRevenueInterface { use AppSetup; - public function transform($transaction) + public function transform(BankIntegration $bank_integration, $transaction) { - $data = []; - - if (!property_exists($transaction, 'transaction')) - return $data; - - foreach ($transaction->transaction as $transaction) { - $data[] = $this->transformTransaction($transaction); - } - - return $data; - } - - public function transformTransaction($transaction) - { + if (!property_exists($transaction, 'transactionId') || !property_exists($transaction, 'transactionAmount') || !property_exists($transaction, 'balances') || !property_exists($transaction, 'institution')) + throw new \Exception('invalid dataset'); return [ - 'transaction_id' => $transaction->id, - 'amount' => $transaction->amount->amount, - 'currency_id' => $this->convertCurrency($transaction->amount->currency), - 'account_type' => $transaction->CONTAINER, + 'transaction_id' => $transaction->transactionId, + 'amount' => abs($transaction->transactionAmount->amount), + 'currency_id' => $this->convertCurrency($transaction->transactionAmount->currency), + 'account_type' => 'bank', 'category_id' => $transaction->highLevelCategoryId, 'category_type' => $transaction->categoryType, - 'date' => $transaction->date, - 'bank_account_id' => $transaction->accountId, - 'description' => $transaction->description->original, - 'base_type' => property_exists($transaction, 'baseType') ? $transaction->baseType : $this->calculateBaseType($transaction), + 'date' => $transaction->bookingDate, + 'bank_account_id' => $bank_integration->id, + 'description' => $transaction->remittanceInformationUnstructured, + 'base_type' => $transaction->transactionAmount->amount > 0 ? 'DEBIT' : 'CREDIT', ]; - } - - private function calculateBaseType($transaction) - { - //CREDIT / DEBIT - - if (property_exists($transaction, 'highLevelCategoryId') && $transaction->highLevelCategoryId == 10000012) - return 'CREDIT'; - - return 'DEBIT'; } @@ -180,7 +106,6 @@ class IncomeTransformer implements BankRevenueInterface } - } diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 8bb976c3ea27..9d5089c8e0a2 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -22,6 +22,7 @@ use App\Models\Company; use Cache; use Illuminate\Http\Request; use Log; +use Nordigen\NordigenPHP\Exceptions\NordigenExceptions\NordigenException; class NordigenController extends BaseController { @@ -166,34 +167,42 @@ class NordigenController extends BaseController }*/ public function connect(ConnectNordigenBankIntegrationRequest $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 Nordigen Bank Integration service'], 400); - $data = $request->all(); - $context = Cache::get($data["hash"]); - Log::info($context); - if (!$context || $context["context"] != "nordigen" || array_key_exists("requisition", $context)) // TODO: check for requisition array key - return response()->json(['message' => 'Invalid context one_time_token. (not-found|invalid-context|already-used) Call /api/v1/one_time_token with context: \'nordigen\' first.'], 400); + $context = Cache::get($data["one_time_token"]); - Log::info(config('ninja.app_url') . '/api/v1/nordigen/confirm'); + if (!$context || $context["context"] != "nordigen" || array_key_exists("requisitionId", $context)) + return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=one-time-token-invalid"); + + $company = Company::where('company_key', $context["company_key"])->first(); + $account = $company->account; + + if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) + return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid"); $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); - $requisition = $nordigen->createRequisition(config('ninja.app_url') . '/api/v1/nordigen/confirm', $data['institutionId'], $data["hash"]); + try { + $requisition = $nordigen->createRequisition(config('ninja.app_url') . '/api/v1/nordigen/confirm', $data['institution_id'], "1"); + } 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 + Log::error($e); + $responseBody = $e->getResponse()->getBody(); + Log::info($responseBody); + + if (property_exists($responseBody, "institution_id")) // provided institution_id was wrong + return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=institution-invalid"); + else if (property_exists($responseBody, "reference")) // this error can occur, when a reference was used double or is invalid => therefor we suggest the frontend to use another one-time-token + return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=one-time-token-invalid"); + else + return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=unknown"); + } // save cache - if (array_key_exists("redirectUri", $data)) - $context["redirectUri"] = $data["redirectUri"]; + if (array_key_exists("redirect", $data)) + $context["redirect"] = $data["redirect"]; $context["requisitionId"] = $requisition["id"]; - Cache::put($data["hash"], $context, 3600); - - return response()->json([ - 'result' => $requisition, - 'redirectUri' => array_key_exists("redirectUri", $data) ? $data["redirectUri"] : null, - ]); + Cache::put($data["one_time_token"], $context, 3600); + return response()->redirectTo($requisition["link"]); } /** @@ -266,62 +275,27 @@ class NordigenController extends BaseController $data = $request->all(); $context = Cache::get($data["ref"]); - if (!$context || $context["context"] != "nordigen" || !array_key_exists("requisitionId", $context)) { - if ($context && array_key_exists("redirectUri", $context)) - return response()->redirectTo($context["redirectUri"] . "?action=nordigen_connect&status=failed&reason=ref-invalid"); + if (!$context || $context["context"] != "nordigen" || !array_key_exists("requisitionId", $context)) + return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=ref-invalid"); - return response()->json([ - 'status' => 'failed', - 'reason' => 'ref-invalid', - ], 400); - } $company = Company::where('company_key', $context["company_key"])->first(); $account = $company->account; - if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) { - if (array_key_exists("redirectUri", $context)) - return response()->redirectTo($context["redirectUri"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid"); - - return response()->json([ - 'status' => 'failed', - 'reason' => 'account-config-invalid', - ], 400); - } + if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) + return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid"); // fetch requisition $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); $requisition = $nordigen->getRequisition($context["requisitionId"]); // check validity of requisition - if (!$requisition) { - if (array_key_exists("redirectUri", $context)) - return response()->redirectTo($context["redirectUri"] . "?action=nordigen_connect&status=failed&reason=requisition-not-found"); - - return response()->json([ - 'status' => 'failed', - 'reason' => 'requisition-not-found', - ], 400); - } - if ($requisition["status"] != "LN") { - if (array_key_exists("redirectUri", $context)) - return response()->redirectTo($context["redirectUri"] . "?action=nordigen_connect&status=failed&reason=requisition-invalid-status"); - - return response()->json([ - 'status' => 'failed', - 'reason' => 'requisition-invalid-status', - ], 400); - } - if (sizeof($requisition["accounts"]) == 0) { - if (array_key_exists("redirectUri", $context)) - return response()->redirectTo($context["redirectUri"] . "?action=nordigen_connect&status=failed&reason=requisition-no-accounts"); - - return response()->json([ - 'status' => 'failed', - 'reason' => 'requisition-no-accounts', - ], 400); - } - + if (!$requisition) + return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-not-found"); + if ($requisition["status"] != "LN") + return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-invalid-status"); + if (sizeof($requisition["accounts"]) == 0) + return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-no-accounts"); // connect new accounts $bank_integration_ids = []; @@ -381,14 +355,8 @@ class NordigenController extends BaseController // prevent rerun of this method with same ref Cache::delete($data["ref"]); - // Successfull Response - if (array_key_exists("redirectUri", $context)) - return response()->redirectTo($context["redirectUri"] . "?action=nordigen_connect&status=success&bank_integrations=" . implode(',', $bank_integration_ids)); - - return response()->json([ - 'status' => 'success', - 'bank_integrations' => $bank_integration_ids, - ]); + // Successfull Response => Redirect + return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=success&bank_integrations=" . implode(',', $bank_integration_ids)); } diff --git a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php index de249b18e207..639e7c0c546f 100644 --- a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php +++ b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php @@ -12,6 +12,8 @@ namespace App\Http\Requests\Nordigen; use App\Http\Requests\Request; +use Cache; +use Log; class ConnectNordigenBankIntegrationRequest extends Request { @@ -33,9 +35,29 @@ class ConnectNordigenBankIntegrationRequest extends Request public function rules() { return [ - 'institutionId' => 'required|string', - 'hash' => 'required|string', // One Time Token - 'redirectUri' => 'string', // TODO: @turbo124 @todo validate, that this is a url without / at the end + 'institution_id' => 'required|string', + 'one_time_token' => 'required|string', // One Time Token + 'redirect' => 'string', // TODO: @turbo124 @todo validate, that this is a url without / at the end ]; } + + // @turbo124 @todo please check for validity, when issue request from frontend + public function prepareForValidation() + { + $input = $this->all(); + + if (!array_key_exists('redirect', $input)) { + $context = Cache::get($input['one_time_token']); + + if (array_key_exists('is_react', $context)) + $input["redirect"] = $context["is_react"] ? config("ninja.react_url") : config("ninja.app_url"); + else + $input["redirect"] = config("ninja.app_url"); + + Log::info($input); + + $this->replace($input); + + } + } } diff --git a/routes/api.php b/routes/api.php index c9202f65fb54..3cd306fdaa4d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -378,7 +378,7 @@ Route::post('api/v1/yodlee/refresh_updates', [YodleeController::class, 'refreshU Route::post('api/v1/yodlee/balance', [YodleeController::class, 'balanceWebhook'])->middleware('throttle:100,1'); Route::get('api/v1/nordigen/institutions', [NordigenController::class, 'institutions'])->middleware('throttle:100,1')->middleware('token_auth')->name('nordigen_institutions'); -Route::post('api/v1/nordigen/connect', [NordigenController::class, 'connect'])->middleware('throttle:100,1')->middleware('token_auth')->name('nordigen_connect'); +Route::any('api/v1/nordigen/connect', [NordigenController::class, 'connect'])->middleware('throttle:100,1')->name('nordigen_connect'); Route::any('api/v1/nordigen/confirm', [NordigenController::class, 'confirm'])->middleware('throttle:100,1')->name('nordigen_callback'); Route::fallback([BaseController::class, 'notFound']); From f3dfdc4d800e6520dd96c8f906788c8555f9e507 Mon Sep 17 00:00:00 2001 From: paulwer Date: Sat, 9 Dec 2023 15:13:00 +0100 Subject: [PATCH 17/69] introduce nordigen bank_selection ui --- .../Controllers/Bank/NordigenController.php | 250 +++++++----------- .../ConnectNordigenBankIntegrationRequest.php | 39 ++- .../views/bank/nordigen/connect.blade.php | 67 +++++ routes/api.php | 1 - routes/web.php | 6 +- 5 files changed, 204 insertions(+), 159 deletions(-) create mode 100644 resources/views/bank/nordigen/connect.blade.php diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 9d5089c8e0a2..1e10f3ae8c2c 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -15,7 +15,6 @@ use App\Helpers\Bank\Nordigen\Nordigen; use App\Http\Controllers\BaseController; use App\Http\Requests\Nordigen\ConfirmNordigenBankIntegrationRequest; use App\Http\Requests\Nordigen\ConnectNordigenBankIntegrationRequest; -use App\Http\Requests\Yodlee\YodleeAuthRequest; use App\Jobs\Bank\ProcessBankTransactionsNordigen; use App\Models\BankIntegration; use App\Models\Company; @@ -26,163 +25,39 @@ use Nordigen\NordigenPHP\Exceptions\NordigenExceptions\NordigenException; class NordigenController extends BaseController { - /** - * 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 institutions(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 Nordigen Bank Integration service'], 400); - - $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); - return response()->json($nordigen->getInstitutions()); - } - - /** Creates a new requisition (oAuth like connection of bank-account) - * - * @param ConnectNordigenBankIntegrationRequest $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(ConnectNordigenBankIntegrationRequest $request) { $data = $request->all(); - - $context = Cache::get($data["one_time_token"]); + $context = $request->getTokenContent(); if (!$context || $context["context"] != "nordigen" || array_key_exists("requisitionId", $context)) - return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=one-time-token-invalid"); + return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=token-invalid"); - $company = Company::where('company_key', $context["company_key"])->first(); - $account = $company->account; + $company = $request->getCompany(); - if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) + if (!$company->account->bank_integration_nordigen_secret_id || !$company->account->bank_integration_nordigen_secret_key) return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid"); - $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); + $nordigen = new Nordigen($company->account->bank_integration_nordigen_secret_id, $company->account->bank_integration_nordigen_secret_key); + + // show bank_selection_screen, when institution_id is not present + if (!array_key_exists("institution_id", $data)) { + $data = [ + 'token' => $request->token, + 'context' => $context, + 'institutions' => $nordigen->getInstitutions(), + 'company' => $company, + 'account' => $company->account, + 'redirect' => config('ninja.app_url') . '/nordigen/connect', + ]; + + return view('bank.nordigen.connect', $data); + } + + // redirect to requisition flow try { - $requisition = $nordigen->createRequisition(config('ninja.app_url') . '/api/v1/nordigen/confirm', $data['institution_id'], "1"); + $requisition = $nordigen->createRequisition(config('ninja.app_url') . '/api/v1/nordigen/confirm', $data['institution_id'], $request->token); } 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 Log::error($e); $responseBody = $e->getResponse()->getBody(); @@ -190,8 +65,8 @@ class NordigenController extends BaseController if (property_exists($responseBody, "institution_id")) // provided institution_id was wrong return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=institution-invalid"); - else if (property_exists($responseBody, "reference")) // this error can occur, when a reference was used double or is invalid => therefor we suggest the frontend to use another one-time-token - return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=one-time-token-invalid"); + else if (property_exists($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 response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=token-invalid"); else return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=unknown"); } @@ -200,7 +75,7 @@ class NordigenController extends BaseController if (array_key_exists("redirect", $data)) $context["redirect"] = $data["redirect"]; $context["requisitionId"] = $requisition["id"]; - Cache::put($data["one_time_token"], $context, 3600); + Cache::put($request->token, $context, 3600); return response()->redirectTo($requisition["link"]); } @@ -360,4 +235,79 @@ class NordigenController extends BaseController } + /** + * 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 institutions(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 Nordigen Bank Integration service'], 400); + + $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); + return response()->json($nordigen->getInstitutions()); + } + } diff --git a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php index 639e7c0c546f..a35f9b7ab698 100644 --- a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php +++ b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php @@ -12,6 +12,9 @@ namespace App\Http\Requests\Nordigen; use App\Http\Requests\Request; +use App\Libraries\MultiDB; +use App\Models\Company; +use App\Models\User; use Cache; use Log; @@ -35,8 +38,7 @@ class ConnectNordigenBankIntegrationRequest extends Request public function rules() { return [ - 'institution_id' => 'required|string', - 'one_time_token' => 'required|string', // One Time Token + 'institution_id' => 'string', 'redirect' => 'string', // TODO: @turbo124 @todo validate, that this is a url without / at the end ]; } @@ -47,17 +49,42 @@ class ConnectNordigenBankIntegrationRequest extends Request $input = $this->all(); if (!array_key_exists('redirect', $input)) { - $context = Cache::get($input['one_time_token']); + $context = $this->getTokenContent(); - if (array_key_exists('is_react', $context)) + if ($context && array_key_exists('is_react', $context)) $input["redirect"] = $context["is_react"] ? config("ninja.react_url") : config("ninja.app_url"); else $input["redirect"] = config("ninja.app_url"); - Log::info($input); - $this->replace($input); } } + public function getTokenContent() + { + if ($this->state) { + $this->token = $this->state; + } + + $data = Cache::get($this->token); + + return $data; + } + + public function getContact() + { + MultiDB::findAndSetDbByCompanyKey($this->getTokenContent()['company_key']); + + return User::findOrFail($this->getTokenContent()['user_id']); + + } + + public function getCompany() + { + + MultiDB::findAndSetDbByCompanyKey($this->getTokenContent()['company_key']); + + return Company::where('company_key', $this->getTokenContent()['company_key'])->firstOrFail(); + + } } diff --git a/resources/views/bank/nordigen/connect.blade.php b/resources/views/bank/nordigen/connect.blade.php new file mode 100644 index 000000000000..5eafe708f3b4 --- /dev/null +++ b/resources/views/bank/nordigen/connect.blade.php @@ -0,0 +1,67 @@ +@extends('layouts.ninja') +@section('meta_title', ctrans('texts.new_bank_account')) + +@push('head') + + + +@endpush + +@section('body') + +
+ +@endsection + +@push('footer') + + + + + +@endpush \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 3cd306fdaa4d..bd50851abd27 100644 --- a/routes/api.php +++ b/routes/api.php @@ -378,7 +378,6 @@ Route::post('api/v1/yodlee/refresh_updates', [YodleeController::class, 'refreshU Route::post('api/v1/yodlee/balance', [YodleeController::class, 'balanceWebhook'])->middleware('throttle:100,1'); Route::get('api/v1/nordigen/institutions', [NordigenController::class, 'institutions'])->middleware('throttle:100,1')->middleware('token_auth')->name('nordigen_institutions'); -Route::any('api/v1/nordigen/connect', [NordigenController::class, 'connect'])->middleware('throttle:100,1')->name('nordigen_connect'); Route::any('api/v1/nordigen/confirm', [NordigenController::class, 'confirm'])->middleware('throttle:100,1')->name('nordigen_callback'); Route::fallback([BaseController::class, 'notFound']); diff --git a/routes/web.php b/routes/web.php index 26ddc312bec6..32bf59ee956b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,6 +3,7 @@ use App\Http\Controllers\Auth\ForgotPasswordController; use App\Http\Controllers\Auth\LoginController; use App\Http\Controllers\Auth\ResetPasswordController; +use App\Http\Controllers\Bank\NordigenController; use App\Http\Controllers\Bank\YodleeController; use App\Http\Controllers\BaseController; use App\Http\Controllers\ClientPortal\ApplePayDomainController; @@ -18,7 +19,7 @@ use Illuminate\Support\Facades\Route; //Auth::routes(['password.reset' => false]); Route::get('/', [BaseController::class, 'flutterRoute'])->middleware('guest'); - // Route::get('self-update', [SelfUpdateController::class, 'update'])->middleware('guest'); +// Route::get('self-update', [SelfUpdateController::class, 'update'])->middleware('guest'); Route::get('setup', [SetupController::class, 'index'])->middleware('guest'); Route::post('setup', [SetupController::class, 'doSetup'])->middleware('guest'); @@ -54,8 +55,9 @@ Route::get('stripe/signup/{token}', [StripeConnectController::class, 'initialize Route::get('stripe/completed', [StripeConnectController::class, 'completed'])->name('stripe_connect.return'); Route::get('yodlee/onboard/{token}', [YodleeController::class, 'auth'])->name('yodlee.auth'); +Route::get('nordigen/connect/{token}', [NordigenController::class, 'connect'])->name('nordigen.connect'); Route::get('checkout/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', [Checkout3dsController::class, 'index'])->middleware('domain_db')->name('checkout.3ds_redirect'); Route::get('mollie/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', [Mollie3dsController::class, 'index'])->middleware('domain_db')->name('mollie.3ds_redirect'); Route::get('gocardless/ibp_redirect/{company_key}/{company_gateway_id}/{hash}', [GoCardlessController::class, 'ibpRedirect'])->middleware('domain_db')->name('gocardless.ibp_redirect'); -Route::get('.well-known/apple-developer-merchantid-domain-association', [ApplePayDomainController::class, 'showAppleMerchantId']); \ No newline at end of file +Route::get('.well-known/apple-developer-merchantid-domain-association', [ApplePayDomainController::class, 'showAppleMerchantId']); From fa5edbc29cd56ce17bae6b8afbae0f0b481f1c43 Mon Sep 17 00:00:00 2001 From: paulwer Date: Sat, 9 Dec 2023 17:16:01 +0100 Subject: [PATCH 18/69] latest changes --- app/Helpers/Bank/Nordigen/Nordigen.php | 9 +++++++- .../Transformer/IncomeTransformer.php | 23 +++++++++++++++---- .../Bank/ProcessBankTransactionsNordigen.php | 9 +------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index 1e8e23ac0dfc..34b79a3da2db 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -114,10 +114,17 @@ class Nordigen } + /** + * this method returns booked transactions from the bank_account, pending transactions are not part of the result + * @todo @turbo124 should we include pending transactions within the integration-process and mark them with a specific category?! + */ public function getTransactions(string $accountId, string $dateFrom = null) { - return $this->client->account($accountId)->getAccountTransactions($dateFrom); + $transactionResponse = $this->client->account($accountId)->getAccountTransactions($dateFrom); + + $it = new IncomeTransformer(); + return $it->transform($transactionResponse); } } diff --git a/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php index 43476923a4ee..bb6961e3c5cc 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php @@ -65,7 +65,22 @@ class IncomeTransformer implements BankRevenueInterface { use AppSetup; - public function transform(BankIntegration $bank_integration, $transaction) + public function transform($transaction) + { + + $data = []; + + if (!property_exists($transaction, 'transactions') || !property_exists($transaction->transactions, 'booked')) + throw new \Exception('invalid dataset'); + + foreach ($transaction->transactions->booked as $transaction) { + $data[] = $this->transformTransaction($transaction); + } + + return $data; + } + + public function transformTransaction($transaction) { if (!property_exists($transaction, 'transactionId') || !property_exists($transaction, 'transactionAmount') || !property_exists($transaction, 'balances') || !property_exists($transaction, 'institution')) @@ -75,11 +90,9 @@ class IncomeTransformer implements BankRevenueInterface 'transaction_id' => $transaction->transactionId, 'amount' => abs($transaction->transactionAmount->amount), 'currency_id' => $this->convertCurrency($transaction->transactionAmount->currency), - 'account_type' => 'bank', - 'category_id' => $transaction->highLevelCategoryId, - 'category_type' => $transaction->categoryType, + 'category_id' => $transaction->highLevelCategoryId, // TODO + 'category_type' => $transaction->categoryType, // TODO 'date' => $transaction->bookingDate, - 'bank_account_id' => $bank_integration->id, 'description' => $transaction->remittanceInformationUnstructured, 'base_type' => $transaction->transactionAmount->amount > 0 ? 'DEBIT' : 'CREDIT', ]; diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index 0ef3f9dfee96..b976030dff9f 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -99,18 +99,11 @@ class ProcessBankTransactionsNordigen implements ShouldQueue return; } - $data = [ - 'top' => 500, - 'fromDate' => $this->from_date, - 'accountId' => $this->bank_integration->bank_account_id, - 'skip' => $this->skip, - ]; - //Get transaction count object $transactions = $nordigen->getTransactions($this->bank_integration->bank_account_id, $this->from_date); //Get int count - $count = sizeof($transactions->transactions->booked); + $count = sizeof($transactions); //if no transactions, update the from_date and move on if (count($transactions) == 0) { From a244faaaee29de49637ee57432fd3830fc41038f Mon Sep 17 00:00:00 2001 From: paulwer Date: Sat, 9 Dec 2023 18:39:10 +0100 Subject: [PATCH 19/69] minor additions for IncomeTransformer --- app/Helpers/Bank/Nordigen/Nordigen.php | 22 --------------- .../Transformer/AccountTransformer.php | 20 +++++++------ .../Transformer/IncomeTransformer.php | 28 +++++++++++-------- ...3_11_26_082959_add_bank_integration_id.php | 2 ++ 4 files changed, 29 insertions(+), 43 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index 34b79a3da2db..1ddbe3e80fc8 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -92,28 +92,6 @@ class Nordigen } - /** - * this method will remove all according requisitions => this can result in removing multiple accounts, if a user reuses a requisition - */ - public function deleteAccount(string $account_id) - { - - // get all valid requisitions - $requisitions = $this->client->requisition->getRequisitions(); - - // fetch all valid accounts for activated requisitions - foreach ($requisitions as $requisition) { - foreach ($requisition->accounts as $accountId) { - - if ($accountId) { - $this->client->requisition->deleteRequisition($accountId); - } - - } - } - - } - /** * this method returns booked transactions from the bank_account, pending transactions are not part of the result * @todo @turbo124 should we include pending transactions within the integration-process and mark them with a specific category?! diff --git a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php index fcf81cee3352..1a164bdf991a 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php @@ -83,33 +83,35 @@ use App\Helpers\Bank\AccountTransformerInterface; */ -class AccountTransformer implements AccountTransformerInterface { +class AccountTransformer implements AccountTransformerInterface +{ - public function transform($nordigen_account) { + public function transform($nordigen_account) + { - if(!property_exists($nordigen_account, 'data') || !property_exists($nordigen_account, 'metadata') || !property_exists($nordigen_account, 'balances') || !property_exists($nordigen_account, 'institution')) + if (!property_exists($nordigen_account, 'data') || !property_exists($nordigen_account, 'metadata') || !property_exists($nordigen_account, 'balances') || !property_exists($nordigen_account, 'institution')) throw new \Exception('invalid dataset'); $used_balance = $nordigen_account->balances[0]; // prefer entry with closingBooked - foreach($nordigen_account->balances as $entry) { - if($entry["balanceType"] === 'closingBooked') { // available: closingBooked, interimAvailable + foreach ($nordigen_account->balances as $entry) { + if ($entry["balanceType"] === 'closingBooked') { // available: closingBooked, interimAvailable $used_balance = $entry; break; } } return [ - 'id' => $nordigen_account->metadata["id"], + 'id' => 'nordigen:' . $nordigen_account->metadata["id"], 'account_type' => "bank", 'account_name' => $nordigen_account->data["iban"], 'account_status' => $nordigen_account->metadata["status"], - 'account_number' => '**** '.substr($nordigen_account->data["iban"], -7), - 'provider_account_id' => $nordigen_account->data["iban"], + 'account_number' => '**** ' . substr($nordigen_account->data["iban"], -7), + 'provider_account_id' => $nordigen_account->metadata["id"], 'provider_id' => $nordigen_account->institution["id"], 'provider_name' => $nordigen_account->institution["name"], 'nickname' => $nordigen_account->data["ownerName"] ? $nordigen_account->data["ownerName"] : '', - 'current_balance' => (int)$used_balance ? $used_balance["balanceAmount"]["amount"] : 0, + 'current_balance' => (int) $used_balance ? $used_balance["balanceAmount"]["amount"] : 0, 'account_currency' => $used_balance ? $used_balance["balanceAmount"]["currency"] : '', ]; diff --git a/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php index bb6961e3c5cc..5105ce33c3a4 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/IncomeTransformer.php @@ -15,6 +15,7 @@ use App\Helpers\Bank\BankRevenueInterface; use App\Models\BankIntegration; use App\Utils\Traits\AppSetup; use Illuminate\Support\Facades\Cache; +use Log; /** { @@ -65,15 +66,16 @@ class IncomeTransformer implements BankRevenueInterface { use AppSetup; - public function transform($transaction) + public function transform($transactionResponse) { + Log::info($transactionResponse); $data = []; - if (!property_exists($transaction, 'transactions') || !property_exists($transaction->transactions, 'booked')) + if (!array_key_exists('transactions', $transactionResponse) || !array_key_exists('booked', $transactionResponse["transactions"])) throw new \Exception('invalid dataset'); - foreach ($transaction->transactions->booked as $transaction) { + foreach ($transactionResponse["transactions"]["booked"] as $transaction) { $data[] = $this->transformTransaction($transaction); } @@ -83,18 +85,20 @@ class IncomeTransformer implements BankRevenueInterface public function transformTransaction($transaction) { - if (!property_exists($transaction, 'transactionId') || !property_exists($transaction, 'transactionAmount') || !property_exists($transaction, 'balances') || !property_exists($transaction, 'institution')) + if (!array_key_exists('transactionId', $transaction) || !array_key_exists('transactionAmount', $transaction)) throw new \Exception('invalid dataset'); return [ - 'transaction_id' => $transaction->transactionId, - 'amount' => abs($transaction->transactionAmount->amount), - 'currency_id' => $this->convertCurrency($transaction->transactionAmount->currency), - 'category_id' => $transaction->highLevelCategoryId, // TODO - 'category_type' => $transaction->categoryType, // TODO - 'date' => $transaction->bookingDate, - 'description' => $transaction->remittanceInformationUnstructured, - 'base_type' => $transaction->transactionAmount->amount > 0 ? 'DEBIT' : 'CREDIT', + 'transaction_id' => 'nordigen:' . $transaction["transactionId"], + 'amount' => abs((int) $transaction["transactionAmount"]["amount"]), + 'currency_id' => $this->convertCurrency($transaction["transactionAmount"]["currency"]), + 'category_id' => 0, // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc + 'category_type' => $transaction["additionalInformation"], // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc + 'date' => $transaction["bookingDate"], + 'description' => array_key_exists('bank_remittanceInformationStructured', $transaction) ? $transaction["bank_remittanceInformationStructured"] : array_key_exists('bank_remittanceInformationStructuredArray', $transaction) ? implode($transaction["bank_remittanceInformationStructured"], '\r\n') : '', + // 'description' => `IBAN: ${elem . json["bank_debtorAccount"] && elem . json["bank_debtorAccount"]["iban"] ? elem . json["bank_debtorAccount"]["iban"] : ' -'}\nVerwendungszweck: ${elem . json["bank_remittanceInformationStructured"] || ' -'}\nName: ${elem . json["bank_debtorName"] || ' -'}`, // 2 fields to get data from (structured and structuredArray (have to be joined)) + // TODO: debitor name & iban & bic + 'base_type' => (int) $transaction["transactionAmount"]["amount"] > 0 ? 'DEBIT' : 'CREDIT', ]; } diff --git a/database/migrations/2023_11_26_082959_add_bank_integration_id.php b/database/migrations/2023_11_26_082959_add_bank_integration_id.php index 1c5de199a325..6189012da8de 100644 --- a/database/migrations/2023_11_26_082959_add_bank_integration_id.php +++ b/database/migrations/2023_11_26_082959_add_bank_integration_id.php @@ -16,6 +16,8 @@ return new class extends Migration { { Schema::table('bank_integrations', function (Blueprint $table) { $table->string('integration_type')->nullable(); + // $table->string('provider_id'); // migrate to string, because nordigen provides a string like: SANDBOXFINANCE_SFIN0000 + // $table->string('bank_account_id'); // migrate to string, because nordigen uses uuid() strings }); // migrate old account to be used with yodlee From 2206d2190a2627266b0d5775b7e67e9082bb058e Mon Sep 17 00:00:00 2001 From: paulwer Date: Sun, 10 Dec 2023 08:23:53 +0100 Subject: [PATCH 20/69] changes related to merge --- app/Models/BankIntegration.php | 3 --- routes/api.php | 6 +----- routes/web.php | 1 + 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/Models/BankIntegration.php b/app/Models/BankIntegration.php index c70d78be5a1b..04e5bc9245a9 100644 --- a/app/Models/BankIntegration.php +++ b/app/Models/BankIntegration.php @@ -73,9 +73,6 @@ class BankIntegration extends BaseModel 'auto_sync', ]; - protected $dates = [ - ]; - const INTEGRATION_TYPE_YODLEE = 'YODLEE'; const INTEGRATION_TYPE_NORDIGEN = 'NORDIGEN'; diff --git a/routes/api.php b/routes/api.php index ee39dc7b2824..cca9161dc91e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -121,7 +121,7 @@ Route::group(['middleware' => ['throttle:api', 'api_secret_check']], function () Route::post('api/v1/oauth_login', [LoginController::class, 'oauthApiLogin']); }); -Route::group(['middleware' => ['throttle:login','api_secret_check','email_db']], function () { +Route::group(['middleware' => ['throttle:login', 'api_secret_check', 'email_db']], function () { Route::post('api/v1/login', [LoginController::class, 'apiLogin'])->name('login.submit'); Route::post('api/v1/reset_password', [ForgotPasswordController::class, 'sendResetLinkEmail']); }); @@ -427,13 +427,9 @@ Route::post('api/v1/yodlee/data_updates', [YodleeController::class, 'dataUpdates 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'); -<<<<<<< HEAD Route::get('api/v1/nordigen/institutions', [NordigenController::class, 'institutions'])->middleware('throttle:100,1')->middleware('token_auth')->name('nordigen_institutions'); Route::any('api/v1/nordigen/confirm', [NordigenController::class, 'confirm'])->middleware('throttle:100,1')->name('nordigen_callback'); -Route::fallback([BaseController::class, 'notFound']); -======= Route::get('api/v1/protected_download/{hash}', [ProtectedDownloadController::class, 'index'])->name('protected_download')->middleware('throttle:300,1'); Route::fallback([BaseController::class, 'notFound'])->middleware('throttle:404'); ->>>>>>> v5-develop diff --git a/routes/web.php b/routes/web.php index 32bf59ee956b..c47c9e139332 100644 --- a/routes/web.php +++ b/routes/web.php @@ -55,6 +55,7 @@ Route::get('stripe/signup/{token}', [StripeConnectController::class, 'initialize Route::get('stripe/completed', [StripeConnectController::class, 'completed'])->name('stripe_connect.return'); Route::get('yodlee/onboard/{token}', [YodleeController::class, 'auth'])->name('yodlee.auth'); + Route::get('nordigen/connect/{token}', [NordigenController::class, 'connect'])->name('nordigen.connect'); Route::get('checkout/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', [Checkout3dsController::class, 'index'])->middleware('domain_db')->name('checkout.3ds_redirect'); From f8d0186f2f3e28c801ac37c2604749aac912fd86 Mon Sep 17 00:00:00 2001 From: paulwer Date: Sun, 10 Dec 2023 16:09:23 +0100 Subject: [PATCH 21/69] changes to job-schedule --- app/Console/Kernel.php | 10 +++++----- app/Jobs/Ninja/BankTransactionSync.php | 20 +++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index f67dc4af685b..9beb55d2b94f 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -97,6 +97,9 @@ class Kernel extends ConsoleKernel /* Fires webhooks for overdue Invoice */ $schedule->job(new InvoiceCheckLateWebhook)->dailyAt('07:00')->withoutOverlapping()->name('invoice-overdue-job')->onOneServer(); + /* Pulls in bank transactions from third party services */ + $schedule->job(new BankTransactionSync)->everyFourHours()->withoutOverlapping()->name('bank-trans-sync-job')->onOneServer(); + if (Ninja::isSelfHost()) { $schedule->call(function () { Account::whereNotNull('id')->update(['is_scheduler_running' => true]); @@ -107,9 +110,6 @@ class Kernel extends ConsoleKernel if (Ninja::isHosted()) { $schedule->job(new AdjustEmailQuota)->dailyAt('23:30')->withoutOverlapping(); - /* Pulls in bank transactions from third party services */ - $schedule->job(new BankTransactionSync)->everyFourHours()->withoutOverlapping()->name('bank-trans-sync-job')->onOneServer(); - /* Checks ACH verification status and updates state to authorize when verified */ $schedule->job(new CheckACHStatus)->everySixHours()->withoutOverlapping()->name('ach-status-job')->onOneServer(); @@ -120,7 +120,7 @@ class Kernel extends ConsoleKernel $schedule->command('ninja:s3-cleanup')->dailyAt('23:15')->withoutOverlapping()->name('s3-cleanup-job')->onOneServer(); } - if (config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled') && ! config('ninja.is_docker')) { + if (config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled') && !config('ninja.is_docker')) { $schedule->command('queue:work database --stop-when-empty --memory=256')->everyMinute()->withoutOverlapping(); $schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping(); @@ -134,7 +134,7 @@ class Kernel extends ConsoleKernel */ protected function commands() { - $this->load(__DIR__.'/Commands'); + $this->load(__DIR__ . '/Commands'); require base_path('routes/console.php'); } diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php index 978abbab1e80..1a68e643a44b 100644 --- a/app/Jobs/Ninja/BankTransactionSync.php +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -49,17 +49,19 @@ class BankTransactionSync implements ShouldQueue foreach (MultiDB::$dbs as $db) { MultiDB::setDB($db); - nlog("syncing transactions - yodlee"); + if (Ninja::isSelfHost()) { // @turbo124 @todo I migrated the schedule for the job within the kernel to execute on all platforms and use the same expression here to determine if yodlee can run or not. Please chek/verify + nlog("syncing transactions - yodlee"); - $a = Account::with('bank_integrations')->whereNotNull('bank_integration_yodlee_account_id')->cursor()->each(function ($account) { - // $queue = Ninja::isHosted() ? 'bank' : 'default'; + $a = Account::with('bank_integrations')->whereNotNull('bank_integration_yodlee_account_id')->cursor()->each(function ($account) { + // $queue = Ninja::isHosted() ? 'bank' : 'default'; - if ($account->isPaid() && $account->plan == 'enterprise') { - $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->andWhere('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { - (new ProcessBankTransactionsYodlee($account, $bank_integration))->handle(); - }); - } - }); + if ($account->isPaid() && $account->plan == 'enterprise') { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->andWhere('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { + (new ProcessBankTransactionsYodlee($account, $bank_integration))->handle(); + }); + } + }); + } nlog("syncing transactions - nordigen"); From bb872e4294c42fa2cc389bcf16b58667b9042141 Mon Sep 17 00:00:00 2001 From: paulwer Date: Sun, 10 Dec 2023 16:10:36 +0100 Subject: [PATCH 22/69] fixes --- app/Jobs/Ninja/BankTransactionSync.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php index 1a68e643a44b..af1fd5bcbce4 100644 --- a/app/Jobs/Ninja/BankTransactionSync.php +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -49,7 +49,7 @@ class BankTransactionSync implements ShouldQueue foreach (MultiDB::$dbs as $db) { MultiDB::setDB($db); - if (Ninja::isSelfHost()) { // @turbo124 @todo I migrated the schedule for the job within the kernel to execute on all platforms and use the same expression here to determine if yodlee can run or not. Please chek/verify + if (Ninja::isHosted()) { // @turbo124 @todo I migrated the schedule for the job within the kernel to execute on all platforms and use the same expression here to determine if yodlee can run or not. Please chek/verify nlog("syncing transactions - yodlee"); $a = Account::with('bank_integrations')->whereNotNull('bank_integration_yodlee_account_id')->cursor()->each(function ($account) { From 6200a8e3d3730c4d14ff24ba801af5bc7339e403 Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 11 Dec 2023 07:37:56 +0100 Subject: [PATCH 23/69] replace font with local one --- .../Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php | 2 +- resources/views/bank/nordigen/connect.blade.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php index a35f9b7ab698..36fc055c655f 100644 --- a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php +++ b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php @@ -43,7 +43,7 @@ class ConnectNordigenBankIntegrationRequest extends Request ]; } - // @turbo124 @todo please check for validity, when issue request from frontend + // @turbo124 @todo please check for validity, when request from frontend public function prepareForValidation() { $input = $this->all(); diff --git a/resources/views/bank/nordigen/connect.blade.php b/resources/views/bank/nordigen/connect.blade.php index 5eafe708f3b4..2009feb2bddf 100644 --- a/resources/views/bank/nordigen/connect.blade.php +++ b/resources/views/bank/nordigen/connect.blade.php @@ -32,7 +32,7 @@ styles: { // Primary // Link to google font - fontFamily: 'https://fonts.googleapis.com/css2?family=Roboto&display=swap', // @todo replace to match german law: not use google fonts and use local instead + fontFamily: '/assets/fonts/Roboto-Regular.ttf', fontSize: '15', backgroundColor: '#F2F2F2', textColor: '#222', From cd4dbb897f8074104462475d3231cf39d5674503 Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 11 Dec 2023 09:15:41 +0100 Subject: [PATCH 24/69] dev-workaround for storing accountId + updates to transaction jobs + local fonts --- .../Controllers/Bank/NordigenController.php | 6 +- .../Controllers/BankIntegrationController.php | 68 ++++++----------- .../ConnectNordigenBankIntegrationRequest.php | 5 +- .../Bank/ProcessBankTransactionsNordigen.php | 74 +++++++++++++++---- app/Models/BankIntegration.php | 1 + composer.lock | 48 ++++++------ ...3_11_26_082959_add_bank_integration_id.php | 1 + .../views/bank/nordigen/connect.blade.php | 8 +- 8 files changed, 119 insertions(+), 92 deletions(-) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 1e10f3ae8c2c..149b822ac7ec 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -49,7 +49,6 @@ class NordigenController extends BaseController 'institutions' => $nordigen->getInstitutions(), 'company' => $company, 'account' => $company->account, - 'redirect' => config('ninja.app_url') . '/nordigen/connect', ]; return view('bank.nordigen.connect', $data); @@ -187,12 +186,13 @@ class NordigenController extends BaseController $bank_integration->company_id = $company->id; $bank_integration->account_id = $company->account_id; $bank_integration->user_id = $company->owner()->id; - $bank_integration->bank_account_id = $nordigen_account['id']; + // $bank_integration->bank_account_id = $nordigen_account['id']; // TODO $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->provider_id = $nordigen_account['provider_id']; + // $bank_integration->provider_id = $nordigen_account['provider_id']; // TODO + $bank_integration->nordigen_meta = (string) $nordigen_account['id'] . "," . $nordigen_account['provider_id']; // TODO: maybe move to bank_account_id and provider_id $bank_integration->provider_name = $nordigen_account['provider_name']; $bank_integration->nickname = $nordigen_account['nickname']; $bank_integration->balance = $nordigen_account['current_balance']; diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 5a837f687c20..6a1e2fdcc381 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -206,17 +206,19 @@ class BankIntegrationController extends BaseController return response()->json(BankIntegration::query()->company(), 200); // Processing transactions for each bank account - $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->each(function ($bank_integration) use ($user_account) { + if (!$user->account->bank_integration_yodlee_account_id) + $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->each(function ($bank_integration) use ($user_account) { - ProcessBankTransactionsYodlee::dispatch($user_account, $bank_integration); + ProcessBankTransactionsYodlee::dispatch($user_account, $bank_integration); - }); + }); - $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function ($bank_integration) use ($user_account) { + if (!$user->account->bank_integration_nordigen_secret_id || !$user->account->bank_integration_nordigen_secret_key) + $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function ($bank_integration) use ($user_account) { - ProcessBankTransactionsNordigen::dispatch($user_account, $bank_integration); + ProcessBankTransactionsNordigen::dispatch($user_account, $bank_integration); - }); + }); Cache::put("throttle_polling:{$user_account->key}", true, 300); @@ -225,9 +227,8 @@ class BankIntegrationController extends BaseController private function refreshAccountsYodlee(User $user) { - if (!$user->account->bank_integration_yodlee_account_id) { - return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); - } + if (!$user->account->bank_integration_yodlee_account_id) + return; $yodlee = new Yodlee($user->account->bank_integration_yodlee_account_id); @@ -263,36 +264,26 @@ class BankIntegrationController extends BaseController private function refreshAccountsNordigen(User $user) { if (!$user->account->bank_integration_nordigen_secret_id || !$user->account->bank_integration_nordigen_secret_key) - return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); + return; $nordigen = new Nordigen($user->account->bank_integration_nordigen_secret_id, $user->account->bank_integration_nordigen_secret_key); - $accounts = $nordigen->getAccounts(); // TODO?! + BankIntegration::withTrashed()->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function (BankIntegration $bank_integration) use ($nordigen, $user) { + $account = $nordigen->getAccount(explode(',', $bank_integration->nordigen_meta)[0]); - foreach ($accounts as $account) { - if ($bi = BankIntegration::withTrashed()->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('bank_account_id', $account['id'])->where('company_id', $user->company()->id)->first()) { - $bi->balance = $account['current_balance']; - $bi->currency = $account['account_currency']; - $bi->save(); - } else { - $bank_integration = new BankIntegration(); - $bank_integration->company_id = $user->company()->id; - $bank_integration->account_id = $user->account_id; - $bank_integration->user_id = $user->id; - $bank_integration->bank_account_id = $account['id']; - $bank_integration->bank_account_type = $account['account_type']; - $bank_integration->bank_account_name = $account['account_name']; - $bank_integration->bank_account_status = $account['account_status']; - $bank_integration->bank_account_number = $account['account_number']; - $bank_integration->provider_id = $account['provider_id']; - $bank_integration->provider_name = $account['provider_name']; - $bank_integration->nickname = $account['nickname']; - $bank_integration->balance = $account['current_balance']; - $bank_integration->currency = $account['account_currency']; + if (!$account) { + $bank_integration->disabled_upstream = true; $bank_integration->save(); + return; } - } + + $bank_integration->bank_account_status = $account['account_status']; + $bank_integration->balance = $account['current_balance']; + $bank_integration->currency = $account['account_currency']; + + $bank_integration->save(); + }); } /** @@ -314,8 +305,7 @@ 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); + // we dont remove Accounts from nordigen, because they could be used within other companies $this->bank_integration_repo->delete($bank_integration); @@ -332,16 +322,6 @@ class BankIntegrationController extends BaseController $yodlee->deleteAccount($bank_integration->bank_account_id); } - private function removeAccountNordigen(Account $account, BankIntegration $bank_integration) - { - 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_secret_id, $account->bank_integration_nordigen_secret_key); - $nordigen->deleteAccount($bank_integration->bank_account_id); - } - - /** * Return the remote list of accounts stored on the third party provider * and update our local cache. diff --git a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php index 36fc055c655f..b68b1d76ce16 100644 --- a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php +++ b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php @@ -51,10 +51,7 @@ class ConnectNordigenBankIntegrationRequest extends Request if (!array_key_exists('redirect', $input)) { $context = $this->getTokenContent(); - if ($context && array_key_exists('is_react', $context)) - $input["redirect"] = $context["is_react"] ? config("ninja.react_url") : config("ninja.app_url"); - else - $input["redirect"] = config("ninja.app_url"); + $input["redirect"] = isset($context['is_react']) && $context['is_react'] ? config('ninja.react_url') : config('ninja.app_url'); $this->replace($input); diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index b976030dff9f..a85247f3ef30 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -17,6 +17,7 @@ use App\Models\Account; use App\Models\BankIntegration; use App\Models\BankTransaction; use App\Models\Company; +use App\Notifications\Ninja\GenericNinjaAdminNotification; use App\Services\Bank\BankMatchingService; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -39,6 +40,8 @@ class ProcessBankTransactionsNordigen implements ShouldQueue private int $skip = 0; public Company $company; + public Nordigen $nordigen; + public $nordigen_account; /** * Create a new job instance. @@ -53,6 +56,8 @@ class ProcessBankTransactionsNordigen implements ShouldQueue if ($this->bank_integration->integration_type != BankIntegration::INTEGRATION_TYPE_NORDIGEN) throw new \Exception("Invalid BankIntegration Type"); + $this->nordigen = new Nordigen($this->account->bank_integration_nordigen_secret_id, $this->account->bank_integration_nordigen_secret_key); + } /** @@ -69,12 +74,39 @@ class ProcessBankTransactionsNordigen implements ShouldQueue //Loop through everything until we are up to date $this->from_date = $this->from_date ?: '2021-01-01'; + // UPDATE ACCOUNT + try { + $this->updateAccount(); + } catch (\Exception $e) { + nlog("{$this->account->bank_integration_nordigen_secret_id} - exited abnormally => " . $e->getMessage()); + + $content = [ + "Processing transactions for account: {$this->bank_integration->account->key} failed", + "Exception Details => ", + $e->getMessage(), + ]; + + $this->bank_integration->company->notification(new GenericNinjaAdminNotification($content))->ninja(); + return; + } + if (!$this->nordigen_account) + return; + + // UPDATE TRANSACTIONS do { try { $this->processTransactions(); } catch (\Exception $e) { nlog("{$this->account->bank_integration_nordigen_secret_id} - exited abnormally => " . $e->getMessage()); + + $content = [ + "Processing transactions for account: {$this->bank_integration->account->key} failed", + "Exception Details => ", + $e->getMessage(), + ]; + + $this->bank_integration->company->notification(new GenericNinjaAdminNotification($content))->ninja(); return; } @@ -85,13 +117,12 @@ class ProcessBankTransactionsNordigen implements ShouldQueue } - - private function processTransactions() + private function updateAccount() { - $nordigen = new Nordigen($this->account->bank_integration_nordigen_secret_id, $this->account->bank_integration_nordigen_secret_key); // TODO: maybe implement credentials + $bank_account_id = explode(',', $this->bank_integration->nordigen_meta)[0]; // maybe replace it later with bank_account_id - if (!$nordigen->isAccountActive($this->bank_integration->bank_account_id)) { + if (!$this->nordigen->isAccountActive($bank_account_id)) { $this->bank_integration->disabled_upstream = true; $this->bank_integration->save(); $this->stop_loop = false; @@ -99,8 +130,21 @@ class ProcessBankTransactionsNordigen implements ShouldQueue return; } + $this->nordigen_account = $this->nordigen->getAccount($bank_account_id); + + $this->bank_integration->bank_account_status = $this->nordigen_account['account_status']; + $this->bank_integration->balance = $this->nordigen_account['current_balance']; + $this->bank_integration->currency = $this->nordigen_account['account_currency']; + + $this->bank_integration->save(); + + } + + private function processTransactions() + { + //Get transaction count object - $transactions = $nordigen->getTransactions($this->bank_integration->bank_account_id, $this->from_date); + $transactions = $this->nordigen->getTransactions($this->nordigen_account["id"], $this->from_date); //Get int count $count = sizeof($transactions); @@ -108,7 +152,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue //if no transactions, update the from_date and move on if (count($transactions) == 0) { - $this->bank_integration->from_date = now()->subDays(2); + $this->bank_integration->from_date = now()->subDays(5); $this->bank_integration->disabled_upstream = false; $this->bank_integration->save(); $this->stop_loop = false; @@ -133,7 +177,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue continue; //this should be much faster to insert than using ::create() - $bt = \DB::table('bank_transactions')->insert( + \DB::table('bank_transactions')->insert( array_merge($transaction, [ 'company_id' => $this->company->id, 'user_id' => $user_id, @@ -146,14 +190,18 @@ class ProcessBankTransactionsNordigen implements ShouldQueue } - $this->skip = $this->skip + 500; + // $this->skip = $this->skip + 500; - if ($count < 500) { - $this->stop_loop = false; - $this->bank_integration->from_date = now()->subDays(2); - $this->bank_integration->save(); + // if ($count < 500) { + // $this->stop_loop = false; + // $this->bank_integration->from_date = now()->subDays(5); + // $this->bank_integration->save(); - } + // } + + $this->stop_loop = false; + $this->bank_integration->from_date = now()->subDays(5); + $this->bank_integration->save(); } } diff --git a/app/Models/BankIntegration.php b/app/Models/BankIntegration.php index 04e5bc9245a9..613e6f1dc210 100644 --- a/app/Models/BankIntegration.php +++ b/app/Models/BankIntegration.php @@ -30,6 +30,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property float $balance * @property int|null $currency * @property string $nickname + * @property string $nordigen_meta // TODO: maybe move to bank_account_id and provider_id * @property string|null $from_date * @property bool $is_deleted * @property int|null $created_at diff --git a/composer.lock b/composer.lock index b57c5b4170a0..3d5ee32d34aa 100644 --- a/composer.lock +++ b/composer.lock @@ -2617,16 +2617,16 @@ }, { "name": "google/apiclient-services", - "version": "v0.326.1", + "version": "v0.327.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "4e89c28c499f87eb517679e13356469896a119c6" + "reference": "51a11d4ff70dd9f60334525e71bf4cf592e6d282" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/4e89c28c499f87eb517679e13356469896a119c6", - "reference": "4e89c28c499f87eb517679e13356469896a119c6", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/51a11d4ff70dd9f60334525e71bf4cf592e6d282", + "reference": "51a11d4ff70dd9f60334525e71bf4cf592e6d282", "shasum": "" }, "require": { @@ -2655,9 +2655,9 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.326.1" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.327.0" }, - "time": "2023-12-04T01:18:18+00:00" + "time": "2023-12-11T00:52:16+00:00" }, { "name": "google/auth", @@ -6831,16 +6831,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.17.1", + "version": "v4.18.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", "shasum": "" }, "require": { @@ -6881,9 +6881,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2023-12-10T21:03:43+00:00" }, { "name": "nordigen/nordigen-php", @@ -15318,16 +15318,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.41.0", + "version": "v3.41.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "7d8d18e19095a939b8a3b8046f57108feaad6134" + "reference": "8b6ae8dcbaf23f09680643ab832a4a3a260265f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7d8d18e19095a939b8a3b8046f57108feaad6134", - "reference": "7d8d18e19095a939b8a3b8046f57108feaad6134", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/8b6ae8dcbaf23f09680643ab832a4a3a260265f6", + "reference": "8b6ae8dcbaf23f09680643ab832a4a3a260265f6", "shasum": "" }, "require": { @@ -15397,7 +15397,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.41.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.41.1" }, "funding": [ { @@ -15405,7 +15405,7 @@ "type": "github" } ], - "time": "2023-12-08T22:54:33+00:00" + "time": "2023-12-10T19:59:27+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -16184,16 +16184,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.9", + "version": "10.1.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "a56a9ab2f680246adcf3db43f38ddf1765774735" + "reference": "599109c8ca6bae97b23482d557d2874c25a65e59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a56a9ab2f680246adcf3db43f38ddf1765774735", - "reference": "a56a9ab2f680246adcf3db43f38ddf1765774735", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/599109c8ca6bae97b23482d557d2874c25a65e59", + "reference": "599109c8ca6bae97b23482d557d2874c25a65e59", "shasum": "" }, "require": { @@ -16250,7 +16250,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.9" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.10" }, "funding": [ { @@ -16258,7 +16258,7 @@ "type": "github" } ], - "time": "2023-11-23T12:23:20+00:00" + "time": "2023-12-11T06:28:43+00:00" }, { "name": "phpunit/php-file-iterator", diff --git a/database/migrations/2023_11_26_082959_add_bank_integration_id.php b/database/migrations/2023_11_26_082959_add_bank_integration_id.php index 6189012da8de..13ead010801d 100644 --- a/database/migrations/2023_11_26_082959_add_bank_integration_id.php +++ b/database/migrations/2023_11_26_082959_add_bank_integration_id.php @@ -16,6 +16,7 @@ return new class extends Migration { { Schema::table('bank_integrations', function (Blueprint $table) { $table->string('integration_type')->nullable(); + $table->string('nordigen_meta')->nullable(); // accountId,institutionId @todo: maybe replace with the following below // $table->string('provider_id'); // migrate to string, because nordigen provides a string like: SANDBOXFINANCE_SFIN0000 // $table->string('bank_account_id'); // migrate to string, because nordigen uses uuid() strings }); diff --git a/resources/views/bank/nordigen/connect.blade.php b/resources/views/bank/nordigen/connect.blade.php index 2009feb2bddf..a823726d99e2 100644 --- a/resources/views/bank/nordigen/connect.blade.php +++ b/resources/views/bank/nordigen/connect.blade.php @@ -22,8 +22,8 @@ // Pass your redirect link after user has been authorized in institution const config = { - // Text that will be displayed on the left side under the logo. Text is limited to 100 characters, and rest will be truncated. - text: "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean mavdvd", + // Text that will be displayed on the left side under the logo. Text is limited to 100 characters, and rest will be truncated. @turbo124 replace with a translated version like ctrans() + text: "{{ $account && !$account->isPaid() ? 'Invoice Ninja' : (isset($company) && !is_null($company) ? $company->name : '') }} will gain access for your selected bank account. After selecting your institution you are redirected to theire front-page to complete the request with your account credentials.", // Logo URL that will be shown below the modal form. logoUrl: "{{ $account && !$account->isPaid() ? asset('images/invoiceninja-black-logo-2.png') : (isset($company) && !is_null($company) ? $company->present()->logo() : '') }}", // Will display country list with corresponding institutions. When `countryFilter` is set to `false`, only list of institutions will be shown. @@ -32,7 +32,7 @@ styles: { // Primary // Link to google font - fontFamily: '/assets/fonts/Roboto-Regular.ttf', + fontFamily: new URL("assets/fonts/Roboto-Regular.ttf", window.location.origin).href, fontSize: '15', backgroundColor: '#F2F2F2', textColor: '#222', @@ -58,7 +58,7 @@ const institutionId = institution.getAttribute('data-institution'); const url = new URL(window.location.href); url.searchParams.set('institution_id', institutionId); - window.location.href = url.href; + w.location.href = url.href; }); }); From 3229650858dd00873ac23d72e9f147244dbc17e5 Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 11 Dec 2023 09:23:35 +0100 Subject: [PATCH 25/69] dedicated keys for nordigen --- app/Http/Controllers/Bank/NordigenController.php | 5 ++--- app/Http/Controllers/BankIntegrationController.php | 4 ++-- app/Jobs/Bank/ProcessBankTransactionsNordigen.php | 8 +++----- app/Models/BankIntegration.php | 4 +++- .../2023_11_26_082959_add_bank_integration_id.php | 5 ++--- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 149b822ac7ec..58323b36e7c1 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -186,13 +186,12 @@ class NordigenController extends BaseController $bank_integration->company_id = $company->id; $bank_integration->account_id = $company->account_id; $bank_integration->user_id = $company->owner()->id; - // $bank_integration->bank_account_id = $nordigen_account['id']; // TODO + $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->provider_id = $nordigen_account['provider_id']; // TODO - $bank_integration->nordigen_meta = (string) $nordigen_account['id'] . "," . $nordigen_account['provider_id']; // TODO: maybe move to bank_account_id and provider_id + $bank_integration->nordigen_provider_id = $nordigen_account['provider_id']; $bank_integration->provider_name = $nordigen_account['provider_name']; $bank_integration->nickname = $nordigen_account['nickname']; $bank_integration->balance = $nordigen_account['current_balance']; diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 6a1e2fdcc381..52b3073e3b5b 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -268,8 +268,8 @@ class BankIntegrationController extends BaseController $nordigen = new Nordigen($user->account->bank_integration_nordigen_secret_id, $user->account->bank_integration_nordigen_secret_key); - BankIntegration::withTrashed()->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function (BankIntegration $bank_integration) use ($nordigen, $user) { - $account = $nordigen->getAccount(explode(',', $bank_integration->nordigen_meta)[0]); + BankIntegration::withTrashed()->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function (BankIntegration $bank_integration) use ($nordigen) { + $account = $nordigen->getAccount($bank_integration->nordigen_account_id); if (!$account) { $bank_integration->disabled_upstream = true; diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index a85247f3ef30..a5d4d557df7f 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -120,9 +120,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue private function updateAccount() { - $bank_account_id = explode(',', $this->bank_integration->nordigen_meta)[0]; // maybe replace it later with bank_account_id - - if (!$this->nordigen->isAccountActive($bank_account_id)) { + if (!$this->nordigen->isAccountActive($this->bank_integration->nordigen_account_id)) { $this->bank_integration->disabled_upstream = true; $this->bank_integration->save(); $this->stop_loop = false; @@ -130,7 +128,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue return; } - $this->nordigen_account = $this->nordigen->getAccount($bank_account_id); + $this->nordigen_account = $this->nordigen->getAccount($this->bank_integration->nordigen_account_id); $this->bank_integration->bank_account_status = $this->nordigen_account['account_status']; $this->bank_integration->balance = $this->nordigen_account['current_balance']; @@ -144,7 +142,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue { //Get transaction count object - $transactions = $this->nordigen->getTransactions($this->nordigen_account["id"], $this->from_date); + $transactions = $this->nordigen->getTransactions($this->bank_integration->nordigen_account_id, $this->from_date); //Get int count $count = sizeof($transactions); diff --git a/app/Models/BankIntegration.php b/app/Models/BankIntegration.php index 613e6f1dc210..446b8b25f2c0 100644 --- a/app/Models/BankIntegration.php +++ b/app/Models/BankIntegration.php @@ -20,6 +20,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property int $account_id * @property int $company_id * @property int $user_id + * @property string $integration_type * @property string $provider_name * @property int $provider_id * @property int $bank_account_id @@ -30,7 +31,8 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property float $balance * @property int|null $currency * @property string $nickname - * @property string $nordigen_meta // TODO: maybe move to bank_account_id and provider_id + * @property string $nordigen_account_id + * @property string $nordigen_provider_id * @property string|null $from_date * @property bool $is_deleted * @property int|null $created_at diff --git a/database/migrations/2023_11_26_082959_add_bank_integration_id.php b/database/migrations/2023_11_26_082959_add_bank_integration_id.php index 13ead010801d..98ab0e7c815b 100644 --- a/database/migrations/2023_11_26_082959_add_bank_integration_id.php +++ b/database/migrations/2023_11_26_082959_add_bank_integration_id.php @@ -16,9 +16,8 @@ return new class extends Migration { { Schema::table('bank_integrations', function (Blueprint $table) { $table->string('integration_type')->nullable(); - $table->string('nordigen_meta')->nullable(); // accountId,institutionId @todo: maybe replace with the following below - // $table->string('provider_id'); // migrate to string, because nordigen provides a string like: SANDBOXFINANCE_SFIN0000 - // $table->string('bank_account_id'); // migrate to string, because nordigen uses uuid() strings + $table->string('nordigen_account_id')->nullable(); + $table->string('nordigen_provider_id')->nullable(); }); // migrate old account to be used with yodlee From a937f5dcefbd39894a1979155c94a81f28ed39ad Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 11 Dec 2023 10:40:55 +0100 Subject: [PATCH 26/69] fixes --- app/Http/Controllers/Bank/NordigenController.php | 2 +- app/Http/Controllers/BankIntegrationController.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 58323b36e7c1..ad9d4758db07 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -220,7 +220,7 @@ class NordigenController extends BaseController } // perform update in background - $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function ($bank_integration) use ($company) { + $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->andWhere('auto_sync', true)->each(function ($bank_integration) use ($company) { ProcessBankTransactionsNordigen::dispatch($company->account, $bank_integration); diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 52b3073e3b5b..8da51fe35fed 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -206,15 +206,15 @@ class BankIntegrationController extends BaseController return response()->json(BankIntegration::query()->company(), 200); // Processing transactions for each bank account - if (!$user->account->bank_integration_yodlee_account_id) - $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->each(function ($bank_integration) use ($user_account) { + if ($user->account->bank_integration_yodlee_account_id) + $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->andWhere('auto_sync', true)->each(function ($bank_integration) use ($user_account) { ProcessBankTransactionsYodlee::dispatch($user_account, $bank_integration); }); - if (!$user->account->bank_integration_nordigen_secret_id || !$user->account->bank_integration_nordigen_secret_key) - $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function ($bank_integration) use ($user_account) { + if ($user->account->bank_integration_nordigen_secret_id && $user->account->bank_integration_nordigen_secret_key) + $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->andWhere('auto_sync', true)->each(function ($bank_integration) use ($user_account) { ProcessBankTransactionsNordigen::dispatch($user_account, $bank_integration); From fab47bd08cf131122f94634118b62351abfe00e6 Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 11 Dec 2023 13:23:28 +0100 Subject: [PATCH 27/69] introduce ENV-variables for nordigen --- app/Helpers/Bank/Nordigen/Nordigen.php | 2 +- .../Controllers/Bank/NordigenController.php | 13 +++++++------ .../Controllers/BankIntegrationController.php | 8 +++++--- .../Bank/ProcessBankTransactionsNordigen.php | 10 +++++++--- app/Jobs/Ninja/BankTransactionSync.php | 17 ++++++++++++----- config/ninja.php | 11 ++++++++--- 6 files changed, 40 insertions(+), 21 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index 1ddbe3e80fc8..68ca093a4e26 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -21,7 +21,7 @@ use Nordigen\NordigenPHP\Exceptions\NordigenExceptions\NordigenException; class Nordigen { - public bool $test_mode = false; // https://developer.gocardless.com/bank-account-data/sandbox + public bool $test_mode = config('ninja.nordigen.test_mode'); // https://developer.gocardless.com/bank-account-data/sandbox public string $sandbox_institutionId = "SANDBOXFINANCE_SFIN0000"; diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index ad9d4758db07..d2caa70f625e 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -35,11 +35,12 @@ class NordigenController extends BaseController return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=token-invalid"); $company = $request->getCompany(); + $account = $company->account; - if (!$company->account->bank_integration_nordigen_secret_id || !$company->account->bank_integration_nordigen_secret_key) + if (!(($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))) return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid"); - $nordigen = new Nordigen($company->account->bank_integration_nordigen_secret_id, $company->account->bank_integration_nordigen_secret_key); + $nordigen = ($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) ? new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key) : new Nordigen(config('ninja.nordigen.secret_id'), config('ninja.nordigen.secret_key')); // show bank_selection_screen, when institution_id is not present if (!array_key_exists("institution_id", $data)) { @@ -156,11 +157,11 @@ class NordigenController extends BaseController $company = Company::where('company_key', $context["company_key"])->first(); $account = $company->account; - if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) + if (!(($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))) return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid"); // fetch requisition - $nordigen = new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); + $nordigen = ($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) ? new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key) : new Nordigen(config('ninja.nordigen.secret_id'), config('ninja.nordigen.secret_key')); $requisition = $nordigen->getRequisition($context["requisitionId"]); // check validity of requisition @@ -302,10 +303,10 @@ class NordigenController extends BaseController { $account = auth()->user()->account; - if (!$account->bank_integration_nordigen_secret_id || !$account->bank_integration_nordigen_secret_key) + if (!(($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) || (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($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key); + $nordigen = ($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) ? new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key) : new Nordigen(config('ninja.nordigen.secret_id'), config('ninja.nordigen.secret_key')); return response()->json($nordigen->getInstitutions()); } diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 8da51fe35fed..dfc4e281b630 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -213,7 +213,7 @@ class BankIntegrationController extends BaseController }); - if ($user->account->bank_integration_nordigen_secret_id && $user->account->bank_integration_nordigen_secret_key) + if (($user->account->bank_integration_nordigen_secret_id && $user->account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->andWhere('auto_sync', true)->each(function ($bank_integration) use ($user_account) { ProcessBankTransactionsNordigen::dispatch($user_account, $bank_integration); @@ -263,10 +263,12 @@ class BankIntegrationController extends BaseController private function refreshAccountsNordigen(User $user) { - if (!$user->account->bank_integration_nordigen_secret_id || !$user->account->bank_integration_nordigen_secret_key) + $account = $user->account; + + if (!(($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))) return; - $nordigen = new Nordigen($user->account->bank_integration_nordigen_secret_id, $user->account->bank_integration_nordigen_secret_key); + $nordigen = ($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) ? new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key) : new Nordigen(config('ninja.nordigen.secret_id'), config('ninja.nordigen.secret_key')); BankIntegration::withTrashed()->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function (BankIntegration $bank_integration) use ($nordigen) { $account = $nordigen->getAccount($bank_integration->nordigen_account_id); diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index a5d4d557df7f..42c196937b33 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -56,8 +56,10 @@ class ProcessBankTransactionsNordigen implements ShouldQueue if ($this->bank_integration->integration_type != BankIntegration::INTEGRATION_TYPE_NORDIGEN) throw new \Exception("Invalid BankIntegration Type"); - $this->nordigen = new Nordigen($this->account->bank_integration_nordigen_secret_id, $this->account->bank_integration_nordigen_secret_key); + if (!(($this->account->bank_integration_nordigen_secret_id && $this->account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))) + throw new \Exception("Missing credentials for bank_integration service nortigen"); + $this->nordigen = ($this->account->bank_integration_nordigen_secret_id && $this->account->bank_integration_nordigen_secret_key) ? new Nordigen($this->account->bank_integration_nordigen_secret_id, $this->account->bank_integration_nordigen_secret_key) : new Nordigen(config('ninja.nordigen.secret_id'), config('ninja.nordigen.secret_key')); } /** @@ -78,7 +80,8 @@ class ProcessBankTransactionsNordigen implements ShouldQueue try { $this->updateAccount(); } catch (\Exception $e) { - nlog("{$this->account->bank_integration_nordigen_secret_id} - exited abnormally => " . $e->getMessage()); + $secretId = $this->account->bank_integration_nordigen_secret_id ?: config('ninja.nortigen.secret_id'); + nlog("{$secretId} - exited abnormally => " . $e->getMessage()); $content = [ "Processing transactions for account: {$this->bank_integration->account->key} failed", @@ -98,7 +101,8 @@ class ProcessBankTransactionsNordigen implements ShouldQueue try { $this->processTransactions(); } catch (\Exception $e) { - nlog("{$this->account->bank_integration_nordigen_secret_id} - exited abnormally => " . $e->getMessage()); + $secretId = $this->account->bank_integration_nordigen_secret_id ?: config('ninja.nortigen.secret_id'); + nlog("{$secretId} - exited abnormally => " . $e->getMessage()); $content = [ "Processing transactions for account: {$this->bank_integration->account->key} failed", diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php index af1fd5bcbce4..a65d52046e9b 100644 --- a/app/Jobs/Ninja/BankTransactionSync.php +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -52,7 +52,7 @@ class BankTransactionSync implements ShouldQueue if (Ninja::isHosted()) { // @turbo124 @todo I migrated the schedule for the job within the kernel to execute on all platforms and use the same expression here to determine if yodlee can run or not. Please chek/verify nlog("syncing transactions - yodlee"); - $a = Account::with('bank_integrations')->whereNotNull('bank_integration_yodlee_account_id')->cursor()->each(function ($account) { + Account::with('bank_integrations')->whereNotNull('bank_integration_yodlee_account_id')->cursor()->each(function ($account) { // $queue = Ninja::isHosted() ? 'bank' : 'default'; if ($account->isPaid() && $account->plan == 'enterprise') { @@ -65,11 +65,18 @@ class BankTransactionSync implements ShouldQueue nlog("syncing transactions - nordigen"); - $b = 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) { - (new ProcessBankTransactionsNordigen($account, $bank_integration))->handle(); + if (config("ninja.nortigen.secret_id") && config("ninja.nortigen.secret_key")) + Account::with('bank_integrations')->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) { + (new ProcessBankTransactionsNordigen($account, $bank_integration))->handle(); + }); + }); + else + 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) { + (new ProcessBankTransactionsNordigen($account, $bank_integration))->handle(); + }); }); - }); nlog("syncing transactions - done"); } diff --git a/config/ninja.php b/config/ninja.php index 94d0a307fc6a..5053cc40d9ca 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -7,7 +7,7 @@ return [ 'license_url' => 'https://app.invoiceninja.com', 'react_url' => env('REACT_URL', 'https://app.invoicing.co'), 'production' => env('NINJA_PROD', false), - 'license' => env('NINJA_LICENSE', ''), + 'license' => env('NINJA_LICENSE', ''), 'version_url' => 'https://pdf.invoicing.co/api/version', 'app_name' => env('APP_NAME', 'Invoice Ninja'), 'app_env' => env('APP_ENV', 'selfhosted'), @@ -86,7 +86,7 @@ return [ 'password' => 'password', 'stripe' => env('STRIPE_KEYS', ''), 'paypal' => env('PAYPAL_KEYS', ''), - 'ppcp' => env('PPCP_KEYS', ''), + 'ppcp' => env('PPCP_KEYS', ''), 'paypal_rest' => env('PAYPAL_REST_KEYS', ''), 'authorize' => env('AUTHORIZE_KEYS', ''), 'checkout' => env('CHECKOUT_KEYS', ''), @@ -196,7 +196,7 @@ return [ 'ninja_default_company_id' => env('NINJA_COMPANY_ID', null), 'ninja_default_company_gateway_id' => env('NINJA_COMPANY_GATEWAY_ID', null), 'ninja_hosted_secret' => env('NINJA_HOSTED_SECRET', ''), - 'ninja_hosted_header' =>env('NINJA_HEADER', ''), + 'ninja_hosted_header' => env('NINJA_HEADER', ''), 'ninja_connect_secret' => env('NINJA_CONNECT_SECRET', ''), 'internal_queue_enabled' => env('INTERNAL_QUEUE_ENABLED', true), 'ninja_apple_api_key' => env('APPLE_API_KEY', false), @@ -218,6 +218,11 @@ return [ 'dev_mode' => env("YODLEE_DEV_MODE", false), 'config_name' => env("YODLEE_CONFIG_NAME", false), ], + 'nordigen' => [ + 'client_id' => env('NORDIGEN_SECRET_ID', false), + 'client_secret' => env('NORDIGEN_SECRET_KEY', false), + 'test_mode' => env("NORDIGEN_TEST_MODE", false), + ], 'licenses' => env('LICENSES', false), 'google_application_credentials' => env("GOOGLE_APPLICATION_CREDENTIALS", false), 'shopify' => [ From 0593a57e5ce4ddf8b3cdb5a7f4a4bee2ab1339f3 Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 11 Dec 2023 16:13:26 +0100 Subject: [PATCH 28/69] fixes --- app/Helpers/Bank/Nordigen/Nordigen.php | 8 +- .../Transformer/ExpenseTransformer.php | 80 ------------------- ...sformer.php => TransactionTransformer.php} | 2 +- .../Controllers/Bank/NordigenController.php | 2 +- app/Models/BankIntegration.php | 2 +- ...3_11_26_082959_add_bank_integration_id.php | 2 +- .../views/bank/nordigen/connect.blade.php | 2 +- 7 files changed, 10 insertions(+), 88 deletions(-) delete mode 100644 app/Helpers/Bank/Nordigen/Transformer/ExpenseTransformer.php rename app/Helpers/Bank/Nordigen/Transformer/{IncomeTransformer.php => TransactionTransformer.php} (98%) diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index 68ca093a4e26..153303b37b1c 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -15,13 +15,13 @@ namespace App\Helpers\Bank\Nordigen; use App\Exceptions\NordigenApiException; use App\Helpers\Bank\Nordigen\Transformer\AccountTransformer; -use App\Helpers\Bank\Nordigen\Transformer\IncomeTransformer; +use App\Helpers\Bank\Nordigen\Transformer\TransactionTransformer; use Log; use Nordigen\NordigenPHP\Exceptions\NordigenExceptions\NordigenException; class Nordigen { - public bool $test_mode = config('ninja.nordigen.test_mode'); // https://developer.gocardless.com/bank-account-data/sandbox + public bool $test_mode; // https://developer.gocardless.com/bank-account-data/sandbox public string $sandbox_institutionId = "SANDBOXFINANCE_SFIN0000"; @@ -30,6 +30,8 @@ class Nordigen public function __construct(string $secret_id, string $secret_key) { + $this->test_mode = config('ninja.nordigen.test_mode'); + $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 @@ -101,7 +103,7 @@ class Nordigen $transactionResponse = $this->client->account($accountId)->getAccountTransactions($dateFrom); - $it = new IncomeTransformer(); + $it = new TransactionTransformer(); return $it->transform($transactionResponse); } diff --git a/app/Helpers/Bank/Nordigen/Transformer/ExpenseTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/ExpenseTransformer.php deleted file mode 100644 index ade514d8fdea..000000000000 --- a/app/Helpers/Bank/Nordigen/Transformer/ExpenseTransformer.php +++ /dev/null @@ -1,80 +0,0 @@ -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_provider_id = $nordigen_account['provider_id']; + $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->balance = $nordigen_account['current_balance']; diff --git a/app/Models/BankIntegration.php b/app/Models/BankIntegration.php index 446b8b25f2c0..5be2f2313b1d 100644 --- a/app/Models/BankIntegration.php +++ b/app/Models/BankIntegration.php @@ -32,7 +32,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property int|null $currency * @property string $nickname * @property string $nordigen_account_id - * @property string $nordigen_provider_id + * @property string $nordigen_institution_id * @property string|null $from_date * @property bool $is_deleted * @property int|null $created_at diff --git a/database/migrations/2023_11_26_082959_add_bank_integration_id.php b/database/migrations/2023_11_26_082959_add_bank_integration_id.php index 98ab0e7c815b..da6f778877da 100644 --- a/database/migrations/2023_11_26_082959_add_bank_integration_id.php +++ b/database/migrations/2023_11_26_082959_add_bank_integration_id.php @@ -17,7 +17,7 @@ return new class extends Migration { Schema::table('bank_integrations', function (Blueprint $table) { $table->string('integration_type')->nullable(); $table->string('nordigen_account_id')->nullable(); - $table->string('nordigen_provider_id')->nullable(); + $table->string('nordigen_institution_id')->nullable(); }); // migrate old account to be used with yodlee diff --git a/resources/views/bank/nordigen/connect.blade.php b/resources/views/bank/nordigen/connect.blade.php index a823726d99e2..716ee1837783 100644 --- a/resources/views/bank/nordigen/connect.blade.php +++ b/resources/views/bank/nordigen/connect.blade.php @@ -58,7 +58,7 @@ const institutionId = institution.getAttribute('data-institution'); const url = new URL(window.location.href); url.searchParams.set('institution_id', institutionId); - w.location.href = url.href; + window.location.href = url.href; }); }); From 3a8bb304250fc89d8fc1f722a82f6305780416a1 Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 11 Dec 2023 17:16:14 +0100 Subject: [PATCH 29/69] fixes --- app/Http/Controllers/Bank/NordigenController.php | 2 +- app/Http/Controllers/BankIntegrationController.php | 4 ++-- app/Jobs/Ninja/BankTransactionSync.php | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 5c6db66e8a90..03de5bdb7a5d 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -221,7 +221,7 @@ class NordigenController extends BaseController } // perform update in background - $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->andWhere('auto_sync', true)->each(function ($bank_integration) use ($company) { + $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->each(function ($bank_integration) use ($company) { ProcessBankTransactionsNordigen::dispatch($company->account, $bank_integration); diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index dfc4e281b630..5ddd92cb91c9 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -207,14 +207,14 @@ class BankIntegrationController extends BaseController // Processing transactions for each bank account if ($user->account->bank_integration_yodlee_account_id) - $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->andWhere('auto_sync', true)->each(function ($bank_integration) use ($user_account) { + $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->each(function ($bank_integration) use ($user_account) { ProcessBankTransactionsYodlee::dispatch($user_account, $bank_integration); }); if (($user->account->bank_integration_nordigen_secret_id && $user->account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) - $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->andWhere('auto_sync', true)->each(function ($bank_integration) use ($user_account) { + $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->each(function ($bank_integration) use ($user_account) { ProcessBankTransactionsNordigen::dispatch($user_account, $bank_integration); diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php index a65d52046e9b..786125545dd1 100644 --- a/app/Jobs/Ninja/BankTransactionSync.php +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -56,7 +56,7 @@ class BankTransactionSync implements ShouldQueue // $queue = Ninja::isHosted() ? 'bank' : 'default'; if ($account->isPaid() && $account->plan == 'enterprise') { - $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->andWhere('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { (new ProcessBankTransactionsYodlee($account, $bank_integration))->handle(); }); } @@ -67,13 +67,13 @@ class BankTransactionSync implements ShouldQueue if (config("ninja.nortigen.secret_id") && config("ninja.nortigen.secret_key")) Account::with('bank_integrations')->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) { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { (new ProcessBankTransactionsNordigen($account, $bank_integration))->handle(); }); }); else - 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) { + Account::with('bank_integrations')->whereNotNull('bank_integration_nordigen_secret_id')->whereNotNull('bank_integration_nordigen_secret_key')->cursor()->each(function ($account) { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { (new ProcessBankTransactionsNordigen($account, $bank_integration))->handle(); }); }); From a364bc24de4f9f94a17288da73b9cfda8c38b04f Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 11 Dec 2023 19:18:32 +0100 Subject: [PATCH 30/69] fix: missing context error --- app/Http/Controllers/Bank/NordigenController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 03de5bdb7a5d..98a5f69919f0 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -32,7 +32,7 @@ class NordigenController extends BaseController $context = $request->getTokenContent(); if (!$context || $context["context"] != "nordigen" || array_key_exists("requisitionId", $context)) - return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=token-invalid"); + return response()->redirectTo(($context && array_key_exists("redirect", $context) ? $context["redirect"] : config('ninja.app_url')) . "?action=nordigen_connect&status=failed&reason=token-invalid"); $company = $request->getCompany(); $account = $company->account; @@ -151,7 +151,7 @@ class NordigenController extends BaseController $context = Cache::get($data["ref"]); if (!$context || $context["context"] != "nordigen" || !array_key_exists("requisitionId", $context)) - return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=ref-invalid"); + return response()->redirectTo(($context && array_key_exists("redirect", $context) ? $context["redirect"] : config('ninja.app_url')) . "?action=nordigen_connect&status=failed&reason=ref-invalid"); $company = Company::where('company_key', $context["company_key"])->first(); @@ -168,7 +168,7 @@ class NordigenController extends BaseController if (!$requisition) return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-not-found"); if ($requisition["status"] != "LN") - return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-invalid-status"); + return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-invalid-status&status=" . $requisition["status"]); if (sizeof($requisition["accounts"]) == 0) return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-no-accounts"); From 16378d551db0a6d44a8bfb8a67cee0b6a847b83c Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 11 Dec 2023 21:21:42 +0100 Subject: [PATCH 31/69] fixes --- .../Controllers/Bank/NordigenController.php | 2 +- .../Bank/ProcessBankTransactionsNordigen.php | 21 ++++++++++++------- .../Bank/ProcessBankTransactionsYodlee.php | 1 - 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 98a5f69919f0..c65693c8254c 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -154,7 +154,7 @@ class NordigenController extends BaseController return response()->redirectTo(($context && array_key_exists("redirect", $context) ? $context["redirect"] : config('ninja.app_url')) . "?action=nordigen_connect&status=failed&reason=ref-invalid"); - $company = Company::where('company_key', $context["company_key"])->first(); + $company = Company::where('company_key', $context["company_key"])->firstOrFail(); $account = $company->account; if (!(($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))) diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index 42c196937b33..6ba21e3bbc04 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -30,8 +30,9 @@ class ProcessBankTransactionsNordigen implements ShouldQueue use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; private Account $account; - private BankIntegration $bank_integration; + private string $secret_id; + private string $secret_key; private ?string $from_date; @@ -56,10 +57,18 @@ class ProcessBankTransactionsNordigen implements ShouldQueue if ($this->bank_integration->integration_type != BankIntegration::INTEGRATION_TYPE_NORDIGEN) throw new \Exception("Invalid BankIntegration Type"); - if (!(($this->account->bank_integration_nordigen_secret_id && $this->account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))) + if (!(($this->account->bank_integration_secret_id && $this->account->integration_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))) throw new \Exception("Missing credentials for bank_integration service nortigen"); - $this->nordigen = ($this->account->bank_integration_nordigen_secret_id && $this->account->bank_integration_nordigen_secret_key) ? new Nordigen($this->account->bank_integration_nordigen_secret_id, $this->account->bank_integration_nordigen_secret_key) : new Nordigen(config('ninja.nordigen.secret_id'), config('ninja.nordigen.secret_key')); + if ($this->account->bank_integration_secret_id && $this->account->bank_integration_secret_key) { + $this->secret_id = $this->account->bank_integration_secret_id; + $this->secret_key = $this->account->bank_integration_secret_key; + } else { + $this->secret_id = config('ninja.nordigen.secret_id'); + $this->secret_key = config('ninja.nordigen.secret_key'); + } + + $this->nordigen = new Nordigen($this->secret_id, $this->secret_key); } /** @@ -80,8 +89,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue try { $this->updateAccount(); } catch (\Exception $e) { - $secretId = $this->account->bank_integration_nordigen_secret_id ?: config('ninja.nortigen.secret_id'); - nlog("{$secretId} - exited abnormally => " . $e->getMessage()); + nlog("{$this->secret_id} - exited abnormally => " . $e->getMessage()); $content = [ "Processing transactions for account: {$this->bank_integration->account->key} failed", @@ -101,8 +109,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue try { $this->processTransactions(); } catch (\Exception $e) { - $secretId = $this->account->bank_integration_nordigen_secret_id ?: config('ninja.nortigen.secret_id'); - nlog("{$secretId} - exited abnormally => " . $e->getMessage()); + nlog("{$this->secret_id} - exited abnormally => " . $e->getMessage()); $content = [ "Processing transactions for account: {$this->bank_integration->account->key} failed", diff --git a/app/Jobs/Bank/ProcessBankTransactionsYodlee.php b/app/Jobs/Bank/ProcessBankTransactionsYodlee.php index ac30579f6a95..1e74c95533c9 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsYodlee.php +++ b/app/Jobs/Bank/ProcessBankTransactionsYodlee.php @@ -11,7 +11,6 @@ namespace App\Jobs\Bank; -use App\Helpers\Bank\Yodlee\Nordigen; use App\Helpers\Bank\Yodlee\Transformer\AccountTransformer; use App\Helpers\Bank\Yodlee\Yodlee; use App\Libraries\MultiDB; From d5a9a1c839db87e288455db8c94137889bdc3190 Mon Sep 17 00:00:00 2001 From: paulwer Date: Tue, 12 Dec 2023 07:08:40 +0100 Subject: [PATCH 32/69] fixes --- .../Bank/ProcessBankTransactionsNordigen.php | 32 +++++++++---------- .../Bank/ProcessBankTransactionsYodlee.php | 8 ++--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index 6ba21e3bbc04..4f2d887ec705 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -53,22 +53,6 @@ class ProcessBankTransactionsNordigen implements ShouldQueue $this->bank_integration = $bank_integration; $this->from_date = $bank_integration->from_date; $this->company = $this->bank_integration->company; - - if ($this->bank_integration->integration_type != BankIntegration::INTEGRATION_TYPE_NORDIGEN) - throw new \Exception("Invalid BankIntegration Type"); - - if (!(($this->account->bank_integration_secret_id && $this->account->integration_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))) - throw new \Exception("Missing credentials for bank_integration service nortigen"); - - if ($this->account->bank_integration_secret_id && $this->account->bank_integration_secret_key) { - $this->secret_id = $this->account->bank_integration_secret_id; - $this->secret_key = $this->account->bank_integration_secret_key; - } else { - $this->secret_id = config('ninja.nordigen.secret_id'); - $this->secret_key = config('ninja.nordigen.secret_key'); - } - - $this->nordigen = new Nordigen($this->secret_id, $this->secret_key); } /** @@ -80,6 +64,22 @@ class ProcessBankTransactionsNordigen implements ShouldQueue public function handle() { + if ($this->bank_integration->integration_type != BankIntegration::INTEGRATION_TYPE_NORDIGEN) + throw new \Exception("Invalid BankIntegration Type"); + + if (!(($this->account->bank_integration_nordigen_secret_id && $this->account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))) + throw new \Exception("Missing credentials for bank_integration service nortigen"); + + if ($this->account->bank_integration_nordigen_secret_id && $this->account->bank_integration_nordigen_secret_key) { + $this->secret_id = $this->account->bank_integration_nordigen_secret_id; + $this->secret_key = $this->account->bank_integration_nordigen_secret_key; + } else { + $this->secret_id = config('ninja.nordigen.secret_id'); + $this->secret_key = config('ninja.nordigen.secret_key'); + } + + $this->nordigen = new Nordigen($this->secret_id, $this->secret_key); + set_time_limit(0); //Loop through everything until we are up to date diff --git a/app/Jobs/Bank/ProcessBankTransactionsYodlee.php b/app/Jobs/Bank/ProcessBankTransactionsYodlee.php index 1e74c95533c9..e88819c0d55a 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsYodlee.php +++ b/app/Jobs/Bank/ProcessBankTransactionsYodlee.php @@ -52,10 +52,6 @@ class ProcessBankTransactionsYodlee implements ShouldQueue $this->bank_integration = $bank_integration; $this->from_date = $bank_integration->from_date; $this->company = $this->bank_integration->company; - - if ($this->bank_integration->integration_type != BankIntegration::INTEGRATION_TYPE_YODLEE) - throw new \Exception("Invalid BankIntegration Type"); - } /** @@ -66,6 +62,10 @@ class ProcessBankTransactionsYodlee implements ShouldQueue */ public function handle() { + + if ($this->bank_integration->integration_type != BankIntegration::INTEGRATION_TYPE_YODLEE) + throw new \Exception("Invalid BankIntegration Type"); + set_time_limit(0); //Loop through everything until we are up to date From 27ab262a4fda008a4697526f4d6f3f77f5b10099 Mon Sep 17 00:00:00 2001 From: paulwer Date: Tue, 12 Dec 2023 07:23:53 +0100 Subject: [PATCH 33/69] fixes --- app/Helpers/Bank/Nordigen/Nordigen.php | 33 ++++++++++--------- .../Transformer/AccountTransformer.php | 2 +- .../Transformer/TransactionTransformer.php | 2 +- .../Controllers/BankIntegrationController.php | 3 +- .../BankIntegrationTransformer.php | 17 +++++----- 5 files changed, 30 insertions(+), 27 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index 153303b37b1c..b9cddac153ab 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -29,13 +29,11 @@ class Nordigen public function __construct(string $secret_id, string $secret_key) { - $this->test_mode = config('ninja.nordigen.test_mode'); $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 @@ -64,22 +62,26 @@ class Nordigen // TODO: return null on not found public function getAccount(string $account_id) { + try { + $out = new \stdClass(); - $out = new \stdClass(); + $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->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"]); - - $it = new AccountTransformer(); - return $it->transform($out); + $it = new AccountTransformer(); + return $it->transform($out); + } catch (\Exception $e) { + if (strpos($e->getMessage(), "Invalid Account ID") !== false) + return null; + throw $e; + } } public function isAccountActive(string $account_id) { - try { $account = $this->client->account($account_id)->getAccountMetaData(); @@ -88,10 +90,11 @@ class Nordigen return true; } catch (\Exception $e) { - // TODO: check for not-found exception - return false; - } + if (strpos($e->getMessage(), "Invalid Account ID") !== false) + return false; + throw $e; + } } /** @@ -100,11 +103,9 @@ class Nordigen */ public function getTransactions(string $accountId, string $dateFrom = null) { - $transactionResponse = $this->client->account($accountId)->getAccountTransactions($dateFrom); $it = new TransactionTransformer(); return $it->transform($transactionResponse); - } } diff --git a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php index 1a164bdf991a..f0aa77379478 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/AccountTransformer.php @@ -102,7 +102,7 @@ class AccountTransformer implements AccountTransformerInterface } return [ - 'id' => 'nordigen:' . $nordigen_account->metadata["id"], + 'id' => $nordigen_account->metadata["id"], 'account_type' => "bank", 'account_name' => $nordigen_account->data["iban"], 'account_status' => $nordigen_account->metadata["status"], diff --git a/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php index 7342f8c6397c..95d5d733c23d 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php @@ -95,7 +95,7 @@ class TransactionTransformer implements BankRevenueInterface 'category_id' => 0, // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc 'category_type' => $transaction["additionalInformation"], // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc 'date' => $transaction["bookingDate"], - 'description' => array_key_exists('bank_remittanceInformationStructured', $transaction) ? $transaction["bank_remittanceInformationStructured"] : array_key_exists('bank_remittanceInformationStructuredArray', $transaction) ? implode($transaction["bank_remittanceInformationStructured"], '\r\n') : '', + 'description' => array_key_exists('bank_remittanceInformationStructured', $transaction) ? $transaction["bank_remittanceInformationStructured"] : (array_key_exists('bank_remittanceInformationStructuredArray', $transaction) ? implode($transaction["bank_remittanceInformationStructured"], '\r\n') : ''), // 'description' => `IBAN: ${elem . json["bank_debtorAccount"] && elem . json["bank_debtorAccount"]["iban"] ? elem . json["bank_debtorAccount"]["iban"] : ' -'}\nVerwendungszweck: ${elem . json["bank_remittanceInformationStructured"] || ' -'}\nName: ${elem . json["bank_debtorName"] || ' -'}`, // 2 fields to get data from (structured and structuredArray (have to be joined)) // TODO: debitor name & iban & bic 'base_type' => (int) $transaction["transactionAmount"]["amount"] > 0 ? 'DEBIT' : 'CREDIT', diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 5ddd92cb91c9..54f87c7caeef 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -270,7 +270,7 @@ class BankIntegrationController extends BaseController $nordigen = ($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) ? new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key) : new Nordigen(config('ninja.nordigen.secret_id'), config('ninja.nordigen.secret_key')); - BankIntegration::withTrashed()->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function (BankIntegration $bank_integration) use ($nordigen) { + BankIntegration::withTrashed()->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->whereNotNull('nordigen_account_id')->each(function (BankIntegration $bank_integration) use ($nordigen) { $account = $nordigen->getAccount($bank_integration->nordigen_account_id); if (!$account) { @@ -280,6 +280,7 @@ class BankIntegrationController extends BaseController return; } + $bank_integration->disabled_upstream = false; $bank_integration->bank_account_status = $account['account_status']; $bank_integration->balance = $account['current_balance']; $bank_integration->currency = $account['account_currency']; diff --git a/app/Transformers/BankIntegrationTransformer.php b/app/Transformers/BankIntegrationTransformer.php index 7757ee7559e7..8496b23e11ac 100644 --- a/app/Transformers/BankIntegrationTransformer.php +++ b/app/Transformers/BankIntegrationTransformer.php @@ -50,20 +50,21 @@ class BankIntegrationTransformer extends EntityTransformer { return [ 'id' => (string) $this->encodePrimaryKey($bank_integration->id), - 'provider_name' => (string)$bank_integration->provider_name ?: '', + 'provider_name' => (string) $bank_integration->provider_name ?: '', 'provider_id' => (int) $bank_integration->provider_id ?: 0, 'bank_account_id' => (int) $bank_integration->bank_account_id ?: 0, 'bank_account_name' => (string) $bank_integration->bank_account_name ?: '', 'bank_account_number' => (string) $bank_integration->bank_account_number ?: '', - 'bank_account_status' => (string)$bank_integration->bank_account_status ?: '', - 'bank_account_type' => (string)$bank_integration->bank_account_type ?: '', - 'balance' => (float)$bank_integration->balance ?: 0, - 'currency' => (string)$bank_integration->currency ?: '', - 'nickname' => (string)$bank_integration->nickname ?: '', - 'from_date' => (string)$bank_integration->from_date ?: '', + 'bank_account_status' => (string) $bank_integration->bank_account_status ?: '', + 'bank_account_type' => (string) $bank_integration->bank_account_type ?: '', + 'nordigen_institution_id' => (string) $bank_integration->nordigen_institution_id ?: '', + 'balance' => (float) $bank_integration->balance ?: 0, + 'currency' => (string) $bank_integration->currency ?: '', + 'nickname' => (string) $bank_integration->nickname ?: '', + 'from_date' => (string) $bank_integration->from_date ?: '', 'is_deleted' => (bool) $bank_integration->is_deleted, 'disabled_upstream' => (bool) $bank_integration->disabled_upstream, - 'auto_sync' => (bool)$bank_integration->auto_sync, + 'auto_sync' => (bool) $bank_integration->auto_sync, 'created_at' => (int) $bank_integration->created_at, 'updated_at' => (int) $bank_integration->updated_at, 'archived_at' => (int) $bank_integration->deleted_at, From dcdd9112d063bf0d0a4ca9ca5d74f202253c724b Mon Sep 17 00:00:00 2001 From: paulwer Date: Tue, 12 Dec 2023 07:52:53 +0100 Subject: [PATCH 34/69] fixes --- .../Bank/Nordigen/Transformer/TransactionTransformer.php | 2 -- app/Http/Controllers/Bank/NordigenController.php | 5 +++-- app/Http/Controllers/BankIntegrationController.php | 2 +- app/Jobs/Bank/ProcessBankTransactionsNordigen.php | 6 +++++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php index 95d5d733c23d..6f8b77f505f4 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php @@ -68,8 +68,6 @@ class TransactionTransformer implements BankRevenueInterface public function transform($transactionResponse) { - - Log::info($transactionResponse); $data = []; if (!array_key_exists('transactions', $transactionResponse) || !array_key_exists('booked', $transactionResponse["transactions"])) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index c65693c8254c..47cdcf601316 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -178,7 +178,7 @@ class NordigenController extends BaseController $nordigen_account = $nordigen->getAccount($nordigenAccountId); - $existing_bank_integration = BankIntegration::where('bank_account_id', $nordigen_account['id'])->where('company_id', $company->id)->first(); + $existing_bank_integration = BankIntegration::where('nordigen_account_id', $nordigen_account['id'])->where('company_id', $company->id)->first(); if (!$existing_bank_integration) { @@ -199,7 +199,7 @@ class NordigenController extends BaseController $bank_integration->currency = $nordigen_account['account_currency']; $bank_integration->disabled_upstream = false; $bank_integration->auto_sync = true; - $bank_integration->from_date = now()->subYear(); + $bank_integration->from_date = now()->subDays(90); // default max-fetch interval of nortigen is 90 days $bank_integration->save(); @@ -212,6 +212,7 @@ class NordigenController extends BaseController $existing_bank_integration->bank_account_status = $account['account_status']; $existing_bank_integration->disabled_upstream = false; $existing_bank_integration->auto_sync = true; + $bank_integration->from_date = now()->subDays(90); // default max-fetch interval of nortigen is 90 days $existing_bank_integration->save(); diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 54f87c7caeef..69ffebdd7ba8 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -304,7 +304,7 @@ class BankIntegrationController extends BaseController $account = $user->account; - $bank_integration = BankIntegration::withTrashed()->where('bank_account_id', $acc_id)->company()->firstOrFail(); + $bank_integration = BankIntegration::withTrashed()->where('bank_account_id', $acc_id)->orWhere('nordigen_account_id', $acc_id)->company()->firstOrFail(); // @turbo124 please check if ($bank_integration->integration_type == BankIntegration::INTEGRATION_TYPE_YODLEE) $this->removeAccountYodlee($account, $bank_integration); diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index 4f2d887ec705..7f7f110b8ab2 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -24,6 +24,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Log; class ProcessBankTransactionsNordigen implements ShouldQueue { @@ -141,9 +142,9 @@ class ProcessBankTransactionsNordigen implements ShouldQueue $this->nordigen_account = $this->nordigen->getAccount($this->bank_integration->nordigen_account_id); + $this->bank_integration->disabled_upstream = false; $this->bank_integration->bank_account_status = $this->nordigen_account['account_status']; $this->bank_integration->balance = $this->nordigen_account['current_balance']; - $this->bank_integration->currency = $this->nordigen_account['account_currency']; $this->bank_integration->save(); @@ -155,6 +156,8 @@ class ProcessBankTransactionsNordigen implements ShouldQueue //Get transaction count object $transactions = $this->nordigen->getTransactions($this->bank_integration->nordigen_account_id, $this->from_date); + Log::Info($transactions); + //Get int count $count = sizeof($transactions); @@ -180,6 +183,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue $now = now(); + foreach ($transactions as $transaction) { if (BankTransaction::where('transaction_id', $transaction['transaction_id'])->where('company_id', $this->company->id)->withTrashed()->exists()) From da6488adaced39c0cd2427c0a9878bcdeaea14cc Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 13 Dec 2023 15:37:19 +0100 Subject: [PATCH 35/69] remove credentials on account level + remove ApiException + fixes --- .env.example | 5 ++- app/Exceptions/NordigenApiException.php | 41 ------------------- app/Helpers/Bank/Nordigen/Nordigen.php | 18 +++++--- .../Controllers/Bank/NordigenController.php | 23 +++++++---- .../Controllers/BankIntegrationController.php | 35 ++++++++-------- .../Bank/ProcessBankTransactionsNordigen.php | 16 ++------ app/Jobs/Ninja/BankTransactionSync.php | 23 +++++------ app/Models/Account.php | 4 -- ...3_11_26_082959_add_bank_integration_id.php | 2 - 9 files changed, 64 insertions(+), 103 deletions(-) delete mode 100644 app/Exceptions/NordigenApiException.php diff --git a/.env.example b/.env.example index 17130c14603f..ea1f4a320728 100644 --- a/.env.example +++ b/.env.example @@ -68,4 +68,7 @@ MICROSOFT_REDIRECT_URI= APPLE_CLIENT_ID= APPLE_CLIENT_SECRET= -APPLE_REDIRECT_URI= \ No newline at end of file +APPLE_REDIRECT_URI= + +NORDIGEN_SECRET_ID= +NORDIGEN_SECRET_KEY= diff --git a/app/Exceptions/NordigenApiException.php b/app/Exceptions/NordigenApiException.php deleted file mode 100644 index bc81d968a470..000000000000 --- a/app/Exceptions/NordigenApiException.php +++ /dev/null @@ -1,41 +0,0 @@ -getMessage() && strlen($this->getMessage()) >= 1) { - $msg = $this->getMessage(); - } - - return response()->json([ - 'message' => $msg, - ], 400); - } -} diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index b9cddac153ab..52f4d29a5134 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -9,15 +9,18 @@ * @license https://www.elastic.co/licensing/elastic-license * * Documentation of Api-Usage: https://developer.gocardless.com/bank-account-data/overview + * + * Institutions: Are Banks or Payment-Providers, which manages bankaccounts. + * + * Accounts: Accounts are existing bank_accounts at a specific institution. + * + * Requisitions: Are registered/active user-flows to authenticate one or many accounts. After completition, the accoundId could be used to fetch data for this account. After the access expires, the user could create a new requisition to connect accounts again. */ namespace App\Helpers\Bank\Nordigen; -use App\Exceptions\NordigenApiException; use App\Helpers\Bank\Nordigen\Transformer\AccountTransformer; use App\Helpers\Bank\Nordigen\Transformer\TransactionTransformer; -use Log; -use Nordigen\NordigenPHP\Exceptions\NordigenExceptions\NordigenException; class Nordigen { @@ -27,11 +30,14 @@ class Nordigen protected \Nordigen\NordigenPHP\API\NordigenClient $client; - public function __construct(string $secret_id, string $secret_key) + public function __construct() { $this->test_mode = config('ninja.nordigen.test_mode'); - $this->client = new \Nordigen\NordigenPHP\API\NordigenClient($secret_id, $secret_key); + if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) + throw new \Exception('missing nordigen credentials'); + + $this->client = new \Nordigen\NordigenPHP\API\NordigenClient(config('ninja.nordigen.secret_id'), config('ninja.nordigen.secret_key')); $this->client->createAccessToken(); // access_token is valid 24h -> so we dont have to implement a refresh-cycle } @@ -51,7 +57,7 @@ class Nordigen if ($this->test_mode && $initutionId != $this->sandbox_institutionId) throw new \Exception('invalid institutionId while in test-mode'); - return $this->client->requisition->createRequisition($redirect, $initutionId, null, $reference); // we dont reuse existing requisitions, to prevent double usage of them. see: deleteAccount + return $this->client->requisition->createRequisition($redirect, $initutionId, null, $reference); } public function getRequisition(string $requisitionId) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 47cdcf601316..f277a1836dc8 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -18,6 +18,7 @@ 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\Http\Request; use Log; @@ -37,10 +38,13 @@ class NordigenController extends BaseController $company = $request->getCompany(); $account = $company->account; - if (!(($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))) + if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid"); - $nordigen = ($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) ? new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key) : new Nordigen(config('ninja.nordigen.secret_id'), config('ninja.nordigen.secret_key')); + if (!(Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) + return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=not-available"); + + $nordigen = new Nordigen(); // show bank_selection_screen, when institution_id is not present if (!array_key_exists("institution_id", $data)) { @@ -157,11 +161,14 @@ class NordigenController extends BaseController $company = Company::where('company_key', $context["company_key"])->firstOrFail(); $account = $company->account; - if (!(($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))) + if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid"); + if (!(Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) + return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=not-available"); + // fetch requisition - $nordigen = ($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) ? new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key) : new Nordigen(config('ninja.nordigen.secret_id'), config('ninja.nordigen.secret_key')); + $nordigen = new Nordigen(); $requisition = $nordigen->getRequisition($context["requisitionId"]); // check validity of requisition @@ -222,9 +229,9 @@ class NordigenController extends BaseController } // perform update in background - $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->each(function ($bank_integration) use ($company) { + $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->each(function ($bank_integration) { - ProcessBankTransactionsNordigen::dispatch($company->account, $bank_integration); + ProcessBankTransactionsNordigen::dispatch($bank_integration); }); @@ -304,10 +311,10 @@ class NordigenController extends BaseController { $account = auth()->user()->account; - if (!(($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))) + 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 = ($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) ? new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key) : new Nordigen(config('ninja.nordigen.secret_id'), config('ninja.nordigen.secret_key')); + $nordigen = new Nordigen(); return response()->json($nordigen->getInstitutions()); } diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 69ffebdd7ba8..36adeda18661 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -30,6 +30,7 @@ use App\Models\BankIntegration; use App\Models\User; use App\Repositories\BankIntegrationRepository; use App\Transformers\BankIntegrationTransformer; +use App\Utils\Ninja; use App\Utils\Traits\MakesHash; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response; @@ -206,17 +207,17 @@ class BankIntegrationController extends BaseController return response()->json(BankIntegration::query()->company(), 200); // Processing transactions for each bank account - if ($user->account->bank_integration_yodlee_account_id) + if (Ninja::isHosted() && $user->account->bank_integration_yodlee_account_id) $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->each(function ($bank_integration) use ($user_account) { ProcessBankTransactionsYodlee::dispatch($user_account, $bank_integration); }); - if (($user->account->bank_integration_nordigen_secret_id && $user->account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) - $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->each(function ($bank_integration) use ($user_account) { + if (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key') && (Ninja::isSelfHost() || (Ninja::isHosted() && $user_account->isPaid() && $user_account->plan == 'enterprise'))) + $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->each(function ($bank_integration) { - ProcessBankTransactionsNordigen::dispatch($user_account, $bank_integration); + ProcessBankTransactionsNordigen::dispatch($bank_integration); }); @@ -265,10 +266,10 @@ class BankIntegrationController extends BaseController { $account = $user->account; - if (!(($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))) + if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) return; - $nordigen = ($account->bank_integration_nordigen_secret_id && $account->bank_integration_nordigen_secret_key) ? new Nordigen($account->bank_integration_nordigen_secret_id, $account->bank_integration_nordigen_secret_key) : new Nordigen(config('ninja.nordigen.secret_id'), config('ninja.nordigen.secret_key')); + $nordigen = new Nordigen(); BankIntegration::withTrashed()->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->whereNotNull('nordigen_account_id')->each(function (BankIntegration $bank_integration) use ($nordigen) { $account = $nordigen->getAccount($bank_integration->nordigen_account_id); @@ -334,18 +335,20 @@ class BankIntegrationController extends BaseController */ public function getTransactions(AdminBankIntegrationRequest $request) { - /** @var \App\Models\User $user */ - $user = auth()->user(); + /** @var \App\Models\Account $account */ + $account = auth()->user()->account(); - // Yodlee - $user->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->each(function ($bank_integration) use ($user) { - (new ProcessBankTransactionsYodlee($user->account, $bank_integration))->handle(); - }); + if (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise') { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { + (new ProcessBankTransactionsYodlee($account, $bank_integration))->handle(); + }); + } - // Nordigen - $user->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->each(function ($bank_integration) use ($user) { - (new ProcessBankTransactionsYodlee($user->account, $bank_integration))->handle(); - }); + if (config("ninja.nortigen.secret_id") && config("ninja.nortigen.secret_key") && (Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->cursor()->each(function ($bank_integration) { + (new ProcessBankTransactionsNordigen($bank_integration))->handle(); + }); + } return response()->json(['message' => 'Fetching transactions....'], 200); } diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index 7f7f110b8ab2..bd49eb1efd3c 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -30,7 +30,6 @@ class ProcessBankTransactionsNordigen implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - private Account $account; private BankIntegration $bank_integration; private string $secret_id; private string $secret_key; @@ -48,9 +47,8 @@ class ProcessBankTransactionsNordigen implements ShouldQueue /** * Create a new job instance. */ - public function __construct(Account $account, BankIntegration $bank_integration) + public function __construct(BankIntegration $bank_integration) { - $this->account = $account; $this->bank_integration = $bank_integration; $this->from_date = $bank_integration->from_date; $this->company = $this->bank_integration->company; @@ -68,18 +66,10 @@ class ProcessBankTransactionsNordigen implements ShouldQueue if ($this->bank_integration->integration_type != BankIntegration::INTEGRATION_TYPE_NORDIGEN) throw new \Exception("Invalid BankIntegration Type"); - if (!(($this->account->bank_integration_nordigen_secret_id && $this->account->bank_integration_nordigen_secret_key) || (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')))) + if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) throw new \Exception("Missing credentials for bank_integration service nortigen"); - if ($this->account->bank_integration_nordigen_secret_id && $this->account->bank_integration_nordigen_secret_key) { - $this->secret_id = $this->account->bank_integration_nordigen_secret_id; - $this->secret_key = $this->account->bank_integration_nordigen_secret_key; - } else { - $this->secret_id = config('ninja.nordigen.secret_id'); - $this->secret_key = config('ninja.nordigen.secret_key'); - } - - $this->nordigen = new Nordigen($this->secret_id, $this->secret_key); + $this->nordigen = new Nordigen(); set_time_limit(0); diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php index 786125545dd1..923743805c3c 100644 --- a/app/Jobs/Ninja/BankTransactionSync.php +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -53,30 +53,29 @@ class BankTransactionSync implements ShouldQueue nlog("syncing transactions - yodlee"); Account::with('bank_integrations')->whereNotNull('bank_integration_yodlee_account_id')->cursor()->each(function ($account) { - // $queue = Ninja::isHosted() ? 'bank' : 'default'; if ($account->isPaid() && $account->plan == 'enterprise') { $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { (new ProcessBankTransactionsYodlee($account, $bank_integration))->handle(); }); } + }); } - nlog("syncing transactions - nordigen"); + if (config("ninja.nortigen.secret_id") && config("ninja.nortigen.secret_key")) { // @turbo124 check condition, when to execute this should be placed here (isSelfHosted || isPro/isEnterprise) + nlog("syncing transactions - nordigen"); - if (config("ninja.nortigen.secret_id") && config("ninja.nortigen.secret_key")) Account::with('bank_integrations')->cursor()->each(function ($account) { - $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { - (new ProcessBankTransactionsNordigen($account, $bank_integration))->handle(); - }); - }); - else - Account::with('bank_integrations')->whereNotNull('bank_integration_nordigen_secret_id')->whereNotNull('bank_integration_nordigen_secret_key')->cursor()->each(function ($account) { - $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { - (new ProcessBankTransactionsNordigen($account, $bank_integration))->handle(); - }); + + if ((Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->cursor()->each(function ($bank_integration) { + (new ProcessBankTransactionsNordigen($bank_integration))->handle(); + }); + } + }); + } nlog("syncing transactions - done"); } diff --git a/app/Models/Account.php b/app/Models/Account.php index 7d95b2b217ac..888029ee3f8d 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -70,8 +70,6 @@ use Laracasts\Presenter\PresentableTrait; * @property string|null $account_sms_verification_number * @property bool $account_sms_verified * @property string|null $bank_integration_yodlee_account_id - * @property string|null $bank_integration_nordigen_secret_id - * @property string|null $bank_integration_nordigen_secret_key * @property int $is_trial * @property-read int|null $bank_integrations_count * @property-read int|null $companies_count @@ -128,8 +126,6 @@ class Account extends BaseModel 'platform', 'set_react_as_default_ap', 'inapp_transaction_id', - 'bank_integration_nordigen_secret_id', - 'bank_integration_nordigen_secret_key', ]; protected $casts = [ diff --git a/database/migrations/2023_11_26_082959_add_bank_integration_id.php b/database/migrations/2023_11_26_082959_add_bank_integration_id.php index da6f778877da..faa2bd68cb68 100644 --- a/database/migrations/2023_11_26_082959_add_bank_integration_id.php +++ b/database/migrations/2023_11_26_082959_add_bank_integration_id.php @@ -29,8 +29,6 @@ return new class extends Migration { // MAYBE migration of account->bank_account_id etc Schema::table('accounts', function (Blueprint $table) { $table->renameColumn('bank_integration_account_id', 'bank_integration_yodlee_account_id'); - $table->string('bank_integration_nordigen_secret_id')->nullable(); - $table->string('bank_integration_nordigen_secret_key')->nullable(); }); } From 8ba64bd435294fe987642c6acd84934b8fdc314e Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 13 Dec 2023 15:38:37 +0100 Subject: [PATCH 36/69] fix spelling --- app/Http/Controllers/Bank/NordigenController.php | 4 ++-- app/Http/Controllers/BankIntegrationController.php | 2 +- app/Jobs/Bank/ProcessBankTransactionsNordigen.php | 2 +- app/Jobs/Ninja/BankTransactionSync.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index f277a1836dc8..777cf62a57fc 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -206,7 +206,7 @@ class NordigenController extends BaseController $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 nortigen is 90 days + $bank_integration->from_date = now()->subDays(90); // default max-fetch interval of nordigen is 90 days $bank_integration->save(); @@ -219,7 +219,7 @@ class NordigenController extends BaseController $existing_bank_integration->bank_account_status = $account['account_status']; $existing_bank_integration->disabled_upstream = false; $existing_bank_integration->auto_sync = true; - $bank_integration->from_date = now()->subDays(90); // default max-fetch interval of nortigen is 90 days + $bank_integration->from_date = now()->subDays(90); // default max-fetch interval of nordigen is 90 days $existing_bank_integration->save(); diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 36adeda18661..88d69d24185a 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -344,7 +344,7 @@ class BankIntegrationController extends BaseController }); } - if (config("ninja.nortigen.secret_id") && config("ninja.nortigen.secret_key") && (Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) { + if (config("ninja.nordigen.secret_id") && config("ninja.nordigen.secret_key") && (Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) { $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->cursor()->each(function ($bank_integration) { (new ProcessBankTransactionsNordigen($bank_integration))->handle(); }); diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index bd49eb1efd3c..ada76ea25ada 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -67,7 +67,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue throw new \Exception("Invalid BankIntegration Type"); if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) - throw new \Exception("Missing credentials for bank_integration service nortigen"); + throw new \Exception("Missing credentials for bank_integration service nordigen"); $this->nordigen = new Nordigen(); diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php index 923743805c3c..a4b284d75545 100644 --- a/app/Jobs/Ninja/BankTransactionSync.php +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -63,7 +63,7 @@ class BankTransactionSync implements ShouldQueue }); } - if (config("ninja.nortigen.secret_id") && config("ninja.nortigen.secret_key")) { // @turbo124 check condition, when to execute this should be placed here (isSelfHosted || isPro/isEnterprise) + if (config("ninja.nordigen.secret_id") && config("ninja.nordigen.secret_key")) { // @turbo124 check condition, when to execute this should be placed here (isSelfHosted || isPro/isEnterprise) nlog("syncing transactions - nordigen"); Account::with('bank_integrations')->cursor()->each(function ($account) { From 1d0506a83286e3dd3a90e71c334835cfe1854906 Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 13 Dec 2023 15:45:27 +0100 Subject: [PATCH 37/69] fixes for env --- config/ninja.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/ninja.php b/config/ninja.php index 5053cc40d9ca..cf8da90026ff 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -219,8 +219,8 @@ return [ 'config_name' => env("YODLEE_CONFIG_NAME", false), ], 'nordigen' => [ - 'client_id' => env('NORDIGEN_SECRET_ID', false), - 'client_secret' => env('NORDIGEN_SECRET_KEY', false), + 'secret_id' => env('NORDIGEN_SECRET_ID', false), + 'secret_key' => env('NORDIGEN_SECRET_KEY', false), 'test_mode' => env("NORDIGEN_TEST_MODE", false), ], 'licenses' => env('LICENSES', false), From e12fb1f7658037a5590f18863895ba689da0b128 Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 13 Dec 2023 16:18:18 +0100 Subject: [PATCH 38/69] fixes for sync transactions --- app/Helpers/Bank/Nordigen/Nordigen.php | 3 +++ .../Transformer/TransactionTransformer.php | 19 +++++++++++++++---- .../Controllers/BankIntegrationController.php | 9 ++++++--- .../Bank/ProcessBankTransactionsNordigen.php | 16 +++++++++------- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index 52f4d29a5134..23aad272fe68 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -21,6 +21,7 @@ namespace App\Helpers\Bank\Nordigen; use App\Helpers\Bank\Nordigen\Transformer\AccountTransformer; use App\Helpers\Bank\Nordigen\Transformer\TransactionTransformer; +use Log; class Nordigen { @@ -111,6 +112,8 @@ class Nordigen { $transactionResponse = $this->client->account($accountId)->getAccountTransactions($dateFrom); + Log::info($transactionResponse); + $it = new TransactionTransformer(); return $it->transform($transactionResponse); } diff --git a/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php index 6f8b77f505f4..0d98b30c0033 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php @@ -86,17 +86,28 @@ class TransactionTransformer implements BankRevenueInterface if (!array_key_exists('transactionId', $transaction) || !array_key_exists('transactionAmount', $transaction)) throw new \Exception('invalid dataset'); + // description could be in varios places + $description = ''; + if (array_key_exists('bank_remittanceInformationStructured', $transaction)) + $description = $transaction["bank_remittanceInformationStructured"]; + else if (array_key_exists('bank_remittanceInformationStructuredArray', $transaction)) + $description = implode($transaction["bank_remittanceInformationStructured"], '\r\n'); + else if (array_key_exists('remittanceInformationUnstructured', $transaction)) + $description = $transaction["remittanceInformationUnstructured"]; + else + Log::warning("Missing description for the following transaction: " . json_encode($transaction)); + return [ - 'transaction_id' => 'nordigen:' . $transaction["transactionId"], + 'transaction_id' => $transaction["transactionId"], 'amount' => abs((int) $transaction["transactionAmount"]["amount"]), 'currency_id' => $this->convertCurrency($transaction["transactionAmount"]["currency"]), 'category_id' => 0, // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc - 'category_type' => $transaction["additionalInformation"], // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc + 'category_type' => array_key_exists('additionalInformation', $transaction) ? $transaction["additionalInformation"] : '', // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc 'date' => $transaction["bookingDate"], - 'description' => array_key_exists('bank_remittanceInformationStructured', $transaction) ? $transaction["bank_remittanceInformationStructured"] : (array_key_exists('bank_remittanceInformationStructuredArray', $transaction) ? implode($transaction["bank_remittanceInformationStructured"], '\r\n') : ''), + 'description' => $description, // 'description' => `IBAN: ${elem . json["bank_debtorAccount"] && elem . json["bank_debtorAccount"]["iban"] ? elem . json["bank_debtorAccount"]["iban"] : ' -'}\nVerwendungszweck: ${elem . json["bank_remittanceInformationStructured"] || ' -'}\nName: ${elem . json["bank_debtorName"] || ' -'}`, // 2 fields to get data from (structured and structuredArray (have to be joined)) // TODO: debitor name & iban & bic - 'base_type' => (int) $transaction["transactionAmount"]["amount"] > 0 ? 'DEBIT' : 'CREDIT', + 'base_type' => (int) $transaction["transactionAmount"]["amount"] <= 0 ? 'DEBIT' : 'CREDIT', ]; } diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 88d69d24185a..7510e511416f 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -35,6 +35,7 @@ use App\Utils\Traits\MakesHash; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response; use Illuminate\Support\Facades\Cache; +use Log; class BankIntegrationController extends BaseController { @@ -199,13 +200,13 @@ class BankIntegrationController extends BaseController $user_account = $user->account; + // if (Cache::get("throttle_polling:{$user_account->key}")) // @todo uncomment for PR + // return response()->json(BankIntegration::query()->company(), 200); + $this->refreshAccountsYodlee($user); $this->refreshAccountsNordigen($user); - if (Cache::get("throttle_polling:{$user_account->key}")) - return response()->json(BankIntegration::query()->company(), 200); - // Processing transactions for each bank account if (Ninja::isHosted() && $user->account->bank_integration_yodlee_account_id) $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->each(function ($bank_integration) use ($user_account) { @@ -346,6 +347,8 @@ class BankIntegrationController extends BaseController if (config("ninja.nordigen.secret_id") && config("ninja.nordigen.secret_key") && (Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) { $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->cursor()->each(function ($bank_integration) { + Log::info($bank_integration); + (new ProcessBankTransactionsNordigen($bank_integration))->handle(); }); } diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index ada76ea25ada..acd010886e69 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -31,8 +31,6 @@ class ProcessBankTransactionsNordigen implements ShouldQueue use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; private BankIntegration $bank_integration; - private string $secret_id; - private string $secret_key; private ?string $from_date; @@ -80,7 +78,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue try { $this->updateAccount(); } catch (\Exception $e) { - nlog("{$this->secret_id} - exited abnormally => " . $e->getMessage()); + nlog("{$this->bank_integration->account->key} - exited abnormally => " . $e->getMessage()); $content = [ "Processing transactions for account: {$this->bank_integration->account->key} failed", @@ -89,7 +87,8 @@ class ProcessBankTransactionsNordigen implements ShouldQueue ]; $this->bank_integration->company->notification(new GenericNinjaAdminNotification($content))->ninja(); - return; + + throw $e; } if (!$this->nordigen_account) return; @@ -100,7 +99,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue try { $this->processTransactions(); } catch (\Exception $e) { - nlog("{$this->secret_id} - exited abnormally => " . $e->getMessage()); + nlog("{$this->bank_integration->account->key} - exited abnormally => " . $e->getMessage()); $content = [ "Processing transactions for account: {$this->bank_integration->account->key} failed", @@ -109,7 +108,8 @@ class ProcessBankTransactionsNordigen implements ShouldQueue ]; $this->bank_integration->company->notification(new GenericNinjaAdminNotification($content))->ninja(); - return; + + throw $e; } } @@ -122,10 +122,12 @@ class ProcessBankTransactionsNordigen implements ShouldQueue private function updateAccount() { + Log::info("try to execute updateAccount"); if (!$this->nordigen->isAccountActive($this->bank_integration->nordigen_account_id)) { $this->bank_integration->disabled_upstream = true; $this->bank_integration->save(); $this->stop_loop = false; + Log::info("account inactive"); // @turbo124 @todo send email for expired account return; } @@ -176,7 +178,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue foreach ($transactions as $transaction) { - if (BankTransaction::where('transaction_id', $transaction['transaction_id'])->where('company_id', $this->company->id)->withTrashed()->exists()) + if (BankTransaction::where('transaction_id', $transaction['transaction_id'])->where('company_id', $this->company->id)->where('bank_integration_id', $this->bank_integration->id)->withTrashed()->exists()) continue; //this should be much faster to insert than using ::create() From 7a70eb873f216eac9e8961a6dcd173b1cec1494e Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 13 Dec 2023 16:30:26 +0100 Subject: [PATCH 39/69] adding additional fields to bank_transactions https://github.com/invoiceninja/invoiceninja/issues/8042 --- .../Nordigen/Transformer/TransactionTransformer.php | 4 ++-- app/Models/BankTransaction.php | 12 ++++++++---- .../2023_11_26_082959_add_bank_integration_id.php | 6 ++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php index 0d98b30c0033..f8fb444797f9 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php @@ -105,8 +105,8 @@ class TransactionTransformer implements BankRevenueInterface 'category_type' => array_key_exists('additionalInformation', $transaction) ? $transaction["additionalInformation"] : '', // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc 'date' => $transaction["bookingDate"], 'description' => $description, - // 'description' => `IBAN: ${elem . json["bank_debtorAccount"] && elem . json["bank_debtorAccount"]["iban"] ? elem . json["bank_debtorAccount"]["iban"] : ' -'}\nVerwendungszweck: ${elem . json["bank_remittanceInformationStructured"] || ' -'}\nName: ${elem . json["bank_debtorName"] || ' -'}`, // 2 fields to get data from (structured and structuredArray (have to be joined)) - // TODO: debitor name & iban & bic + 'debitor' => array_key_exists('debtorAccount', $transaction) && array_key_exists('iban', $transaction["debtorAccount"]) ? $transaction['debtorAccount']['iban'] : null, + 'debitor_name' => array_key_exists('debtorName', $transaction) ? $transaction['debtorName'] : null, 'base_type' => (int) $transaction["transactionAmount"]["amount"] <= 0 ? 'DEBIT' : 'CREDIT', ]; diff --git a/app/Models/BankTransaction.php b/app/Models/BankTransaction.php index c3a7321e5c50..378945656705 100644 --- a/app/Models/BankTransaction.php +++ b/app/Models/BankTransaction.php @@ -34,6 +34,8 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property string|null $date * @property int $bank_account_id * @property string|null $description + * @property string|null $debitor + * @property string|null $debitor_name * @property string $invoice_ids * @property int|null $expense_id * @property int|null $vendor_id @@ -68,7 +70,7 @@ class BankTransaction extends BaseModel use SoftDeletes; use MakesHash; use Filterable; - + const STATUS_UNMATCHED = 1; const STATUS_MATCHED = 2; @@ -84,10 +86,12 @@ class BankTransaction extends BaseModel 'base_type', 'expense_id', 'vendor_id', - 'amount' + 'amount', + 'debitor', + 'debitor_name' ]; - + public function getInvoiceIds() { $collection = collect(); @@ -162,7 +166,7 @@ class BankTransaction extends BaseModel // return $this->belongsTo(Expense::class)->withTrashed(); // } - public function service() :BankService + public function service(): BankService { return new BankService($this); } diff --git a/database/migrations/2023_11_26_082959_add_bank_integration_id.php b/database/migrations/2023_11_26_082959_add_bank_integration_id.php index faa2bd68cb68..11d777a7e55c 100644 --- a/database/migrations/2023_11_26_082959_add_bank_integration_id.php +++ b/database/migrations/2023_11_26_082959_add_bank_integration_id.php @@ -31,6 +31,12 @@ return new class extends Migration { $table->renameColumn('bank_integration_account_id', 'bank_integration_yodlee_account_id'); }); + // MAYBE migration of account->bank_account_id etc + Schema::table('bank_transactions', function (Blueprint $table) { + $table->string('debitor')->nullable(); // iban, credit-card info or else + $table->string('debitor_name')->nullable(); // name + }); + } /** From 4a42e11884a46a007eabdce455b2c55777094fc6 Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 13 Dec 2023 16:32:51 +0100 Subject: [PATCH 40/69] revert yodlee-change: https://github.com/paulwer/invoiceninja/pull/1 --- app/Http/Controllers/Bank/YodleeController.php | 8 ++++---- app/Http/Controllers/BankIntegrationController.php | 10 +++++----- app/Jobs/Bank/MatchBankTransactions.php | 4 ++-- app/Jobs/Bank/ProcessBankTransactionsYodlee.php | 6 +++--- app/Jobs/Ninja/BankTransactionSync.php | 2 +- app/Models/Account.php | 2 +- .../2023_11_26_082959_add_bank_integration_id.php | 5 ----- tests/Feature/Bank/YodleeApiTest.php | 2 +- 8 files changed, 17 insertions(+), 22 deletions(-) diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index e3a386a7de42..ac9b0b478195 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -29,10 +29,10 @@ class YodleeController extends BaseController $company = $request->getCompany(); - if ($company->account->bank_integration_yodlee_account_id) { + if ($company->account->bank_integration_account_id) { $flow = 'edit'; - $token = $company->account->bank_integration_yodlee_account_id; + $token = $company->account->bank_integration_account_id; } else { $flow = 'add'; @@ -40,7 +40,7 @@ class YodleeController extends BaseController $token = $response->user->loginName; - $company->account->bank_integration_yodlee_account_id = $token; + $company->account->bank_integration_account_id = $token; $company->push(); } @@ -301,7 +301,7 @@ class YodleeController extends BaseController return response()->json(['message' => 'Account does not exist.'], 400); } - $yodlee = new Yodlee($user->account->bank_integration_yodlee_account_id); + $yodlee = new Yodlee($user->account->bank_integration_account_id); $summary = $yodlee->getAccountSummary($account_number); diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 7510e511416f..e722f0fab86b 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -208,7 +208,7 @@ class BankIntegrationController extends BaseController $this->refreshAccountsNordigen($user); // Processing transactions for each bank account - if (Ninja::isHosted() && $user->account->bank_integration_yodlee_account_id) + if (Ninja::isHosted() && $user->account->bank_integration_account_id) $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->each(function ($bank_integration) use ($user_account) { ProcessBankTransactionsYodlee::dispatch($user_account, $bank_integration); @@ -229,10 +229,10 @@ class BankIntegrationController extends BaseController private function refreshAccountsYodlee(User $user) { - if (!$user->account->bank_integration_yodlee_account_id) + if (!$user->account->bank_integration_account_id) return; - $yodlee = new Yodlee($user->account->bank_integration_yodlee_account_id); + $yodlee = new Yodlee($user->account->bank_integration_account_id); $accounts = $yodlee->getAccounts(); @@ -319,11 +319,11 @@ class BankIntegrationController extends BaseController private function removeAccountYodlee(Account $account, BankIntegration $bank_integration) { - if (!$account->bank_integration_yodlee_account_id) { + if (!$account->bank_integration_account_id) { return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); } - $yodlee = new Yodlee($account->bank_integration_yodlee_account_id); + $yodlee = new Yodlee($account->bank_integration_account_id); $yodlee->deleteAccount($bank_integration->bank_account_id); } diff --git a/app/Jobs/Bank/MatchBankTransactions.php b/app/Jobs/Bank/MatchBankTransactions.php index c48275c85960..750d9017528f 100644 --- a/app/Jobs/Bank/MatchBankTransactions.php +++ b/app/Jobs/Bank/MatchBankTransactions.php @@ -88,8 +88,8 @@ class MatchBankTransactions implements ShouldQueue $this->company = Company::query()->find($this->company_id); - if ($this->company->account->bank_integration_yodlee_account_id) { - $yodlee = new Yodlee($this->company->account->bank_integration_yodlee_account_id); + if ($this->company->account->bank_integration_account_id) { + $yodlee = new Yodlee($this->company->account->bank_integration_account_id); } else { $yodlee = false; } diff --git a/app/Jobs/Bank/ProcessBankTransactionsYodlee.php b/app/Jobs/Bank/ProcessBankTransactionsYodlee.php index e88819c0d55a..f5344169e211 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsYodlee.php +++ b/app/Jobs/Bank/ProcessBankTransactionsYodlee.php @@ -77,7 +77,7 @@ class ProcessBankTransactionsYodlee implements ShouldQueue try { $this->processTransactions(); } catch (\Exception $e) { - nlog("{$this->account->bank_integration_yodlee_account_id} - exited abnormally => " . $e->getMessage()); + nlog("{$this->account->bank_integration_account_id} - exited abnormally => " . $e->getMessage()); $content = [ "Processing transactions for account: {$this->bank_integration->account->key} failed", @@ -97,7 +97,7 @@ class ProcessBankTransactionsYodlee implements ShouldQueue private function processTransactions() { - $yodlee = new Yodlee($this->account->bank_integration_yodlee_account_id); + $yodlee = new Yodlee($this->account->bank_integration_account_id); if (!$yodlee->getAccount($this->bank_integration->bank_account_id)) { $this->bank_integration->disabled_upstream = true; @@ -193,7 +193,7 @@ class ProcessBankTransactionsYodlee implements ShouldQueue public function middleware() { - return [new WithoutOverlapping($this->account->bank_integration_yodlee_account_id)]; + return [new WithoutOverlapping($this->account->bank_integration_account_id)]; } public function backoff() diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php index a4b284d75545..446552c8793f 100644 --- a/app/Jobs/Ninja/BankTransactionSync.php +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -52,7 +52,7 @@ class BankTransactionSync implements ShouldQueue if (Ninja::isHosted()) { // @turbo124 @todo I migrated the schedule for the job within the kernel to execute on all platforms and use the same expression here to determine if yodlee can run or not. Please chek/verify nlog("syncing transactions - yodlee"); - Account::with('bank_integrations')->whereNotNull('bank_integration_yodlee_account_id')->cursor()->each(function ($account) { + Account::with('bank_integrations')->whereNotNull('bank_integration_account_id')->cursor()->each(function ($account) { if ($account->isPaid() && $account->plan == 'enterprise') { $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { diff --git a/app/Models/Account.php b/app/Models/Account.php index 888029ee3f8d..f7fac464e360 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -69,7 +69,7 @@ use Laracasts\Presenter\PresentableTrait; * @property string|null $account_sms_verification_code * @property string|null $account_sms_verification_number * @property bool $account_sms_verified - * @property string|null $bank_integration_yodlee_account_id + * @property string|null $bank_integration_account_id * @property int $is_trial * @property-read int|null $bank_integrations_count * @property-read int|null $companies_count diff --git a/database/migrations/2023_11_26_082959_add_bank_integration_id.php b/database/migrations/2023_11_26_082959_add_bank_integration_id.php index 11d777a7e55c..3630b4d0d08c 100644 --- a/database/migrations/2023_11_26_082959_add_bank_integration_id.php +++ b/database/migrations/2023_11_26_082959_add_bank_integration_id.php @@ -26,11 +26,6 @@ return new class extends Migration { $bank_integration->save(); }); - // MAYBE migration of account->bank_account_id etc - Schema::table('accounts', function (Blueprint $table) { - $table->renameColumn('bank_integration_account_id', 'bank_integration_yodlee_account_id'); - }); - // MAYBE migration of account->bank_account_id etc Schema::table('bank_transactions', function (Blueprint $table) { $table->string('debitor')->nullable(); // iban, credit-card info or else diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index 034dc3a24072..a2cb1b31eda1 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -75,7 +75,7 @@ class YodleeApiTest extends TestCase public function testIncomeMatchingAndPaymentGeneration() { - $this->account->bank_integration_yodlee_account_id = 'sbMem62e1e69547bfb2'; + $this->account->bank_integration_account_id = 'sbMem62e1e69547bfb2'; $this->account->save(); $invoice = Invoice::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id]); From dbbde047e9de8f8f8747ed43e65c62aaaabb7e11 Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 13 Dec 2023 16:57:51 +0100 Subject: [PATCH 41/69] fixes --- .../Bank/Nordigen/Transformer/TransactionTransformer.php | 8 ++++---- app/Http/Controllers/Bank/NordigenController.php | 2 +- app/Http/Controllers/BankIntegrationController.php | 6 +++--- routes/api.php | 7 +++---- routes/web.php | 1 + 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php index f8fb444797f9..bd905bb42eab 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php @@ -88,10 +88,10 @@ class TransactionTransformer implements BankRevenueInterface // description could be in varios places $description = ''; - if (array_key_exists('bank_remittanceInformationStructured', $transaction)) - $description = $transaction["bank_remittanceInformationStructured"]; - else if (array_key_exists('bank_remittanceInformationStructuredArray', $transaction)) - $description = implode($transaction["bank_remittanceInformationStructured"], '\r\n'); + if (array_key_exists('remittanceInformationStructured', $transaction)) + $description = $transaction["remittanceInformationStructured"]; + else if (array_key_exists('remittanceInformationStructuredArray', $transaction)) + $description = implode(' \r\n', $transaction["remittanceInformationStructuredArray"]); else if (array_key_exists('remittanceInformationUnstructured', $transaction)) $description = $transaction["remittanceInformationUnstructured"]; else diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 777cf62a57fc..e3c68963f395 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -61,7 +61,7 @@ class NordigenController extends BaseController // redirect to requisition flow try { - $requisition = $nordigen->createRequisition(config('ninja.app_url') . '/api/v1/nordigen/confirm', $data['institution_id'], $request->token); + $requisition = $nordigen->createRequisition(config('ninja.app_url') . '/nordigen/confirm', $data['institution_id'], $request->token); } 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 Log::error($e); $responseBody = $e->getResponse()->getBody(); diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index e722f0fab86b..8c1862b1516c 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -200,13 +200,13 @@ class BankIntegrationController extends BaseController $user_account = $user->account; - // if (Cache::get("throttle_polling:{$user_account->key}")) // @todo uncomment for PR - // return response()->json(BankIntegration::query()->company(), 200); - $this->refreshAccountsYodlee($user); $this->refreshAccountsNordigen($user); + if (Cache::get("throttle_polling:{$user_account->key}")) + return response()->json(BankIntegration::query()->company(), 200); + // Processing transactions for each bank account if (Ninja::isHosted() && $user->account->bank_integration_account_id) $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->each(function ($bank_integration) use ($user_account) { diff --git a/routes/api.php b/routes/api.php index cca9161dc91e..ea565393a676 100644 --- a/routes/api.php +++ b/routes/api.php @@ -400,7 +400,9 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::get('statics', StaticController::class); // Route::post('apple_pay/upload_file','ApplyPayController::class, 'upload'); - Route::post('api/v1/yodlee/status/{account_number}', [YodleeController::class, 'accountStatus']); + Route::post('api/v1/yodlee/status/{account_number}', [YodleeController::class, 'accountStatus']); // @todo @turbo124 check route-path?! + + Route::get('nordigen/institutions', [NordigenController::class, 'institutions'])->name('nordigen.institutions'); }); Route::post('api/v1/sms_reset', [TwilioController::class, 'generate2faResetCode'])->name('sms_reset.generate')->middleware('throttle:10,1'); @@ -427,9 +429,6 @@ Route::post('api/v1/yodlee/data_updates', [YodleeController::class, 'dataUpdates 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::get('api/v1/nordigen/institutions', [NordigenController::class, 'institutions'])->middleware('throttle:100,1')->middleware('token_auth')->name('nordigen_institutions'); -Route::any('api/v1/nordigen/confirm', [NordigenController::class, 'confirm'])->middleware('throttle:100,1')->name('nordigen_callback'); - Route::get('api/v1/protected_download/{hash}', [ProtectedDownloadController::class, 'index'])->name('protected_download')->middleware('throttle:300,1'); Route::fallback([BaseController::class, 'notFound'])->middleware('throttle:404'); diff --git a/routes/web.php b/routes/web.php index c47c9e139332..4f60662b7e68 100644 --- a/routes/web.php +++ b/routes/web.php @@ -57,6 +57,7 @@ Route::get('stripe/completed', [StripeConnectController::class, 'completed'])->n Route::get('yodlee/onboard/{token}', [YodleeController::class, 'auth'])->name('yodlee.auth'); Route::get('nordigen/connect/{token}', [NordigenController::class, 'connect'])->name('nordigen.connect'); +Route::any('nordigen/confirm', [NordigenController::class, 'confirm'])->name('nordigen.confirm'); Route::get('checkout/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', [Checkout3dsController::class, 'index'])->middleware('domain_db')->name('checkout.3ds_redirect'); Route::get('mollie/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', [Mollie3dsController::class, 'index'])->middleware('domain_db')->name('mollie.3ds_redirect'); From 06d2baf0010cfa45cb4cec72d9277f9b603345db Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 13 Dec 2023 17:01:34 +0100 Subject: [PATCH 42/69] fixes --- app/Transformers/BankTransactionTransformer.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/Transformers/BankTransactionTransformer.php b/app/Transformers/BankTransactionTransformer.php index 401d5a0a1eb8..ca122a183be6 100644 --- a/app/Transformers/BankTransactionTransformer.php +++ b/app/Transformers/BankTransactionTransformer.php @@ -63,11 +63,13 @@ class BankTransactionTransformer extends EntityTransformer 'bank_account_id' => (int) $bank_transaction->bank_account_id, 'status_id' => (string) $bank_transaction->status_id, 'description' => (string) $bank_transaction->description ?: '', + 'debitor' => (string) $bank_transaction->debitor ?: '', + 'debitor_name' => (string) $bank_transaction->debitor_name ?: '', 'base_type' => (string) $bank_transaction->base_type ?: '', 'invoice_ids' => (string) $bank_transaction->invoice_ids ?: '', - 'expense_id'=> (string) $bank_transaction->expense_id ?: '', - 'payment_id'=> (string) $this->encodePrimaryKey($bank_transaction->payment_id) ?: '', - 'vendor_id'=> (string) $this->encodePrimaryKey($bank_transaction->vendor_id) ?: '', + 'expense_id' => (string) $bank_transaction->expense_id ?: '', + 'payment_id' => (string) $this->encodePrimaryKey($bank_transaction->payment_id) ?: '', + 'vendor_id' => (string) $this->encodePrimaryKey($bank_transaction->vendor_id) ?: '', 'bank_transaction_rule_id' => (string) $this->encodePrimaryKey($bank_transaction->bank_transaction_rule_id) ?: '', 'is_deleted' => (bool) $bank_transaction->is_deleted, 'created_at' => (int) $bank_transaction->created_at, From 3735845c44cbc155eaf232036c6f1e291059f9be Mon Sep 17 00:00:00 2001 From: paulwer Date: Fri, 15 Dec 2023 13:10:45 +0100 Subject: [PATCH 43/69] display occured errors in flow with a view --- app/Helpers/Bank/Nordigen/Nordigen.php | 2 +- .../Controllers/Bank/NordigenController.php | 106 +++++++++++--- .../views/bank/nordigen/connect.blade.php | 67 --------- .../views/bank/nordigen/handler.blade.php | 138 ++++++++++++++++++ 4 files changed, 222 insertions(+), 91 deletions(-) delete mode 100644 resources/views/bank/nordigen/connect.blade.php create mode 100644 resources/views/bank/nordigen/handler.blade.php diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index 23aad272fe68..cc83f304770a 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -47,7 +47,7 @@ class Nordigen public function getInstitutions() { if ($this->test_mode) - return (array) $this->client->institution->getInstitution($this->sandbox_institutionId); + return [$this->client->institution->getInstitution($this->sandbox_institutionId)]; return $this->client->institution->getInstitutions(); } diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index e3c68963f395..b772a25e5b8c 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -32,31 +32,50 @@ class NordigenController extends BaseController $data = $request->all(); $context = $request->getTokenContent(); - if (!$context || $context["context"] != "nordigen" || array_key_exists("requisitionId", $context)) - return response()->redirectTo(($context && array_key_exists("redirect", $context) ? $context["redirect"] : config('ninja.app_url')) . "?action=nordigen_connect&status=failed&reason=token-invalid"); + if (!$context) + return view('bank.nordigen.handler', [ + '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', [ + 'failed_reason' => "token-invalid", + "redirectUrl" => ($context["redirect"]) . "?action=nordigen_connect&status=failed&reason=token-invalid", + ]); $company = $request->getCompany(); $account = $company->account; if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) - return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid"); + return view('bank.nordigen.handler', [ + '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() && $account->isPaid() && $account->plan == 'enterprise'))) - return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=not-available"); + return view('bank.nordigen.handler', [ + 'company' => $company, + 'account' => $company->account, + 'failed_reason' => "not-available", + "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=not-available", + ]); $nordigen = new Nordigen(); // show bank_selection_screen, when institution_id is not present if (!array_key_exists("institution_id", $data)) { $data = [ - 'token' => $request->token, - 'context' => $context, - 'institutions' => $nordigen->getInstitutions(), 'company' => $company, 'account' => $company->account, + 'institutions' => $nordigen->getInstitutions(), + 'redirectUrl' => $context["redirect"] . "?action=nordigen_connect&status=user-aborted" ]; - return view('bank.nordigen.connect', $data); + return view('bank.nordigen.handler', $data); } // redirect to requisition flow @@ -64,20 +83,33 @@ class NordigenController extends BaseController $requisition = $nordigen->createRequisition(config('ninja.app_url') . '/nordigen/confirm', $data['institution_id'], $request->token); } 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 Log::error($e); - $responseBody = $e->getResponse()->getBody(); - Log::info($responseBody); + Log::info((string) $e->getResponse()->getBody()); + $responseBody = (string) $e->getResponse()->getBody(); - if (property_exists($responseBody, "institution_id")) // provided institution_id was wrong - return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=institution-invalid"); - else if (property_exists($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 response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=token-invalid"); + if (str_contains($responseBody, '"institution_id"')) // provided institution_id was wrong + return view('bank.nordigen.handler', [ + 'company' => $company, + 'account' => $company->account, + 'failed_reason' => "institution-invalid", + "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=institution-invalid", + ]); + else if (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', [ + 'company' => $company, + 'account' => $company->account, + 'failed_reason' => "token-invalid", + "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=token-invalid", + ]); else - return response()->redirectTo($data["redirect"] . "?action=nordigen_connect&status=failed&reason=unknown"); + return view('bank.nordigen.handler', [ + 'company' => $company, + 'account' => $company->account, + 'failed_reason' => "unknown", + "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=unknown", + ]); } // save cache - if (array_key_exists("redirect", $data)) - $context["redirect"] = $data["redirect"]; $context["requisitionId"] = $requisition["id"]; Cache::put($request->token, $context, 3600); @@ -155,17 +187,30 @@ class NordigenController extends BaseController $context = Cache::get($data["ref"]); if (!$context || $context["context"] != "nordigen" || !array_key_exists("requisitionId", $context)) - return response()->redirectTo(($context && array_key_exists("redirect", $context) ? $context["redirect"] : config('ninja.app_url')) . "?action=nordigen_connect&status=failed&reason=ref-invalid"); + return view('bank.nordigen.handler', [ + '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", + ]); $company = Company::where('company_key', $context["company_key"])->firstOrFail(); $account = $company->account; if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) - return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=account-config-invalid"); + return view('bank.nordigen.handler', [ + '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() && $account->isPaid() && $account->plan == 'enterprise'))) - return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=not-available"); + return view('bank.nordigen.handler', [ + '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(); @@ -173,11 +218,26 @@ class NordigenController extends BaseController // check validity of requisition if (!$requisition) - return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-not-found"); + return view('bank.nordigen.handler', [ + '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 response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-invalid-status&status=" . $requisition["status"]); + return view('bank.nordigen.handler', [ + '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 response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=failed&reason=requisition-no-accounts"); + return view('bank.nordigen.handler', [ + '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 = []; diff --git a/resources/views/bank/nordigen/connect.blade.php b/resources/views/bank/nordigen/connect.blade.php deleted file mode 100644 index 716ee1837783..000000000000 --- a/resources/views/bank/nordigen/connect.blade.php +++ /dev/null @@ -1,67 +0,0 @@ -@extends('layouts.ninja') -@section('meta_title', ctrans('texts.new_bank_account')) - -@push('head') - - - -@endpush - -@section('body') - -
- -@endsection - -@push('footer') - - - - - -@endpush \ No newline at end of file diff --git a/resources/views/bank/nordigen/handler.blade.php b/resources/views/bank/nordigen/handler.blade.php new file mode 100644 index 000000000000..f32b380d7854 --- /dev/null +++ b/resources/views/bank/nordigen/handler.blade.php @@ -0,0 +1,138 @@ +@extends('layouts.ninja') +@section('meta_title', ctrans('texts.new_bank_account')) + +@push('head') + + + +@endpush + +@section('body') + +
+ +@endsection + +@push('footer') + + + + + +@endpush \ No newline at end of file From 10050d567b9492a18f271203512e9073e5d0dd10 Mon Sep 17 00:00:00 2001 From: paulwer Date: Fri, 15 Dec 2023 14:45:52 +0100 Subject: [PATCH 44/69] translations --- .../Controllers/Bank/NordigenController.php | 17 + .../ConfirmNordigenBankIntegrationRequest.php | 1 + .../ConnectNordigenBankIntegrationRequest.php | 1 + lang/de/texts.php | 606 ++++++++--------- lang/en/texts.php | 625 +++++++++--------- .../views/bank/nordigen/handler.blade.php | 43 +- 6 files changed, 666 insertions(+), 627 deletions(-) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index b772a25e5b8c..8c1fc41b6663 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -18,6 +18,7 @@ use App\Http\Requests\Nordigen\ConnectNordigenBankIntegrationRequest; use App\Jobs\Bank\ProcessBankTransactionsNordigen; use App\Models\BankIntegration; use App\Models\Company; +use App\Models\User; use App\Utils\Ninja; use Cache; use Illuminate\Http\Request; @@ -31,9 +32,11 @@ class NordigenController extends BaseController { $data = $request->all(); $context = $request->getTokenContent(); + $lang = $data['lang'] ?? 'en'; if (!$context) return view('bank.nordigen.handler', [ + 'lang' => $lang, 'failed_reason' => "token-invalid", "redirectUrl" => config("ninja.app_url") . "?action=nordigen_connect&status=failed&reason=token-invalid", ]); @@ -41,6 +44,7 @@ class NordigenController extends BaseController $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", ]); @@ -50,6 +54,7 @@ class NordigenController extends BaseController 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", @@ -58,6 +63,7 @@ class NordigenController extends BaseController if (!(Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) return view('bank.nordigen.handler', [ + 'lang' => $lang, 'company' => $company, 'account' => $company->account, 'failed_reason' => "not-available", @@ -69,6 +75,7 @@ class NordigenController extends BaseController // show bank_selection_screen, when institution_id is not present if (!array_key_exists("institution_id", $data)) { $data = [ + 'lang' => $lang, 'company' => $company, 'account' => $company->account, 'institutions' => $nordigen->getInstitutions(), @@ -88,6 +95,7 @@ class NordigenController extends BaseController 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", @@ -95,6 +103,7 @@ class NordigenController extends BaseController ]); else if (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", @@ -102,6 +111,7 @@ class NordigenController extends BaseController ]); else return view('bank.nordigen.handler', [ + 'lang' => $lang, 'company' => $company, 'account' => $company->account, 'failed_reason' => "unknown", @@ -184,10 +194,12 @@ class NordigenController extends BaseController { $data = $request->all(); + $lang = $data['lang'] ?? 'en'; $context = Cache::get($data["ref"]); 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", ]); @@ -198,6 +210,7 @@ class NordigenController extends BaseController 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", @@ -206,6 +219,7 @@ class NordigenController extends BaseController if (!(Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) return view('bank.nordigen.handler', [ + 'lang' => $lang, 'company' => $company, 'account' => $company->account, 'failed_reason' => "not-available", @@ -219,6 +233,7 @@ class NordigenController extends BaseController // check validity of requisition if (!$requisition) return view('bank.nordigen.handler', [ + 'lang' => $lang, 'company' => $company, 'account' => $company->account, 'failed_reason' => "requisition-not-found", @@ -226,6 +241,7 @@ class NordigenController extends BaseController ]); if ($requisition["status"] != "LN") return view('bank.nordigen.handler', [ + 'lang' => $lang, 'company' => $company, 'account' => $company->account, 'failed_reason' => "requisition-invalid-status", @@ -233,6 +249,7 @@ class NordigenController extends BaseController ]); if (sizeof($requisition["accounts"]) == 0) return view('bank.nordigen.handler', [ + 'lang' => $lang, 'company' => $company, 'account' => $company->account, 'failed_reason' => "requisition-no-accounts", diff --git a/app/Http/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php b/app/Http/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php index 879410bb8bbc..a529b46ed1cf 100644 --- a/app/Http/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php +++ b/app/Http/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php @@ -34,6 +34,7 @@ class ConfirmNordigenBankIntegrationRequest extends Request { return [ 'ref' => 'required|string', // nordigen redirects only with the ref-property + 'lang' => 'string', ]; } } diff --git a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php index b68b1d76ce16..f4834d05d385 100644 --- a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php +++ b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php @@ -38,6 +38,7 @@ class ConnectNordigenBankIntegrationRequest extends Request public function rules() { return [ + 'lang' => 'string', 'institution_id' => 'string', 'redirect' => 'string', // TODO: @turbo124 @todo validate, that this is a url without / at the end ]; diff --git a/lang/de/texts.php b/lang/de/texts.php index 3bf53d2dbd76..9e0731882600 100644 --- a/lang/de/texts.php +++ b/lang/de/texts.php @@ -96,7 +96,7 @@ $lang = array( 'powered_by' => 'Unterstützt durch', 'no_items' => 'Keine Elemente', 'recurring_invoices' => 'Wiederkehrende Rechnungen', - 'recurring_help' => '

Senden Sie Ihren Kunden automatisch die gleichen Rechnungen wöchentlich, zweimonatlich, monatlich, vierteljährlich oder jährlich zu.

+ 'recurring_help' => '

Senden Sie Ihren Kunden automatisch die gleichen Rechnungen wöchentlich, zweimonatlich, monatlich, vierteljährlich oder jährlich zu.

Verwenden Sie :MONTH, :QUARTER oder :YEAR für dynamische Daten. Grundlegende Mathematik funktioniert auch, zum Beispiel :MONTH-1.

Beispiele für dynamische Rechnungsvariablen:

    @@ -3859,308 +3859,308 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting', 'registration_url' => 'Registrierungs-URL', 'show_product_cost' => 'Produktkosten anzeigen', 'complete' => 'Fertigstellen', - 'next' => 'Weiter', - 'next_step' => 'Nächster Schritt', - 'notification_credit_sent_subject' => 'Gutschrift :invoice wurde an Kunde gesendet.', - 'notification_credit_viewed_subject' => 'Gutschrift :invoice wurde von :client angesehen.', - 'notification_credit_sent' => 'Der folgende Kunde :client hat eine Gutschrift :invoice über :amount erhalten.', - 'notification_credit_viewed' => 'Der folgende Kunde :client hat die Gutschrift :credit über :amount angeschaut.', - 'reset_password_text' => 'Bitte geben Sie ihre E-Mail-Adresse an, um das Passwort zurücksetzen zu können.', - 'password_reset' => 'Passwort zurücksetzten', - 'account_login_text' => 'Willkommen! Schön Sie zu sehen.', - 'request_cancellation' => 'Storno beantragen', - 'delete_payment_method' => 'Zahlungsmethode löschen', - 'about_to_delete_payment_method' => 'Diese Zahlungsmethode wird gelöscht.', - 'action_cant_be_reversed' => 'Diese Aktion kann nicht widerrufen werden', - 'profile_updated_successfully' => 'Das Profil wurde erfolgreich aktualisiert.', - 'currency_ethiopian_birr' => 'Äthiopischer Birr', - 'client_information_text' => 'Bitte nutzen Sie eine postfähige Anschrift.', - 'status_id' => 'Rechnungsstatus', - 'email_already_register' => 'Diese E-Mail wird bereits von einem anderen Account verwendet', - 'locations' => 'Standorte', - 'freq_indefinitely' => 'Unendlich', - 'cycles_remaining' => 'Verbleibende Zyklen', - 'i_understand_delete' => 'Ich bin mir der Risiken bewusst, löschen', - 'download_files' => 'Dateien herunterladen', - 'download_timeframe' => 'Nutzen Sie diesen Link um Ihre Dateien herunterzuladen. Der Link läuft in einer Stunde ab.', - 'new_signup' => 'Neue Registrierung', - 'new_signup_text' => 'Ein neuer Benutzer wurde von :user - :email von der IP: :ip erstellt', - 'notification_payment_paid_subject' => 'Neue Zahlung von :client', - 'notification_partial_payment_paid_subject' => 'Neue Anzahlung von :client', - 'notification_payment_paid' => 'Eine Zahlung von :amount wurde von Kunde :client auf :invoice geleistet', - 'notification_partial_payment_paid' => 'Eine Teilzahlung in Höhe von :amount wurde vom Kunden :client auf :invoice geleistet', - 'notification_bot' => 'Benachrichtigungs-Bot', - 'invoice_number_placeholder' => 'Rechnung # :invoice', - 'entity_number_placeholder' => ':entity # :entity_number', - 'email_link_not_working' => 'Wenn die Schaltfläche oben nicht funktioniert, klicken Sie bitte auf den Link', - 'display_log' => 'Log anzeigen', - 'send_fail_logs_to_our_server' => 'Fehler in Echtzeit melden', - 'setup' => 'Setup', - 'quick_overview_statistics' => 'Schnellüberblick & Statistiken', - 'update_your_personal_info' => 'Aktualisieren Sie Ihre Profil', - 'name_website_logo' => 'Name, Webseite & Logo', - 'make_sure_use_full_link' => 'Es ist wichtig, den gesamten Link \'\'https://example.com\'\' einzutragen.', - 'personal_address' => 'Private Adresse', - 'enter_your_personal_address' => 'Bitte geben Sie Ihre Rechnungsadresse an', - 'enter_your_shipping_address' => 'Bitte geben Sie Ihr Lieferadresse an', - 'list_of_invoices' => 'Liste der Rechnungen', - 'with_selected' => 'Breite ausgewählt', - 'invoice_still_unpaid' => 'Diese Rechnung wurde noch nicht beglichen. Klicken um zu vervollständigen.', - 'list_of_recurring_invoices' => 'Liste der wiederkehrende Rechnungen', - 'details_of_recurring_invoice' => 'Details über wiederkehrende Rechnung', - 'cancellation' => 'Storno', - 'about_cancellation' => 'Wenn Sie die wiederkehrende Rechnung stoppen möchten, klicken Sie bitte auf , um die Stornierung zu beantragen.', - 'cancellation_warning' => 'Achtung! Sie beantragen die Stornierung dieses Dienstes. Ihr Dienst kann ohne weitere Mitteilung an Sie storniert werden.', - 'cancellation_pending' => 'Kündigung in Bearbeitung! Wir melden uns bei Ihnen...', - 'list_of_payments' => 'Liste der Zahlungen', - 'payment_details' => 'Details zu der Zahlung', - 'list_of_payment_invoices' => 'Liste der Rechnungen die von dieser Zahlung betroffenen sind', - 'list_of_payment_methods' => 'Liste der Zahlungsmethoden', - 'payment_method_details' => 'Details zu der Zahlungsmethode', - 'permanently_remove_payment_method' => 'Zahlungsmethode endgültig entfernen.', - 'warning_action_cannot_be_reversed' => 'Achtung! Diese Aktion kann nicht widerrufen werden!', - 'confirmation' => 'Bestätigung', - 'list_of_quotes' => 'Angebote', - 'waiting_for_approval' => 'Annahme ausstehend', - 'quote_still_not_approved' => 'Dieses Angebot wurde noch nicht angenommen.', - 'list_of_credits' => 'Gutschriften', - 'required_extensions' => 'Benötigte PHP-Erweiterungen', - 'php_version' => 'PHP Version', - 'writable_env_file' => 'Beschreibbare .env-Datei', - 'env_not_writable' => 'die .env-Datei ist vom aktuellen Benutzer nicht beschreibbar', - 'minumum_php_version' => 'Minimale PHP-Version', - 'satisfy_requirements' => 'Prüfen Sie, ob alle Anforderungen erfüllt sind.', - 'oops_issues' => 'Entschuldigung, das ist wohl etwas schiefgelaufen!', - 'open_in_new_tab' => 'In neuem Fenster öffnen', - 'complete_your_payment' => 'Zahlung abschließen', - 'authorize_for_future_use' => 'Zahlungsmethode für zukünftige Verwendung freigeben', - 'page' => 'Seite', - 'per_page' => 'pro Seite', - 'of' => 'von', - 'view_credit' => 'Gutschrift anzeigen', - 'to_view_entity_password' => 'Um :entity anzusehen, geben Sie bitte Ihr Passwort ein.', - 'showing_x_of' => 'Zeige :first bis :last von :total Ergebnissen', - 'no_results' => 'Kein Ergebnis gefunden.', - 'payment_failed_subject' => 'Zahlung für Kunde :client fehlgeschlagen', - 'payment_failed_body' => 'Eine Zahlung von :client schlug fehl: :message', - 'register' => 'Registrieren', - 'register_label' => 'Benutzerkonto in wenigen Sekunden erstellen', - 'password_confirmation' => 'Passwort bestätigen', - 'verification' => 'Bestätigung', - 'complete_your_bank_account_verification' => 'Ein Bankkonto muss verifiziert werden, bevor es genutzt werden kann.', - 'checkout_com' => 'Checkout.com', - 'footer_label' => 'Copyright © :year :company.', - 'credit_card_invalid' => 'Die angegebene Kreditkartennummer ist ungültig.', - 'month_invalid' => 'Der angegebene Monat ist ungültig', - 'year_invalid' => 'Das angegebene Jahr ist ungültig', - 'https_required' => 'HTTPS ist Pflicht, das Formular wird nicht funktionieren', - 'if_you_need_help' => 'Wenn Sie Hilfe benötigen, wenden Sie sich bitte an unsere', - 'update_password_on_confirm' => 'Nach dem Update des Passworts wird Ihr Account bestätigt.', - 'bank_account_not_linked' => 'Um mit einem Bankkonto zu bezahlen, müssen Sie es zunächst als Zahlungsmethode hinzufügen.', - 'application_settings_label' => 'Lassen Sie uns grundlegende Informationen über Ihr Invoice Ninja speichern!', - 'recommended_in_production' => 'Ausdrücklich für Produktivumgebungen empfohlen!', - 'enable_only_for_development' => 'Nur in Entwicklungsumgebung aktivieren', - 'test_pdf' => 'PDF testen', - 'checkout_authorize_label' => 'Checkout.com kann als Zahlungsmethode für die zukünftige Verwendung gespeichert werden, sobald Sie Ihre erste Transaktion abgeschlossen haben. Vergessen Sie nicht, die Option "Kreditkartendaten speichern" während des Zahlungsvorgangs zu aktivieren.', - 'sofort_authorize_label' => 'Das Bankkonto (SOFORT) kann als Zahlungsmethode für die zukünftige Verwendung gespeichert werden, sobald Sie Ihre erste Transaktion abgeschlossen haben. Vergessen Sie nicht, die Option "Zahlungsdetails speichern" während des Zahlungsvorgangs zu aktivieren.', - 'node_status' => 'Node-Status', - 'npm_status' => 'NPM-Status', - 'node_status_not_found' => 'Node konnte nicht gefunden werden - ist es installiert?', - 'npm_status_not_found' => 'NPM konnte nicht gefunden werden - ist es installiert?', - 'locked_invoice' => 'Diese Rechnung ist gesperrt und kann nicht bearbeitet werden.', - 'downloads' => 'Downloads', - 'resource' => 'Resourcen', - 'document_details' => 'Details zu dem Dokument', - 'hash' => 'Hash', - 'resources' => 'Ressourcen', - 'allowed_file_types' => 'Erlaubte Dateitypen:', - 'common_codes' => 'Gängige Codes und ihre Bedeutungen', - 'payment_error_code_20087' => '20087: Falsche Track-Daten (ungültiger CVV und/oder Verfallsdatum)', - 'download_selected' => 'Ausgewählte herunterladen', - 'to_pay_invoices' => 'Um Rechnungen zu bezahlen, müssen Sie', - 'add_payment_method_first' => 'Zahlungsart hinzufügen', - 'no_items_selected' => 'Keine Objekte ausgewählt.', - 'payment_due' => 'Zahlung überfallig', - 'account_balance' => 'Saldo', - 'thanks' => 'Danke', - 'minimum_required_payment' => 'Mindestbetrag für die Zahlung ist :amount', - 'under_payments_disabled' => 'Das Unternehmen unterstützt keine Unterbezahlungen.', - 'over_payments_disabled' => 'Das Unternehmen unterstützt keine Überbezahlungen.', - 'saved_at' => 'Gespeichert um :time', - 'credit_payment' => 'Gutschrift auf Rechnung :invoice_number angewendet', - 'credit_subject' => 'Neue Gutschrift :number von :account', - 'credit_message' => 'Um Ihre Gutschrift über :amount einzusehen, klicken Sie auf den untenstehenden Link.', - 'payment_type_Crypto' => 'Kryptowährung', - 'payment_type_Credit' => 'Gutschrift', - 'store_for_future_use' => 'Für zukünftige Zahlung speichern', - 'pay_with_credit' => 'Mit Kreditkarte zahlen', - 'payment_method_saving_failed' => 'Die Zahlungsart konnte nicht für zukünftige Zahlungen gespeichert werden.', - 'pay_with' => 'zahlen mit', - 'n/a' => 'n. z.', - 'by_clicking_next_you_accept_terms' => 'Wenn Sie auf "Nächster Schritt" klicken, akzeptieren Sie die Bedingungen.', - 'not_specified' => 'Nicht angegeben', - 'before_proceeding_with_payment_warning' => 'Bevor Sie mit der Zahlung fortfahren, müssen Sie folgende Felder ausfüllen', - 'after_completing_go_back_to_previous_page' => 'Gehen Sie nach dem Ausfüllen zurück zur vorherigen Seite.', - 'pay' => 'Zahlen', - 'instructions' => 'Anleitung', - 'notification_invoice_reminder1_sent_subject' => 'Die 1. Mahnung für Rechnung :invoice wurde an Kunde :client gesendet', - 'notification_invoice_reminder2_sent_subject' => 'Die 2. Mahnung für Rechnung :invoice wurde an Kunde :client gesendet', - 'notification_invoice_reminder3_sent_subject' => 'Die 3. Mahnung für Rechnung :invoice wurde an Kunde :client gesendet', - 'notification_invoice_custom_sent_subject' => 'Benutzerdefinierte Mahnung für Rechnung :invoice wurde an :client gesendet', - 'notification_invoice_reminder_endless_sent_subject' => 'Endlose Mahnung für Rechnung :invoice wurde an :client gesendet', - 'assigned_user' => 'Zugewiesener Benutzer', - 'setup_steps_notice' => 'Um mit dem nächsten Schritt fortzufahren, stellen Sie sicher, dass Sie jeden Abschnitt testen.', - 'setup_phantomjs_note' => 'Anmerkung zu Phantom JS. Mehr...', - 'minimum_payment' => 'Mindestbetrag', - 'no_action_provided' => 'Keine Maßnahme vorgesehen. Wenn Sie glauben, dass dies falsch ist, wenden Sie sich bitte an den Support.', - 'no_payable_invoices_selected' => 'Keine unbezahlten Rechnungen ausgewählt. Stellen Sie sicher, dass Sie nicht versuchen, einen Rechnungsentwurf oder eine Rechnung mit Nullsaldo zu bezahlen.', - 'required_payment_information' => 'Benötigte Zahlungsinformationen', - 'required_payment_information_more' => 'Um eine Zahlung abzuschließen, benötigen wir weitere Informationen über Sie.', - 'required_client_info_save_label' => 'Wir speichern dies, so dass Sie es beim nächsten Mal nicht mehr eingeben müssen.', - 'notification_credit_bounced' => 'Wir waren nicht in der Lage, die Gutschrift :invoice an :contact zu liefern. \n :error', - 'notification_credit_bounced_subject' => 'Gutschrift nicht auslieferbar :invoice', - 'save_payment_method_details' => 'Angaben zur Zahlungsart speichern', - 'new_card' => 'Neue Kreditkarte', - 'new_bank_account' => 'Bankverbindung hinzufügen', - 'company_limit_reached' => 'Maximal :limit Firmen pro Account.', - 'credits_applied_validation' => 'Die Gesamtsumme der Gutschriften kann nicht MEHR sein als die Gesamtsumme der Rechnungen', - 'credit_number_taken' => 'Gutschriftsnummer bereits vergeben.', - 'credit_not_found' => 'Gutschrift nicht gefunden', - 'invoices_dont_match_client' => 'Die ausgewählten Rechnungen stammen von mehr als einem Kunden', - 'duplicate_credits_submitted' => 'Doppelte Zahlung eingereicht', - 'duplicate_invoices_submitted' => 'Doppelte Rechnung', - 'credit_with_no_invoice' => 'Bei der Verwendung eines Kredits in einer Zahlung muss eine Rechnung eingestellt sein.', - 'client_id_required' => 'Kundennummer wird benötigt.', - 'expense_number_taken' => 'Bereits vergebene Ausgabennummer', - 'invoice_number_taken' => 'Diese Rechnungsnummer wurde bereits verwendet.', - 'payment_id_required' => 'Zahlungs-ID notwendig.', - 'unable_to_retrieve_payment' => 'Die angegebene Zahlung kann nicht abgerufen werden', - 'invoice_not_related_to_payment' => 'Rechnungsnr. :invoice ist nicht mit dieser Zahlung verknüpft', - 'credit_not_related_to_payment' => 'Gutschrift :credit ist nicht mit dieser Zahlung verknüpft', - 'max_refundable_invoice' => 'Es wurde versucht, mehr zu erstatten, als für die Rechnungs-ID :invoice zulässig ist, der maximal erstattungsfähige Betrag ist :amount', - 'refund_without_invoices' => 'Wenn Sie versuchen, eine Zahlung mit beigefügten Rechnungen zu erstatten, geben Sie bitte gültige Rechnungen an, die erstattet werden sollen.', - 'refund_without_credits' => 'Wenn Sie versuchen, eine Zahlung mit angefügter Gutschrift zu erstatten, geben Sie bitte eine gültige Gutschrift an, die erstattet werden sollen.', - 'max_refundable_credit' => 'Versuch, mehr zu erstatten, als für die Gutschrift zugelassen ist :credit, maximal erstattungsfähiger Betrag ist :amount', - 'project_client_do_not_match' => 'Projektkunde stimmt nicht mit Entitätskunde überein', - 'quote_number_taken' => 'Angebotsnummer bereits in Verwendung', - 'recurring_invoice_number_taken' => 'Wiederkehrende Rechnungsnummer :number bereits vergeben', - 'user_not_associated_with_account' => 'Kein mit diesem Konto verbundener Benutzer', - 'amounts_do_not_balance' => 'Die Beträge sind nicht korrekt ausgeglichen.', - 'insufficient_applied_amount_remaining' => 'Der angewandte Betrag reicht nicht aus, um die Zahlung zu decken.', - 'insufficient_credit_balance' => 'Unzureichende Gutschrift auf dem Kredit.', - 'one_or_more_invoices_paid' => 'Eine oder mehrere dieser Rechnungen wurden bereits bezahlt', - 'invoice_cannot_be_refunded' => 'Rechnung :number kann nicht erstattet werden', - 'attempted_refund_failed' => 'Erstattungsversuch :amount nur :refundable_amount für Erstattung verfügbar', - 'user_not_associated_with_this_account' => 'Dieser Benutzer kann nicht mit diesem Unternehmen verbunden werden. Vielleicht hat er bereits einen Benutzer für ein anderes Konto registriert?', - 'migration_completed' => 'Umstellung abgeschlossen', - 'migration_completed_description' => 'Die Umstellung wurde erfolgreich abgeschlossen. Bitte prüfen Sie trotzdem Ihre Daten nach dem Login.', - 'api_404' => '404 | Hier gibt es nichts zu sehen!', - 'large_account_update_parameter' => 'Kann ein großes Konto ohne den Parameter updated_at nicht laden', - 'no_backup_exists' => 'Für diese Aktivität ist keine Sicherung vorhanden', - 'company_user_not_found' => 'Firma Benutzerdatensatz nicht gefunden', - 'no_credits_found' => 'Keine Gutschriften gefunden.', - 'action_unavailable' => 'Die angeforderte Aktion :action ist nicht verfügbar.', - 'no_documents_found' => 'Keine Dokumente gefunden.', - 'no_group_settings_found' => 'Keine Gruppeneinstellungen gefunden', - 'access_denied' => 'Unzureichende Berechtigungen für den Zugriff/die Änderung dieser Ressource', - 'invoice_cannot_be_marked_paid' => 'Rechnung kann nicht als "bezahlt" gekennzeichnet werden.', - 'invoice_license_or_environment' => 'Ungültige Lizenz, oder ungültige Umgebung :environment', - 'route_not_available' => 'Pfad nicht verfügbar', - 'invalid_design_object' => 'Ungültiges benutzerdefiniertes Entwurfsobjekt', - 'quote_not_found' => 'Angebot/e nicht gefunden', - 'quote_unapprovable' => 'Dieses Angebot kann nicht genehmigt werden, da es abgelaufen ist.', - 'scheduler_has_run' => 'Aufgabenplaner lief', - 'scheduler_has_never_run' => 'Aufgabenplaner lief noch nie', - 'self_update_not_available' => 'Integrierter Updater auf diesem System nicht verfügbar.', - 'user_detached' => 'Nutzer wurden vom Unternehmen entkoppelt', - 'create_webhook_failure' => 'Webhook konnte nicht erstellt werden', - 'payment_message_extended' => 'Vielen Dank für Ihre Zahlung von :amount für die Rechnung :invoice', - 'online_payments_minimum_note' => 'Hinweis: Online-Zahlungen werden nur unterstützt, wenn der Betrag größer als 1€ oder der entsprechende Währungsbetrag ist.', - 'payment_token_not_found' => 'Zahlungstoken nicht gefunden, bitte versuchen Sie es erneut. Wenn das Problem weiterhin besteht, versuchen Sie es mit einer anderen Zahlungsmethode', - 'vendor_address1' => 'Straße Lieferant', - 'vendor_address2' => 'Lieferant Apt/Suite', - 'partially_unapplied' => 'Teilweise unangewandt', - 'select_a_gmail_user' => 'Bitte wählen Sie einen mit Gmail authentifizierten Benutzer', - 'list_long_press' => 'Liste Langes Drücken', - 'show_actions' => 'Zeige Aktionen', - 'start_multiselect' => 'Mehrfachauswahl', - 'email_sent_to_confirm_email' => 'Eine E-Mail wurde versandt um Ihre E-Mail-Adresse zu bestätigen.', - 'converted_paid_to_date' => 'Umgewandelt Bezahlt bis Datum', - 'converted_credit_balance' => 'Umgerechneter Gutschriftsbetrag', - 'converted_total' => 'Umgerechnet Total', - 'reply_to_name' => 'Name der Antwortadresse', - 'payment_status_-2' => 'Teilweise nicht angewendet', - 'color_theme' => 'Farbthema', - 'start_migration' => 'Beginne mit der Migration.', - 'recurring_cancellation_request' => 'Antrag auf Stornierung wiederkehrender Rechnungen von :contact', - 'recurring_cancellation_request_body' => ':contact vom Kunden :client bittet um Stornierung der wiederkehrenden Rechnung :invoice', - 'hello' => 'Hallo', - 'group_documents' => 'Gruppendokumente', - 'quote_approval_confirmation_label' => 'Sind Sie sicher, dass Sie diesem Angebot / Kostenvoranschlag zustimmen möchten?', - 'migration_select_company_label' => 'Wählen Sie die zu migrierenden Firmen aus', - 'force_migration' => 'Migration erzwingen', - 'require_password_with_social_login' => 'Anmeldung per Social Login notwendig', - 'stay_logged_in' => 'Eingeloggt bleiben', - 'session_about_to_expire' => 'Warnung: Ihre Sitzung läuft bald ab', - 'count_hours' => ':count Stunden', - 'count_day' => '1 Tag', - 'count_days' => ':count Tage', - 'web_session_timeout' => 'Web-Sitzungs-Timeout', - 'security_settings' => 'Sicherheitseinstellungen', - 'resend_email' => 'Bestätigungs-E-Mail erneut versenden ', - 'confirm_your_email_address' => 'Bitte bestätigen Sie Ihre E-Mail-Adresse', - 'freshbooks' => 'FreshBooks', - 'invoice2go' => 'Invoice2go', - 'invoicely' => 'Invoicely', - 'waveaccounting' => 'Wave Accounting', - 'zoho' => 'Zoho', - 'accounting' => 'Buchhaltung', - 'required_files_missing' => 'Bitte geben Sie alle CSV-Dateien an.', - 'migration_auth_label' => 'Authentifizierung fortsetzen.', - 'api_secret' => 'API-Secret', - 'migration_api_secret_notice' => 'Sie finden API_SECRET in der .env-Datei oder in Invoice Ninja v5. Wenn die Eigenschaft fehlt, lassen Sie das Feld leer.', - 'billing_coupon_notice' => 'Ihr Rabatt wird an der Kasse abgezogen.', - 'use_last_email' => 'Vorherige E-Mail benutzen', - 'activate_company' => 'Unternehmen aktivieren', - 'activate_company_help' => 'E-Mails, wiederkehrende Rechnungen und Benachrichtigungen aktivieren', - 'an_error_occurred_try_again' => 'Ein Fehler ist aufgetreten, bitte versuchen Sie es erneut.', - 'please_first_set_a_password' => 'Bitte vergeben Sie zuerst ein Passwort.', - 'changing_phone_disables_two_factor' => 'Achtung: Das Ändern Ihrer Telefonnummer wird die Zwei-Faktor-Authentifizierung deaktivieren', - 'help_translate' => 'Hilf mit beim Übersetzen', - 'please_select_a_country' => 'Bitte wählen Sie ein Land', - 'disabled_two_factor' => '2FA erfolgreich deaktiviert', - 'connected_google' => 'Konto erfolgreich verbunden.', - 'disconnected_google' => 'Konto erfolgreich getrennt.', - 'delivered' => 'zugestellt', - 'spam' => 'Spam', - 'view_docs' => 'Dokumentation ansehen.', - 'enter_phone_to_enable_two_factor' => 'Bitte gib eine Handynummer an, um die Zwei-Faktor-Authentifizierung zu aktivieren', - 'send_sms' => 'SMS senden', - 'sms_code' => 'SMS-Code', - 'connect_google' => 'Google-Konto verbinden', - 'disconnect_google' => 'Google-Konto entfernen', - 'disable_two_factor' => 'Zwei-Faktor-Authentifizierung deaktivieren', - 'invoice_task_datelog' => 'In Aufgabe erfasste Daten in Rechnungen ausweisen', - 'invoice_task_datelog_help' => 'Zeigt Datumsdetails in den Rechnungspositionen an', - 'promo_code' => 'Gutscheincode', - 'recurring_invoice_issued_to' => 'Wiederkehrende Rechnung ausgestellt an', - 'subscription' => 'Abonnement', - 'new_subscription' => 'Neues Abonnement', - 'deleted_subscription' => 'Abonnement gelöscht', - 'removed_subscription' => 'Abonnement entfernt', - 'restored_subscription' => 'Abonnement wiederhergestellt', - 'search_subscription' => 'Suchen Sie 1 Abonnement', - 'search_subscriptions' => ':count Abonnements durchsuchen', - 'subdomain_is_not_available' => 'Subdomain ist nicht verfügbar', - 'connect_gmail' => 'Mit Gmail verbinden', - 'disconnect_gmail' => 'von Gmail trennen', - 'connected_gmail' => 'Mit Gmail erfolgreich verbunden', - 'disconnected_gmail' => 'Von Gmail erfolgreich getrennt', - 'update_fail_help' => 'Änderungen an der Codebasis können das Update blockieren, Sie können diesen Befehl ausführen, um die Änderungen zu verwerfen:', - 'client_id_number' => 'Kundennummer', - 'count_minutes' => ':count Minuten', - 'password_timeout' => 'Passwort-Timeout', - 'shared_invoice_credit_counter' => 'Rechnung / Gutschrift Zähler teilen', + 'next' => 'Weiter', + 'next_step' => 'Nächster Schritt', + 'notification_credit_sent_subject' => 'Gutschrift :invoice wurde an Kunde gesendet.', + 'notification_credit_viewed_subject' => 'Gutschrift :invoice wurde von :client angesehen.', + 'notification_credit_sent' => 'Der folgende Kunde :client hat eine Gutschrift :invoice über :amount erhalten.', + 'notification_credit_viewed' => 'Der folgende Kunde :client hat die Gutschrift :credit über :amount angeschaut.', + 'reset_password_text' => 'Bitte geben Sie ihre E-Mail-Adresse an, um das Passwort zurücksetzen zu können.', + 'password_reset' => 'Passwort zurücksetzten', + 'account_login_text' => 'Willkommen! Schön Sie zu sehen.', + 'request_cancellation' => 'Storno beantragen', + 'delete_payment_method' => 'Zahlungsmethode löschen', + 'about_to_delete_payment_method' => 'Diese Zahlungsmethode wird gelöscht.', + 'action_cant_be_reversed' => 'Diese Aktion kann nicht widerrufen werden', + 'profile_updated_successfully' => 'Das Profil wurde erfolgreich aktualisiert.', + 'currency_ethiopian_birr' => 'Äthiopischer Birr', + 'client_information_text' => 'Bitte nutzen Sie eine postfähige Anschrift.', + 'status_id' => 'Rechnungsstatus', + 'email_already_register' => 'Diese E-Mail wird bereits von einem anderen Account verwendet', + 'locations' => 'Standorte', + 'freq_indefinitely' => 'Unendlich', + 'cycles_remaining' => 'Verbleibende Zyklen', + 'i_understand_delete' => 'Ich bin mir der Risiken bewusst, löschen', + 'download_files' => 'Dateien herunterladen', + 'download_timeframe' => 'Nutzen Sie diesen Link um Ihre Dateien herunterzuladen. Der Link läuft in einer Stunde ab.', + 'new_signup' => 'Neue Registrierung', + 'new_signup_text' => 'Ein neuer Benutzer wurde von :user - :email von der IP: :ip erstellt', + 'notification_payment_paid_subject' => 'Neue Zahlung von :client', + 'notification_partial_payment_paid_subject' => 'Neue Anzahlung von :client', + 'notification_payment_paid' => 'Eine Zahlung von :amount wurde von Kunde :client auf :invoice geleistet', + 'notification_partial_payment_paid' => 'Eine Teilzahlung in Höhe von :amount wurde vom Kunden :client auf :invoice geleistet', + 'notification_bot' => 'Benachrichtigungs-Bot', + 'invoice_number_placeholder' => 'Rechnung # :invoice', + 'entity_number_placeholder' => ':entity # :entity_number', + 'email_link_not_working' => 'Wenn die Schaltfläche oben nicht funktioniert, klicken Sie bitte auf den Link', + 'display_log' => 'Log anzeigen', + 'send_fail_logs_to_our_server' => 'Fehler in Echtzeit melden', + 'setup' => 'Setup', + 'quick_overview_statistics' => 'Schnellüberblick & Statistiken', + 'update_your_personal_info' => 'Aktualisieren Sie Ihre Profil', + 'name_website_logo' => 'Name, Webseite & Logo', + 'make_sure_use_full_link' => 'Es ist wichtig, den gesamten Link \'\'https://example.com\'\' einzutragen.', + 'personal_address' => 'Private Adresse', + 'enter_your_personal_address' => 'Bitte geben Sie Ihre Rechnungsadresse an', + 'enter_your_shipping_address' => 'Bitte geben Sie Ihr Lieferadresse an', + 'list_of_invoices' => 'Liste der Rechnungen', + 'with_selected' => 'Breite ausgewählt', + 'invoice_still_unpaid' => 'Diese Rechnung wurde noch nicht beglichen. Klicken um zu vervollständigen.', + 'list_of_recurring_invoices' => 'Liste der wiederkehrende Rechnungen', + 'details_of_recurring_invoice' => 'Details über wiederkehrende Rechnung', + 'cancellation' => 'Storno', + 'about_cancellation' => 'Wenn Sie die wiederkehrende Rechnung stoppen möchten, klicken Sie bitte auf , um die Stornierung zu beantragen.', + 'cancellation_warning' => 'Achtung! Sie beantragen die Stornierung dieses Dienstes. Ihr Dienst kann ohne weitere Mitteilung an Sie storniert werden.', + 'cancellation_pending' => 'Kündigung in Bearbeitung! Wir melden uns bei Ihnen...', + 'list_of_payments' => 'Liste der Zahlungen', + 'payment_details' => 'Details zu der Zahlung', + 'list_of_payment_invoices' => 'Liste der Rechnungen die von dieser Zahlung betroffenen sind', + 'list_of_payment_methods' => 'Liste der Zahlungsmethoden', + 'payment_method_details' => 'Details zu der Zahlungsmethode', + 'permanently_remove_payment_method' => 'Zahlungsmethode endgültig entfernen.', + 'warning_action_cannot_be_reversed' => 'Achtung! Diese Aktion kann nicht widerrufen werden!', + 'confirmation' => 'Bestätigung', + 'list_of_quotes' => 'Angebote', + 'waiting_for_approval' => 'Annahme ausstehend', + 'quote_still_not_approved' => 'Dieses Angebot wurde noch nicht angenommen.', + 'list_of_credits' => 'Gutschriften', + 'required_extensions' => 'Benötigte PHP-Erweiterungen', + 'php_version' => 'PHP Version', + 'writable_env_file' => 'Beschreibbare .env-Datei', + 'env_not_writable' => 'die .env-Datei ist vom aktuellen Benutzer nicht beschreibbar', + 'minumum_php_version' => 'Minimale PHP-Version', + 'satisfy_requirements' => 'Prüfen Sie, ob alle Anforderungen erfüllt sind.', + 'oops_issues' => 'Entschuldigung, das ist wohl etwas schiefgelaufen!', + 'open_in_new_tab' => 'In neuem Fenster öffnen', + 'complete_your_payment' => 'Zahlung abschließen', + 'authorize_for_future_use' => 'Zahlungsmethode für zukünftige Verwendung freigeben', + 'page' => 'Seite', + 'per_page' => 'pro Seite', + 'of' => 'von', + 'view_credit' => 'Gutschrift anzeigen', + 'to_view_entity_password' => 'Um :entity anzusehen, geben Sie bitte Ihr Passwort ein.', + 'showing_x_of' => 'Zeige :first bis :last von :total Ergebnissen', + 'no_results' => 'Kein Ergebnis gefunden.', + 'payment_failed_subject' => 'Zahlung für Kunde :client fehlgeschlagen', + 'payment_failed_body' => 'Eine Zahlung von :client schlug fehl: :message', + 'register' => 'Registrieren', + 'register_label' => 'Benutzerkonto in wenigen Sekunden erstellen', + 'password_confirmation' => 'Passwort bestätigen', + 'verification' => 'Bestätigung', + 'complete_your_bank_account_verification' => 'Ein Bankkonto muss verifiziert werden, bevor es genutzt werden kann.', + 'checkout_com' => 'Checkout.com', + 'footer_label' => 'Copyright © :year :company.', + 'credit_card_invalid' => 'Die angegebene Kreditkartennummer ist ungültig.', + 'month_invalid' => 'Der angegebene Monat ist ungültig', + 'year_invalid' => 'Das angegebene Jahr ist ungültig', + 'https_required' => 'HTTPS ist Pflicht, das Formular wird nicht funktionieren', + 'if_you_need_help' => 'Wenn Sie Hilfe benötigen, wenden Sie sich bitte an unsere', + 'update_password_on_confirm' => 'Nach dem Update des Passworts wird Ihr Account bestätigt.', + 'bank_account_not_linked' => 'Um mit einem Bankkonto zu bezahlen, müssen Sie es zunächst als Zahlungsmethode hinzufügen.', + 'application_settings_label' => 'Lassen Sie uns grundlegende Informationen über Ihr Invoice Ninja speichern!', + 'recommended_in_production' => 'Ausdrücklich für Produktivumgebungen empfohlen!', + 'enable_only_for_development' => 'Nur in Entwicklungsumgebung aktivieren', + 'test_pdf' => 'PDF testen', + 'checkout_authorize_label' => 'Checkout.com kann als Zahlungsmethode für die zukünftige Verwendung gespeichert werden, sobald Sie Ihre erste Transaktion abgeschlossen haben. Vergessen Sie nicht, die Option "Kreditkartendaten speichern" während des Zahlungsvorgangs zu aktivieren.', + 'sofort_authorize_label' => 'Das Bankkonto (SOFORT) kann als Zahlungsmethode für die zukünftige Verwendung gespeichert werden, sobald Sie Ihre erste Transaktion abgeschlossen haben. Vergessen Sie nicht, die Option "Zahlungsdetails speichern" während des Zahlungsvorgangs zu aktivieren.', + 'node_status' => 'Node-Status', + 'npm_status' => 'NPM-Status', + 'node_status_not_found' => 'Node konnte nicht gefunden werden - ist es installiert?', + 'npm_status_not_found' => 'NPM konnte nicht gefunden werden - ist es installiert?', + 'locked_invoice' => 'Diese Rechnung ist gesperrt und kann nicht bearbeitet werden.', + 'downloads' => 'Downloads', + 'resource' => 'Resourcen', + 'document_details' => 'Details zu dem Dokument', + 'hash' => 'Hash', + 'resources' => 'Ressourcen', + 'allowed_file_types' => 'Erlaubte Dateitypen:', + 'common_codes' => 'Gängige Codes und ihre Bedeutungen', + 'payment_error_code_20087' => '20087: Falsche Track-Daten (ungültiger CVV und/oder Verfallsdatum)', + 'download_selected' => 'Ausgewählte herunterladen', + 'to_pay_invoices' => 'Um Rechnungen zu bezahlen, müssen Sie', + 'add_payment_method_first' => 'Zahlungsart hinzufügen', + 'no_items_selected' => 'Keine Objekte ausgewählt.', + 'payment_due' => 'Zahlung überfallig', + 'account_balance' => 'Saldo', + 'thanks' => 'Danke', + 'minimum_required_payment' => 'Mindestbetrag für die Zahlung ist :amount', + 'under_payments_disabled' => 'Das Unternehmen unterstützt keine Unterbezahlungen.', + 'over_payments_disabled' => 'Das Unternehmen unterstützt keine Überbezahlungen.', + 'saved_at' => 'Gespeichert um :time', + 'credit_payment' => 'Gutschrift auf Rechnung :invoice_number angewendet', + 'credit_subject' => 'Neue Gutschrift :number von :account', + 'credit_message' => 'Um Ihre Gutschrift über :amount einzusehen, klicken Sie auf den untenstehenden Link.', + 'payment_type_Crypto' => 'Kryptowährung', + 'payment_type_Credit' => 'Gutschrift', + 'store_for_future_use' => 'Für zukünftige Zahlung speichern', + 'pay_with_credit' => 'Mit Kreditkarte zahlen', + 'payment_method_saving_failed' => 'Die Zahlungsart konnte nicht für zukünftige Zahlungen gespeichert werden.', + 'pay_with' => 'zahlen mit', + 'n/a' => 'n. z.', + 'by_clicking_next_you_accept_terms' => 'Wenn Sie auf "Nächster Schritt" klicken, akzeptieren Sie die Bedingungen.', + 'not_specified' => 'Nicht angegeben', + 'before_proceeding_with_payment_warning' => 'Bevor Sie mit der Zahlung fortfahren, müssen Sie folgende Felder ausfüllen', + 'after_completing_go_back_to_previous_page' => 'Gehen Sie nach dem Ausfüllen zurück zur vorherigen Seite.', + 'pay' => 'Zahlen', + 'instructions' => 'Anleitung', + 'notification_invoice_reminder1_sent_subject' => 'Die 1. Mahnung für Rechnung :invoice wurde an Kunde :client gesendet', + 'notification_invoice_reminder2_sent_subject' => 'Die 2. Mahnung für Rechnung :invoice wurde an Kunde :client gesendet', + 'notification_invoice_reminder3_sent_subject' => 'Die 3. Mahnung für Rechnung :invoice wurde an Kunde :client gesendet', + 'notification_invoice_custom_sent_subject' => 'Benutzerdefinierte Mahnung für Rechnung :invoice wurde an :client gesendet', + 'notification_invoice_reminder_endless_sent_subject' => 'Endlose Mahnung für Rechnung :invoice wurde an :client gesendet', + 'assigned_user' => 'Zugewiesener Benutzer', + 'setup_steps_notice' => 'Um mit dem nächsten Schritt fortzufahren, stellen Sie sicher, dass Sie jeden Abschnitt testen.', + 'setup_phantomjs_note' => 'Anmerkung zu Phantom JS. Mehr...', + 'minimum_payment' => 'Mindestbetrag', + 'no_action_provided' => 'Keine Maßnahme vorgesehen. Wenn Sie glauben, dass dies falsch ist, wenden Sie sich bitte an den Support.', + 'no_payable_invoices_selected' => 'Keine unbezahlten Rechnungen ausgewählt. Stellen Sie sicher, dass Sie nicht versuchen, einen Rechnungsentwurf oder eine Rechnung mit Nullsaldo zu bezahlen.', + 'required_payment_information' => 'Benötigte Zahlungsinformationen', + 'required_payment_information_more' => 'Um eine Zahlung abzuschließen, benötigen wir weitere Informationen über Sie.', + 'required_client_info_save_label' => 'Wir speichern dies, so dass Sie es beim nächsten Mal nicht mehr eingeben müssen.', + 'notification_credit_bounced' => 'Wir waren nicht in der Lage, die Gutschrift :invoice an :contact zu liefern. \n :error', + 'notification_credit_bounced_subject' => 'Gutschrift nicht auslieferbar :invoice', + 'save_payment_method_details' => 'Angaben zur Zahlungsart speichern', + 'new_card' => 'Neue Kreditkarte', + 'new_bank_account' => 'Bankverbindung hinzufügen', + 'company_limit_reached' => 'Maximal :limit Firmen pro Account.', + 'credits_applied_validation' => 'Die Gesamtsumme der Gutschriften kann nicht MEHR sein als die Gesamtsumme der Rechnungen', + 'credit_number_taken' => 'Gutschriftsnummer bereits vergeben.', + 'credit_not_found' => 'Gutschrift nicht gefunden', + 'invoices_dont_match_client' => 'Die ausgewählten Rechnungen stammen von mehr als einem Kunden', + 'duplicate_credits_submitted' => 'Doppelte Zahlung eingereicht', + 'duplicate_invoices_submitted' => 'Doppelte Rechnung', + 'credit_with_no_invoice' => 'Bei der Verwendung eines Kredits in einer Zahlung muss eine Rechnung eingestellt sein.', + 'client_id_required' => 'Kundennummer wird benötigt.', + 'expense_number_taken' => 'Bereits vergebene Ausgabennummer', + 'invoice_number_taken' => 'Diese Rechnungsnummer wurde bereits verwendet.', + 'payment_id_required' => 'Zahlungs-ID notwendig.', + 'unable_to_retrieve_payment' => 'Die angegebene Zahlung kann nicht abgerufen werden', + 'invoice_not_related_to_payment' => 'Rechnungsnr. :invoice ist nicht mit dieser Zahlung verknüpft', + 'credit_not_related_to_payment' => 'Gutschrift :credit ist nicht mit dieser Zahlung verknüpft', + 'max_refundable_invoice' => 'Es wurde versucht, mehr zu erstatten, als für die Rechnungs-ID :invoice zulässig ist, der maximal erstattungsfähige Betrag ist :amount', + 'refund_without_invoices' => 'Wenn Sie versuchen, eine Zahlung mit beigefügten Rechnungen zu erstatten, geben Sie bitte gültige Rechnungen an, die erstattet werden sollen.', + 'refund_without_credits' => 'Wenn Sie versuchen, eine Zahlung mit angefügter Gutschrift zu erstatten, geben Sie bitte eine gültige Gutschrift an, die erstattet werden sollen.', + 'max_refundable_credit' => 'Versuch, mehr zu erstatten, als für die Gutschrift zugelassen ist :credit, maximal erstattungsfähiger Betrag ist :amount', + 'project_client_do_not_match' => 'Projektkunde stimmt nicht mit Entitätskunde überein', + 'quote_number_taken' => 'Angebotsnummer bereits in Verwendung', + 'recurring_invoice_number_taken' => 'Wiederkehrende Rechnungsnummer :number bereits vergeben', + 'user_not_associated_with_account' => 'Kein mit diesem Konto verbundener Benutzer', + 'amounts_do_not_balance' => 'Die Beträge sind nicht korrekt ausgeglichen.', + 'insufficient_applied_amount_remaining' => 'Der angewandte Betrag reicht nicht aus, um die Zahlung zu decken.', + 'insufficient_credit_balance' => 'Unzureichende Gutschrift auf dem Kredit.', + 'one_or_more_invoices_paid' => 'Eine oder mehrere dieser Rechnungen wurden bereits bezahlt', + 'invoice_cannot_be_refunded' => 'Rechnung :number kann nicht erstattet werden', + 'attempted_refund_failed' => 'Erstattungsversuch :amount nur :refundable_amount für Erstattung verfügbar', + 'user_not_associated_with_this_account' => 'Dieser Benutzer kann nicht mit diesem Unternehmen verbunden werden. Vielleicht hat er bereits einen Benutzer für ein anderes Konto registriert?', + 'migration_completed' => 'Umstellung abgeschlossen', + 'migration_completed_description' => 'Die Umstellung wurde erfolgreich abgeschlossen. Bitte prüfen Sie trotzdem Ihre Daten nach dem Login.', + 'api_404' => '404 | Hier gibt es nichts zu sehen!', + 'large_account_update_parameter' => 'Kann ein großes Konto ohne den Parameter updated_at nicht laden', + 'no_backup_exists' => 'Für diese Aktivität ist keine Sicherung vorhanden', + 'company_user_not_found' => 'Firma Benutzerdatensatz nicht gefunden', + 'no_credits_found' => 'Keine Gutschriften gefunden.', + 'action_unavailable' => 'Die angeforderte Aktion :action ist nicht verfügbar.', + 'no_documents_found' => 'Keine Dokumente gefunden.', + 'no_group_settings_found' => 'Keine Gruppeneinstellungen gefunden', + 'access_denied' => 'Unzureichende Berechtigungen für den Zugriff/die Änderung dieser Ressource', + 'invoice_cannot_be_marked_paid' => 'Rechnung kann nicht als "bezahlt" gekennzeichnet werden.', + 'invoice_license_or_environment' => 'Ungültige Lizenz, oder ungültige Umgebung :environment', + 'route_not_available' => 'Pfad nicht verfügbar', + 'invalid_design_object' => 'Ungültiges benutzerdefiniertes Entwurfsobjekt', + 'quote_not_found' => 'Angebot/e nicht gefunden', + 'quote_unapprovable' => 'Dieses Angebot kann nicht genehmigt werden, da es abgelaufen ist.', + 'scheduler_has_run' => 'Aufgabenplaner lief', + 'scheduler_has_never_run' => 'Aufgabenplaner lief noch nie', + 'self_update_not_available' => 'Integrierter Updater auf diesem System nicht verfügbar.', + 'user_detached' => 'Nutzer wurden vom Unternehmen entkoppelt', + 'create_webhook_failure' => 'Webhook konnte nicht erstellt werden', + 'payment_message_extended' => 'Vielen Dank für Ihre Zahlung von :amount für die Rechnung :invoice', + 'online_payments_minimum_note' => 'Hinweis: Online-Zahlungen werden nur unterstützt, wenn der Betrag größer als 1€ oder der entsprechende Währungsbetrag ist.', + 'payment_token_not_found' => 'Zahlungstoken nicht gefunden, bitte versuchen Sie es erneut. Wenn das Problem weiterhin besteht, versuchen Sie es mit einer anderen Zahlungsmethode', + 'vendor_address1' => 'Straße Lieferant', + 'vendor_address2' => 'Lieferant Apt/Suite', + 'partially_unapplied' => 'Teilweise unangewandt', + 'select_a_gmail_user' => 'Bitte wählen Sie einen mit Gmail authentifizierten Benutzer', + 'list_long_press' => 'Liste Langes Drücken', + 'show_actions' => 'Zeige Aktionen', + 'start_multiselect' => 'Mehrfachauswahl', + 'email_sent_to_confirm_email' => 'Eine E-Mail wurde versandt um Ihre E-Mail-Adresse zu bestätigen.', + 'converted_paid_to_date' => 'Umgewandelt Bezahlt bis Datum', + 'converted_credit_balance' => 'Umgerechneter Gutschriftsbetrag', + 'converted_total' => 'Umgerechnet Total', + 'reply_to_name' => 'Name der Antwortadresse', + 'payment_status_-2' => 'Teilweise nicht angewendet', + 'color_theme' => 'Farbthema', + 'start_migration' => 'Beginne mit der Migration.', + 'recurring_cancellation_request' => 'Antrag auf Stornierung wiederkehrender Rechnungen von :contact', + 'recurring_cancellation_request_body' => ':contact vom Kunden :client bittet um Stornierung der wiederkehrenden Rechnung :invoice', + 'hello' => 'Hallo', + 'group_documents' => 'Gruppendokumente', + 'quote_approval_confirmation_label' => 'Sind Sie sicher, dass Sie diesem Angebot / Kostenvoranschlag zustimmen möchten?', + 'migration_select_company_label' => 'Wählen Sie die zu migrierenden Firmen aus', + 'force_migration' => 'Migration erzwingen', + 'require_password_with_social_login' => 'Anmeldung per Social Login notwendig', + 'stay_logged_in' => 'Eingeloggt bleiben', + 'session_about_to_expire' => 'Warnung: Ihre Sitzung läuft bald ab', + 'count_hours' => ':count Stunden', + 'count_day' => '1 Tag', + 'count_days' => ':count Tage', + 'web_session_timeout' => 'Web-Sitzungs-Timeout', + 'security_settings' => 'Sicherheitseinstellungen', + 'resend_email' => 'Bestätigungs-E-Mail erneut versenden ', + 'confirm_your_email_address' => 'Bitte bestätigen Sie Ihre E-Mail-Adresse', + 'freshbooks' => 'FreshBooks', + 'invoice2go' => 'Invoice2go', + 'invoicely' => 'Invoicely', + 'waveaccounting' => 'Wave Accounting', + 'zoho' => 'Zoho', + 'accounting' => 'Buchhaltung', + 'required_files_missing' => 'Bitte geben Sie alle CSV-Dateien an.', + 'migration_auth_label' => 'Authentifizierung fortsetzen.', + 'api_secret' => 'API-Secret', + 'migration_api_secret_notice' => 'Sie finden API_SECRET in der .env-Datei oder in Invoice Ninja v5. Wenn die Eigenschaft fehlt, lassen Sie das Feld leer.', + 'billing_coupon_notice' => 'Ihr Rabatt wird an der Kasse abgezogen.', + 'use_last_email' => 'Vorherige E-Mail benutzen', + 'activate_company' => 'Unternehmen aktivieren', + 'activate_company_help' => 'E-Mails, wiederkehrende Rechnungen und Benachrichtigungen aktivieren', + 'an_error_occurred_try_again' => 'Ein Fehler ist aufgetreten, bitte versuchen Sie es erneut.', + 'please_first_set_a_password' => 'Bitte vergeben Sie zuerst ein Passwort.', + 'changing_phone_disables_two_factor' => 'Achtung: Das Ändern Ihrer Telefonnummer wird die Zwei-Faktor-Authentifizierung deaktivieren', + 'help_translate' => 'Hilf mit beim Übersetzen', + 'please_select_a_country' => 'Bitte wählen Sie ein Land', + 'disabled_two_factor' => '2FA erfolgreich deaktiviert', + 'connected_google' => 'Konto erfolgreich verbunden.', + 'disconnected_google' => 'Konto erfolgreich getrennt.', + 'delivered' => 'zugestellt', + 'spam' => 'Spam', + 'view_docs' => 'Dokumentation ansehen.', + 'enter_phone_to_enable_two_factor' => 'Bitte gib eine Handynummer an, um die Zwei-Faktor-Authentifizierung zu aktivieren', + 'send_sms' => 'SMS senden', + 'sms_code' => 'SMS-Code', + 'connect_google' => 'Google-Konto verbinden', + 'disconnect_google' => 'Google-Konto entfernen', + 'disable_two_factor' => 'Zwei-Faktor-Authentifizierung deaktivieren', + 'invoice_task_datelog' => 'In Aufgabe erfasste Daten in Rechnungen ausweisen', + 'invoice_task_datelog_help' => 'Zeigt Datumsdetails in den Rechnungspositionen an', + 'promo_code' => 'Gutscheincode', + 'recurring_invoice_issued_to' => 'Wiederkehrende Rechnung ausgestellt an', + 'subscription' => 'Abonnement', + 'new_subscription' => 'Neues Abonnement', + 'deleted_subscription' => 'Abonnement gelöscht', + 'removed_subscription' => 'Abonnement entfernt', + 'restored_subscription' => 'Abonnement wiederhergestellt', + 'search_subscription' => 'Suchen Sie 1 Abonnement', + 'search_subscriptions' => ':count Abonnements durchsuchen', + 'subdomain_is_not_available' => 'Subdomain ist nicht verfügbar', + 'connect_gmail' => 'Mit Gmail verbinden', + 'disconnect_gmail' => 'von Gmail trennen', + 'connected_gmail' => 'Mit Gmail erfolgreich verbunden', + 'disconnected_gmail' => 'Von Gmail erfolgreich getrennt', + 'update_fail_help' => 'Änderungen an der Codebasis können das Update blockieren, Sie können diesen Befehl ausführen, um die Änderungen zu verwerfen:', + 'client_id_number' => 'Kundennummer', + 'count_minutes' => ':count Minuten', + 'password_timeout' => 'Passwort-Timeout', + 'shared_invoice_credit_counter' => 'Rechnung / Gutschrift Zähler teilen', 'activity_80' => ':user hat Abonnement :subscription erstellt', 'activity_81' => ':user hat Abonnement :subscription geändert', 'activity_82' => ':user hat Abonnement :subscription archiviert', diff --git a/lang/en/texts.php b/lang/en/texts.php index a4bc7734fc4b..b81dbea5fa53 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -3857,308 +3857,308 @@ $lang = array( 'registration_url' => 'Registration URL', 'show_product_cost' => 'Show Product Cost', 'complete' => 'Complete', - 'next' => 'Next', - 'next_step' => 'Next step', - 'notification_credit_sent_subject' => 'Credit :invoice was sent to :client', - 'notification_credit_viewed_subject' => 'Credit :invoice was viewed by :client', - 'notification_credit_sent' => 'The following client :client was emailed Credit :invoice for :amount.', - 'notification_credit_viewed' => 'The following client :client viewed Credit :credit for :amount.', - 'reset_password_text' => 'Enter your email to reset your password.', - 'password_reset' => 'Password reset', - 'account_login_text' => 'Welcome! Glad to see you.', - 'request_cancellation' => 'Request cancellation', - 'delete_payment_method' => 'Delete Payment Method', - 'about_to_delete_payment_method' => 'You are about to delete the payment method.', - 'action_cant_be_reversed' => 'Action can\'t be reversed', - 'profile_updated_successfully' => 'The profile has been updated successfully.', - 'currency_ethiopian_birr' => 'Ethiopian Birr', - 'client_information_text' => 'Use a permanent address where you can receive mail.', - 'status_id' => 'Invoice Status', - 'email_already_register' => 'This email is already linked to an account', - 'locations' => 'Locations', - 'freq_indefinitely' => 'Indefinitely', - 'cycles_remaining' => 'Cycles remaining', - 'i_understand_delete' => 'I understand, delete', - 'download_files' => 'Download Files', - 'download_timeframe' => 'Use this link to download your files, the link will expire in 1 hour.', - 'new_signup' => 'New Signup', - 'new_signup_text' => 'A new account has been created by :user - :email - from IP address: :ip', - 'notification_payment_paid_subject' => 'Payment was made by :client', - 'notification_partial_payment_paid_subject' => 'Partial payment was made by :client', - 'notification_payment_paid' => 'A payment of :amount was made by client :client towards :invoice', - 'notification_partial_payment_paid' => 'A partial payment of :amount was made by client :client towards :invoice', - 'notification_bot' => 'Notification Bot', - 'invoice_number_placeholder' => 'Invoice # :invoice', - 'entity_number_placeholder' => ':entity # :entity_number', - 'email_link_not_working' => 'If the button above isn\'t working for you, please click on the link', - 'display_log' => 'Display Log', - 'send_fail_logs_to_our_server' => 'Report errors in realtime', - 'setup' => 'Setup', - 'quick_overview_statistics' => 'Quick overview & statistics', - 'update_your_personal_info' => 'Update your personal information', - 'name_website_logo' => 'Name, website & logo', - 'make_sure_use_full_link' => 'Make sure you use full link to your site', - 'personal_address' => 'Personal address', - 'enter_your_personal_address' => 'Enter your personal address', - 'enter_your_shipping_address' => 'Enter your shipping address', - 'list_of_invoices' => 'List of invoices', - 'with_selected' => 'With selected', - 'invoice_still_unpaid' => 'This invoice is still not paid. Click the button to complete the payment', - 'list_of_recurring_invoices' => 'List of recurring invoices', - 'details_of_recurring_invoice' => 'Here are some details about recurring invoice', - 'cancellation' => 'Cancellation', - 'about_cancellation' => 'In case you want to stop the recurring invoice, please click to request the cancellation.', - 'cancellation_warning' => 'Warning! You are requesting a cancellation of this service. Your service may be cancelled with no further notification to you.', - 'cancellation_pending' => 'Cancellation pending, we\'ll be in touch!', - 'list_of_payments' => 'List of payments', - 'payment_details' => 'Details of the payment', - 'list_of_payment_invoices' => 'List of invoices affected by the payment', - 'list_of_payment_methods' => 'List of payment methods', - 'payment_method_details' => 'Details of payment method', - 'permanently_remove_payment_method' => 'Permanently remove this payment method.', - 'warning_action_cannot_be_reversed' => 'Warning! This action can not be reversed!', - 'confirmation' => 'Confirmation', - 'list_of_quotes' => 'Quotes', - 'waiting_for_approval' => 'Waiting for approval', - 'quote_still_not_approved' => 'This quote is still not approved', - 'list_of_credits' => 'Credits', - 'required_extensions' => 'Required extensions', - 'php_version' => 'PHP version', - 'writable_env_file' => 'Writable .env file', - 'env_not_writable' => '.env file is not writable by the current user.', - 'minumum_php_version' => 'Minimum PHP version', - 'satisfy_requirements' => 'Make sure all requirements are satisfied.', - 'oops_issues' => 'Oops, something does not look right!', - 'open_in_new_tab' => 'Open in new tab', - 'complete_your_payment' => 'Complete payment', - 'authorize_for_future_use' => 'Authorize payment method for future use', - 'page' => 'Page', - 'per_page' => 'Per page', - 'of' => 'Of', - 'view_credit' => 'View Credit', - 'to_view_entity_password' => 'To view the :entity you need to enter password.', - 'showing_x_of' => 'Showing :first to :last out of :total results', - 'no_results' => 'No results found.', - 'payment_failed_subject' => 'Payment failed for Client :client', - 'payment_failed_body' => 'A payment made by client :client failed with message :message', - 'register' => 'Register', - 'register_label' => 'Create your account in seconds', - 'password_confirmation' => 'Confirm your password', - 'verification' => 'Verification', - 'complete_your_bank_account_verification' => 'Before using a bank account it must be verified.', - 'checkout_com' => 'Checkout.com', - 'footer_label' => 'Copyright © :year :company.', - 'credit_card_invalid' => 'Provided credit card number is not valid.', - 'month_invalid' => 'Provided month is not valid.', - 'year_invalid' => 'Provided year is not valid.', - 'https_required' => 'HTTPS is required, form will fail', - 'if_you_need_help' => 'If you need help you can post to our', - 'update_password_on_confirm' => 'After updating password, your account will be confirmed.', - 'bank_account_not_linked' => 'To pay with a bank account, first you have to add it as payment method.', - 'application_settings_label' => 'Let\'s store basic information about your Invoice Ninja!', - 'recommended_in_production' => 'Highly recommended in production', - 'enable_only_for_development' => 'Enable only for development', - 'test_pdf' => 'Test PDF', - 'checkout_authorize_label' => 'Checkout.com can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store credit card details" during payment process.', - 'sofort_authorize_label' => 'Bank account (SOFORT) can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store payment details" during payment process.', - 'node_status' => 'Node status', - 'npm_status' => 'NPM status', - 'node_status_not_found' => 'I could not find Node anywhere. Is it installed?', - 'npm_status_not_found' => 'I could not find NPM anywhere. Is it installed?', - 'locked_invoice' => 'This invoice is locked and unable to be modified', - 'downloads' => 'Downloads', - 'resource' => 'Resource', - 'document_details' => 'Details about the document', - 'hash' => 'Hash', - 'resources' => 'Resources', - 'allowed_file_types' => 'Allowed file types:', - 'common_codes' => 'Common codes and their meanings', - 'payment_error_code_20087' => '20087: Bad Track Data (invalid CVV and/or expiry date)', - 'download_selected' => 'Download selected', - 'to_pay_invoices' => 'To pay invoices, you have to', - 'add_payment_method_first' => 'add payment method', - 'no_items_selected' => 'No items selected.', - 'payment_due' => 'Payment due', - 'account_balance' => 'Account Balance', - 'thanks' => 'Thanks', - 'minimum_required_payment' => 'Minimum required payment is :amount', - 'under_payments_disabled' => 'Company doesn\'t support underpayments.', - 'over_payments_disabled' => 'Company doesn\'t support overpayments.', - 'saved_at' => 'Saved at :time', - 'credit_payment' => 'Credit applied to Invoice :invoice_number', - 'credit_subject' => 'New credit :number from :account', - 'credit_message' => 'To view your credit for :amount, click the link below.', - 'payment_type_Crypto' => 'Cryptocurrency', - 'payment_type_Credit' => 'Credit', - 'store_for_future_use' => 'Store for future use', - 'pay_with_credit' => 'Pay with credit', - 'payment_method_saving_failed' => 'Payment method can\'t be saved for future use.', - 'pay_with' => 'Pay with', - 'n/a' => 'N/A', - 'by_clicking_next_you_accept_terms' => 'By clicking "Next step" you accept terms.', - 'not_specified' => 'Not specified', - 'before_proceeding_with_payment_warning' => 'Before proceeding with payment, you have to fill following fields', - 'after_completing_go_back_to_previous_page' => 'After completing, go back to previous page.', - 'pay' => 'Pay', - 'instructions' => 'Instructions', - 'notification_invoice_reminder1_sent_subject' => 'Reminder 1 for Invoice :invoice was sent to :client', - 'notification_invoice_reminder2_sent_subject' => 'Reminder 2 for Invoice :invoice was sent to :client', - 'notification_invoice_reminder3_sent_subject' => 'Reminder 3 for Invoice :invoice was sent to :client', - 'notification_invoice_custom_sent_subject' => 'Custom reminder for Invoice :invoice was sent to :client', - 'notification_invoice_reminder_endless_sent_subject' => 'Endless reminder for Invoice :invoice was sent to :client', - 'assigned_user' => 'Assigned User', - 'setup_steps_notice' => 'To proceed to next step, make sure you test each section.', - 'setup_phantomjs_note' => 'Note about Phantom JS. Read more.', - 'minimum_payment' => 'Minimum Payment', - 'no_action_provided' => 'No action provided. If you believe this is wrong, please contact the support.', - 'no_payable_invoices_selected' => 'No payable invoices selected. Make sure you are not trying to pay draft invoice or invoice with zero balance due.', - 'required_payment_information' => 'Required payment details', - 'required_payment_information_more' => 'To complete a payment we need more details about you.', - 'required_client_info_save_label' => 'We will save this, so you don\'t have to enter it next time.', - 'notification_credit_bounced' => 'We were unable to deliver Credit :invoice to :contact. \n :error', - 'notification_credit_bounced_subject' => 'Unable to deliver Credit :invoice', - 'save_payment_method_details' => 'Save payment method details', - 'new_card' => 'New card', - 'new_bank_account' => 'New bank account', - 'company_limit_reached' => 'Limit of :limit companies per account.', - 'credits_applied_validation' => 'Total credits applied cannot be MORE than total of invoices', - 'credit_number_taken' => 'Credit number already taken', - 'credit_not_found' => 'Credit not found', - 'invoices_dont_match_client' => 'Selected invoices are not from a single client', - 'duplicate_credits_submitted' => 'Duplicate credits submitted.', - 'duplicate_invoices_submitted' => 'Duplicate invoices submitted.', - 'credit_with_no_invoice' => 'You must have an invoice set when using a credit in a payment', - 'client_id_required' => 'Client id is required', - 'expense_number_taken' => 'Expense number already taken', - 'invoice_number_taken' => 'Invoice number already taken', - 'payment_id_required' => 'Payment `id` required.', - 'unable_to_retrieve_payment' => 'Unable to retrieve specified payment', - 'invoice_not_related_to_payment' => 'Invoice id :invoice is not related to this payment', - 'credit_not_related_to_payment' => 'Credit id :credit is not related to this payment', - 'max_refundable_invoice' => 'Attempting to refund more than allowed for invoice id :invoice, maximum refundable amount is :amount', - 'refund_without_invoices' => 'Attempting to refund a payment with invoices attached, please specify valid invoice/s to be refunded.', - 'refund_without_credits' => 'Attempting to refund a payment with credits attached, please specify valid credits/s to be refunded.', - 'max_refundable_credit' => 'Attempting to refund more than allowed for credit :credit, maximum refundable amount is :amount', - 'project_client_do_not_match' => 'Project client does not match entity client', - 'quote_number_taken' => 'Quote number already taken', - 'recurring_invoice_number_taken' => 'Recurring Invoice number :number already taken', - 'user_not_associated_with_account' => 'User not associated with this account', - 'amounts_do_not_balance' => 'Amounts do not balance correctly.', - 'insufficient_applied_amount_remaining' => 'Insufficient applied amount remaining to cover payment.', - 'insufficient_credit_balance' => 'Insufficient balance on credit.', - 'one_or_more_invoices_paid' => 'One or more of these invoices have been paid', - 'invoice_cannot_be_refunded' => 'Invoice id :number cannot be refunded', - 'attempted_refund_failed' => 'Attempting to refund :amount only :refundable_amount available for refund', - 'user_not_associated_with_this_account' => 'This user is unable to be attached to this company. Perhaps they have already registered a user on another account?', - 'migration_completed' => 'Migration completed', - 'migration_completed_description' => 'Your migration has completed, please review your data after logging in.', - 'api_404' => '404 | Nothing to see here!', - 'large_account_update_parameter' => 'Cannot load a large account without a updated_at parameter', - 'no_backup_exists' => 'No backup exists for this activity', - 'company_user_not_found' => 'Company User record not found', - 'no_credits_found' => 'No credits found.', - 'action_unavailable' => 'The requested action :action is not available.', - 'no_documents_found' => 'No Documents Found', - 'no_group_settings_found' => 'No group settings found', - 'access_denied' => 'Insufficient privileges to access/modify this resource', - 'invoice_cannot_be_marked_paid' => 'Invoice cannot be marked as paid', - 'invoice_license_or_environment' => 'Invalid license, or invalid environment :environment', - 'route_not_available' => 'Route not available', - 'invalid_design_object' => 'Invalid custom design object', - 'quote_not_found' => 'Quote/s not found', - 'quote_unapprovable' => 'Unable to approve this quote as it has expired.', - 'scheduler_has_run' => 'Scheduler has run', - 'scheduler_has_never_run' => 'Scheduler has never run', - 'self_update_not_available' => 'Self update not available on this system.', - 'user_detached' => 'User detached from company', - 'create_webhook_failure' => 'Failed to create Webhook', - 'payment_message_extended' => 'Thank you for your payment of :amount for :invoice', - 'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is bigger than $1 or currency equivalent.', - 'payment_token_not_found' => 'Payment token not found, please try again. If an issue still persist, try with another payment method', - 'vendor_address1' => 'Vendor Street', - 'vendor_address2' => 'Vendor Apt/Suite', - 'partially_unapplied' => 'Partially Unapplied', - 'select_a_gmail_user' => 'Please select a user authenticated with Gmail', - 'list_long_press' => 'List Long Press', - 'show_actions' => 'Show Actions', - 'start_multiselect' => 'Start Multiselect', - 'email_sent_to_confirm_email' => 'An email has been sent to confirm the email address', - 'converted_paid_to_date' => 'Converted Paid to Date', - 'converted_credit_balance' => 'Converted Credit Balance', - 'converted_total' => 'Converted Total', - 'reply_to_name' => 'Reply-To Name', - 'payment_status_-2' => 'Partially Unapplied', - 'color_theme' => 'Color Theme', - 'start_migration' => 'Start Migration', - 'recurring_cancellation_request' => 'Request for recurring invoice cancellation from :contact', - 'recurring_cancellation_request_body' => ':contact from Client :client requested to cancel Recurring Invoice :invoice', - 'hello' => 'Hello', - 'group_documents' => 'Group documents', - 'quote_approval_confirmation_label' => 'Are you sure you want to approve this quote?', - 'migration_select_company_label' => 'Select companies to migrate', - 'force_migration' => 'Force migration', - 'require_password_with_social_login' => 'Require Password with Social Login', - 'stay_logged_in' => 'Stay Logged In', - 'session_about_to_expire' => 'Warning: Your session is about to expire', - 'count_hours' => ':count Hours', - 'count_day' => '1 Day', - 'count_days' => ':count Days', - 'web_session_timeout' => 'Web Session Timeout', - 'security_settings' => 'Security Settings', - 'resend_email' => 'Resend Email', - 'confirm_your_email_address' => 'Please confirm your email address', - 'freshbooks' => 'FreshBooks', - 'invoice2go' => 'Invoice2go', - 'invoicely' => 'Invoicely', - 'waveaccounting' => 'Wave Accounting', - 'zoho' => 'Zoho', - 'accounting' => 'Accounting', - 'required_files_missing' => 'Please provide all CSVs.', - 'migration_auth_label' => 'Let\'s continue by authenticating.', - 'api_secret' => 'API secret', - 'migration_api_secret_notice' => 'You can find API_SECRET in the .env file or Invoice Ninja v5. If property is missing, leave field blank.', - 'billing_coupon_notice' => 'Your discount will be applied on the checkout.', - 'use_last_email' => 'Use last email', - 'activate_company' => 'Activate Company', - 'activate_company_help' => 'Enable emails, recurring invoices and notifications', - 'an_error_occurred_try_again' => 'An error occurred, please try again', - 'please_first_set_a_password' => 'Please first set a password', - 'changing_phone_disables_two_factor' => 'Warning: Changing your phone number will disable 2FA', - 'help_translate' => 'Help Translate', - 'please_select_a_country' => 'Please select a country', - 'disabled_two_factor' => 'Successfully disabled 2FA', - 'connected_google' => 'Successfully connected account', - 'disconnected_google' => 'Successfully disconnected account', - 'delivered' => 'Delivered', - 'spam' => 'Spam', - 'view_docs' => 'View Docs', - 'enter_phone_to_enable_two_factor' => 'Please provide a mobile phone number to enable two factor authentication', - 'send_sms' => 'Send SMS', - 'sms_code' => 'SMS Code', - 'connect_google' => 'Connect Google', - 'disconnect_google' => 'Disconnect Google', - 'disable_two_factor' => 'Disable Two Factor', - 'invoice_task_datelog' => 'Invoice Task Datelog', - 'invoice_task_datelog_help' => 'Add date details to the invoice line items', - 'promo_code' => 'Promo code', - 'recurring_invoice_issued_to' => 'Recurring invoice issued to', - 'subscription' => 'Subscription', - 'new_subscription' => 'New Subscription', - 'deleted_subscription' => 'Successfully deleted subscription', - 'removed_subscription' => 'Successfully removed subscription', - 'restored_subscription' => 'Successfully restored subscription', - 'search_subscription' => 'Search 1 Subscription', - 'search_subscriptions' => 'Search :count Subscriptions', - 'subdomain_is_not_available' => 'Subdomain is not available', - 'connect_gmail' => 'Connect Gmail', - 'disconnect_gmail' => 'Disconnect Gmail', - 'connected_gmail' => 'Successfully connected Gmail', - 'disconnected_gmail' => 'Successfully disconnected Gmail', - 'update_fail_help' => 'Changes to the codebase may be blocking the update, you can run this command to discard the changes:', - 'client_id_number' => 'Client ID Number', - 'count_minutes' => ':count Minutes', - 'password_timeout' => 'Password Timeout', - 'shared_invoice_credit_counter' => 'Share Invoice/Credit Counter', + 'next' => 'Next', + 'next_step' => 'Next step', + 'notification_credit_sent_subject' => 'Credit :invoice was sent to :client', + 'notification_credit_viewed_subject' => 'Credit :invoice was viewed by :client', + 'notification_credit_sent' => 'The following client :client was emailed Credit :invoice for :amount.', + 'notification_credit_viewed' => 'The following client :client viewed Credit :credit for :amount.', + 'reset_password_text' => 'Enter your email to reset your password.', + 'password_reset' => 'Password reset', + 'account_login_text' => 'Welcome! Glad to see you.', + 'request_cancellation' => 'Request cancellation', + 'delete_payment_method' => 'Delete Payment Method', + 'about_to_delete_payment_method' => 'You are about to delete the payment method.', + 'action_cant_be_reversed' => 'Action can\'t be reversed', + 'profile_updated_successfully' => 'The profile has been updated successfully.', + 'currency_ethiopian_birr' => 'Ethiopian Birr', + 'client_information_text' => 'Use a permanent address where you can receive mail.', + 'status_id' => 'Invoice Status', + 'email_already_register' => 'This email is already linked to an account', + 'locations' => 'Locations', + 'freq_indefinitely' => 'Indefinitely', + 'cycles_remaining' => 'Cycles remaining', + 'i_understand_delete' => 'I understand, delete', + 'download_files' => 'Download Files', + 'download_timeframe' => 'Use this link to download your files, the link will expire in 1 hour.', + 'new_signup' => 'New Signup', + 'new_signup_text' => 'A new account has been created by :user - :email - from IP address: :ip', + 'notification_payment_paid_subject' => 'Payment was made by :client', + 'notification_partial_payment_paid_subject' => 'Partial payment was made by :client', + 'notification_payment_paid' => 'A payment of :amount was made by client :client towards :invoice', + 'notification_partial_payment_paid' => 'A partial payment of :amount was made by client :client towards :invoice', + 'notification_bot' => 'Notification Bot', + 'invoice_number_placeholder' => 'Invoice # :invoice', + 'entity_number_placeholder' => ':entity # :entity_number', + 'email_link_not_working' => 'If the button above isn\'t working for you, please click on the link', + 'display_log' => 'Display Log', + 'send_fail_logs_to_our_server' => 'Report errors in realtime', + 'setup' => 'Setup', + 'quick_overview_statistics' => 'Quick overview & statistics', + 'update_your_personal_info' => 'Update your personal information', + 'name_website_logo' => 'Name, website & logo', + 'make_sure_use_full_link' => 'Make sure you use full link to your site', + 'personal_address' => 'Personal address', + 'enter_your_personal_address' => 'Enter your personal address', + 'enter_your_shipping_address' => 'Enter your shipping address', + 'list_of_invoices' => 'List of invoices', + 'with_selected' => 'With selected', + 'invoice_still_unpaid' => 'This invoice is still not paid. Click the button to complete the payment', + 'list_of_recurring_invoices' => 'List of recurring invoices', + 'details_of_recurring_invoice' => 'Here are some details about recurring invoice', + 'cancellation' => 'Cancellation', + 'about_cancellation' => 'In case you want to stop the recurring invoice, please click to request the cancellation.', + 'cancellation_warning' => 'Warning! You are requesting a cancellation of this service. Your service may be cancelled with no further notification to you.', + 'cancellation_pending' => 'Cancellation pending, we\'ll be in touch!', + 'list_of_payments' => 'List of payments', + 'payment_details' => 'Details of the payment', + 'list_of_payment_invoices' => 'List of invoices affected by the payment', + 'list_of_payment_methods' => 'List of payment methods', + 'payment_method_details' => 'Details of payment method', + 'permanently_remove_payment_method' => 'Permanently remove this payment method.', + 'warning_action_cannot_be_reversed' => 'Warning! This action can not be reversed!', + 'confirmation' => 'Confirmation', + 'list_of_quotes' => 'Quotes', + 'waiting_for_approval' => 'Waiting for approval', + 'quote_still_not_approved' => 'This quote is still not approved', + 'list_of_credits' => 'Credits', + 'required_extensions' => 'Required extensions', + 'php_version' => 'PHP version', + 'writable_env_file' => 'Writable .env file', + 'env_not_writable' => '.env file is not writable by the current user.', + 'minumum_php_version' => 'Minimum PHP version', + 'satisfy_requirements' => 'Make sure all requirements are satisfied.', + 'oops_issues' => 'Oops, something does not look right!', + 'open_in_new_tab' => 'Open in new tab', + 'complete_your_payment' => 'Complete payment', + 'authorize_for_future_use' => 'Authorize payment method for future use', + 'page' => 'Page', + 'per_page' => 'Per page', + 'of' => 'Of', + 'view_credit' => 'View Credit', + 'to_view_entity_password' => 'To view the :entity you need to enter password.', + 'showing_x_of' => 'Showing :first to :last out of :total results', + 'no_results' => 'No results found.', + 'payment_failed_subject' => 'Payment failed for Client :client', + 'payment_failed_body' => 'A payment made by client :client failed with message :message', + 'register' => 'Register', + 'register_label' => 'Create your account in seconds', + 'password_confirmation' => 'Confirm your password', + 'verification' => 'Verification', + 'complete_your_bank_account_verification' => 'Before using a bank account it must be verified.', + 'checkout_com' => 'Checkout.com', + 'footer_label' => 'Copyright © :year :company.', + 'credit_card_invalid' => 'Provided credit card number is not valid.', + 'month_invalid' => 'Provided month is not valid.', + 'year_invalid' => 'Provided year is not valid.', + 'https_required' => 'HTTPS is required, form will fail', + 'if_you_need_help' => 'If you need help you can post to our', + 'update_password_on_confirm' => 'After updating password, your account will be confirmed.', + 'bank_account_not_linked' => 'To pay with a bank account, first you have to add it as payment method.', + 'application_settings_label' => 'Let\'s store basic information about your Invoice Ninja!', + 'recommended_in_production' => 'Highly recommended in production', + 'enable_only_for_development' => 'Enable only for development', + 'test_pdf' => 'Test PDF', + 'checkout_authorize_label' => 'Checkout.com can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store credit card details" during payment process.', + 'sofort_authorize_label' => 'Bank account (SOFORT) can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store payment details" during payment process.', + 'node_status' => 'Node status', + 'npm_status' => 'NPM status', + 'node_status_not_found' => 'I could not find Node anywhere. Is it installed?', + 'npm_status_not_found' => 'I could not find NPM anywhere. Is it installed?', + 'locked_invoice' => 'This invoice is locked and unable to be modified', + 'downloads' => 'Downloads', + 'resource' => 'Resource', + 'document_details' => 'Details about the document', + 'hash' => 'Hash', + 'resources' => 'Resources', + 'allowed_file_types' => 'Allowed file types:', + 'common_codes' => 'Common codes and their meanings', + 'payment_error_code_20087' => '20087: Bad Track Data (invalid CVV and/or expiry date)', + 'download_selected' => 'Download selected', + 'to_pay_invoices' => 'To pay invoices, you have to', + 'add_payment_method_first' => 'add payment method', + 'no_items_selected' => 'No items selected.', + 'payment_due' => 'Payment due', + 'account_balance' => 'Account Balance', + 'thanks' => 'Thanks', + 'minimum_required_payment' => 'Minimum required payment is :amount', + 'under_payments_disabled' => 'Company doesn\'t support underpayments.', + 'over_payments_disabled' => 'Company doesn\'t support overpayments.', + 'saved_at' => 'Saved at :time', + 'credit_payment' => 'Credit applied to Invoice :invoice_number', + 'credit_subject' => 'New credit :number from :account', + 'credit_message' => 'To view your credit for :amount, click the link below.', + 'payment_type_Crypto' => 'Cryptocurrency', + 'payment_type_Credit' => 'Credit', + 'store_for_future_use' => 'Store for future use', + 'pay_with_credit' => 'Pay with credit', + 'payment_method_saving_failed' => 'Payment method can\'t be saved for future use.', + 'pay_with' => 'Pay with', + 'n/a' => 'N/A', + 'by_clicking_next_you_accept_terms' => 'By clicking "Next step" you accept terms.', + 'not_specified' => 'Not specified', + 'before_proceeding_with_payment_warning' => 'Before proceeding with payment, you have to fill following fields', + 'after_completing_go_back_to_previous_page' => 'After completing, go back to previous page.', + 'pay' => 'Pay', + 'instructions' => 'Instructions', + 'notification_invoice_reminder1_sent_subject' => 'Reminder 1 for Invoice :invoice was sent to :client', + 'notification_invoice_reminder2_sent_subject' => 'Reminder 2 for Invoice :invoice was sent to :client', + 'notification_invoice_reminder3_sent_subject' => 'Reminder 3 for Invoice :invoice was sent to :client', + 'notification_invoice_custom_sent_subject' => 'Custom reminder for Invoice :invoice was sent to :client', + 'notification_invoice_reminder_endless_sent_subject' => 'Endless reminder for Invoice :invoice was sent to :client', + 'assigned_user' => 'Assigned User', + 'setup_steps_notice' => 'To proceed to next step, make sure you test each section.', + 'setup_phantomjs_note' => 'Note about Phantom JS. Read more.', + 'minimum_payment' => 'Minimum Payment', + 'no_action_provided' => 'No action provided. If you believe this is wrong, please contact the support.', + 'no_payable_invoices_selected' => 'No payable invoices selected. Make sure you are not trying to pay draft invoice or invoice with zero balance due.', + 'required_payment_information' => 'Required payment details', + 'required_payment_information_more' => 'To complete a payment we need more details about you.', + 'required_client_info_save_label' => 'We will save this, so you don\'t have to enter it next time.', + 'notification_credit_bounced' => 'We were unable to deliver Credit :invoice to :contact. \n :error', + 'notification_credit_bounced_subject' => 'Unable to deliver Credit :invoice', + 'save_payment_method_details' => 'Save payment method details', + 'new_card' => 'New card', + 'new_bank_account' => 'New bank account', + 'company_limit_reached' => 'Limit of :limit companies per account.', + 'credits_applied_validation' => 'Total credits applied cannot be MORE than total of invoices', + 'credit_number_taken' => 'Credit number already taken', + 'credit_not_found' => 'Credit not found', + 'invoices_dont_match_client' => 'Selected invoices are not from a single client', + 'duplicate_credits_submitted' => 'Duplicate credits submitted.', + 'duplicate_invoices_submitted' => 'Duplicate invoices submitted.', + 'credit_with_no_invoice' => 'You must have an invoice set when using a credit in a payment', + 'client_id_required' => 'Client id is required', + 'expense_number_taken' => 'Expense number already taken', + 'invoice_number_taken' => 'Invoice number already taken', + 'payment_id_required' => 'Payment `id` required.', + 'unable_to_retrieve_payment' => 'Unable to retrieve specified payment', + 'invoice_not_related_to_payment' => 'Invoice id :invoice is not related to this payment', + 'credit_not_related_to_payment' => 'Credit id :credit is not related to this payment', + 'max_refundable_invoice' => 'Attempting to refund more than allowed for invoice id :invoice, maximum refundable amount is :amount', + 'refund_without_invoices' => 'Attempting to refund a payment with invoices attached, please specify valid invoice/s to be refunded.', + 'refund_without_credits' => 'Attempting to refund a payment with credits attached, please specify valid credits/s to be refunded.', + 'max_refundable_credit' => 'Attempting to refund more than allowed for credit :credit, maximum refundable amount is :amount', + 'project_client_do_not_match' => 'Project client does not match entity client', + 'quote_number_taken' => 'Quote number already taken', + 'recurring_invoice_number_taken' => 'Recurring Invoice number :number already taken', + 'user_not_associated_with_account' => 'User not associated with this account', + 'amounts_do_not_balance' => 'Amounts do not balance correctly.', + 'insufficient_applied_amount_remaining' => 'Insufficient applied amount remaining to cover payment.', + 'insufficient_credit_balance' => 'Insufficient balance on credit.', + 'one_or_more_invoices_paid' => 'One or more of these invoices have been paid', + 'invoice_cannot_be_refunded' => 'Invoice id :number cannot be refunded', + 'attempted_refund_failed' => 'Attempting to refund :amount only :refundable_amount available for refund', + 'user_not_associated_with_this_account' => 'This user is unable to be attached to this company. Perhaps they have already registered a user on another account?', + 'migration_completed' => 'Migration completed', + 'migration_completed_description' => 'Your migration has completed, please review your data after logging in.', + 'api_404' => '404 | Nothing to see here!', + 'large_account_update_parameter' => 'Cannot load a large account without a updated_at parameter', + 'no_backup_exists' => 'No backup exists for this activity', + 'company_user_not_found' => 'Company User record not found', + 'no_credits_found' => 'No credits found.', + 'action_unavailable' => 'The requested action :action is not available.', + 'no_documents_found' => 'No Documents Found', + 'no_group_settings_found' => 'No group settings found', + 'access_denied' => 'Insufficient privileges to access/modify this resource', + 'invoice_cannot_be_marked_paid' => 'Invoice cannot be marked as paid', + 'invoice_license_or_environment' => 'Invalid license, or invalid environment :environment', + 'route_not_available' => 'Route not available', + 'invalid_design_object' => 'Invalid custom design object', + 'quote_not_found' => 'Quote/s not found', + 'quote_unapprovable' => 'Unable to approve this quote as it has expired.', + 'scheduler_has_run' => 'Scheduler has run', + 'scheduler_has_never_run' => 'Scheduler has never run', + 'self_update_not_available' => 'Self update not available on this system.', + 'user_detached' => 'User detached from company', + 'create_webhook_failure' => 'Failed to create Webhook', + 'payment_message_extended' => 'Thank you for your payment of :amount for :invoice', + 'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is bigger than $1 or currency equivalent.', + 'payment_token_not_found' => 'Payment token not found, please try again. If an issue still persist, try with another payment method', + 'vendor_address1' => 'Vendor Street', + 'vendor_address2' => 'Vendor Apt/Suite', + 'partially_unapplied' => 'Partially Unapplied', + 'select_a_gmail_user' => 'Please select a user authenticated with Gmail', + 'list_long_press' => 'List Long Press', + 'show_actions' => 'Show Actions', + 'start_multiselect' => 'Start Multiselect', + 'email_sent_to_confirm_email' => 'An email has been sent to confirm the email address', + 'converted_paid_to_date' => 'Converted Paid to Date', + 'converted_credit_balance' => 'Converted Credit Balance', + 'converted_total' => 'Converted Total', + 'reply_to_name' => 'Reply-To Name', + 'payment_status_-2' => 'Partially Unapplied', + 'color_theme' => 'Color Theme', + 'start_migration' => 'Start Migration', + 'recurring_cancellation_request' => 'Request for recurring invoice cancellation from :contact', + 'recurring_cancellation_request_body' => ':contact from Client :client requested to cancel Recurring Invoice :invoice', + 'hello' => 'Hello', + 'group_documents' => 'Group documents', + 'quote_approval_confirmation_label' => 'Are you sure you want to approve this quote?', + 'migration_select_company_label' => 'Select companies to migrate', + 'force_migration' => 'Force migration', + 'require_password_with_social_login' => 'Require Password with Social Login', + 'stay_logged_in' => 'Stay Logged In', + 'session_about_to_expire' => 'Warning: Your session is about to expire', + 'count_hours' => ':count Hours', + 'count_day' => '1 Day', + 'count_days' => ':count Days', + 'web_session_timeout' => 'Web Session Timeout', + 'security_settings' => 'Security Settings', + 'resend_email' => 'Resend Email', + 'confirm_your_email_address' => 'Please confirm your email address', + 'freshbooks' => 'FreshBooks', + 'invoice2go' => 'Invoice2go', + 'invoicely' => 'Invoicely', + 'waveaccounting' => 'Wave Accounting', + 'zoho' => 'Zoho', + 'accounting' => 'Accounting', + 'required_files_missing' => 'Please provide all CSVs.', + 'migration_auth_label' => 'Let\'s continue by authenticating.', + 'api_secret' => 'API secret', + 'migration_api_secret_notice' => 'You can find API_SECRET in the .env file or Invoice Ninja v5. If property is missing, leave field blank.', + 'billing_coupon_notice' => 'Your discount will be applied on the checkout.', + 'use_last_email' => 'Use last email', + 'activate_company' => 'Activate Company', + 'activate_company_help' => 'Enable emails, recurring invoices and notifications', + 'an_error_occurred_try_again' => 'An error occurred, please try again', + 'please_first_set_a_password' => 'Please first set a password', + 'changing_phone_disables_two_factor' => 'Warning: Changing your phone number will disable 2FA', + 'help_translate' => 'Help Translate', + 'please_select_a_country' => 'Please select a country', + 'disabled_two_factor' => 'Successfully disabled 2FA', + 'connected_google' => 'Successfully connected account', + 'disconnected_google' => 'Successfully disconnected account', + 'delivered' => 'Delivered', + 'spam' => 'Spam', + 'view_docs' => 'View Docs', + 'enter_phone_to_enable_two_factor' => 'Please provide a mobile phone number to enable two factor authentication', + 'send_sms' => 'Send SMS', + 'sms_code' => 'SMS Code', + 'connect_google' => 'Connect Google', + 'disconnect_google' => 'Disconnect Google', + 'disable_two_factor' => 'Disable Two Factor', + 'invoice_task_datelog' => 'Invoice Task Datelog', + 'invoice_task_datelog_help' => 'Add date details to the invoice line items', + 'promo_code' => 'Promo code', + 'recurring_invoice_issued_to' => 'Recurring invoice issued to', + 'subscription' => 'Subscription', + 'new_subscription' => 'New Subscription', + 'deleted_subscription' => 'Successfully deleted subscription', + 'removed_subscription' => 'Successfully removed subscription', + 'restored_subscription' => 'Successfully restored subscription', + 'search_subscription' => 'Search 1 Subscription', + 'search_subscriptions' => 'Search :count Subscriptions', + 'subdomain_is_not_available' => 'Subdomain is not available', + 'connect_gmail' => 'Connect Gmail', + 'disconnect_gmail' => 'Disconnect Gmail', + 'connected_gmail' => 'Successfully connected Gmail', + 'disconnected_gmail' => 'Successfully disconnected Gmail', + 'update_fail_help' => 'Changes to the codebase may be blocking the update, you can run this command to discard the changes:', + 'client_id_number' => 'Client ID Number', + 'count_minutes' => ':count Minutes', + 'password_timeout' => 'Password Timeout', + 'shared_invoice_credit_counter' => 'Share Invoice/Credit Counter', 'activity_80' => ':user created subscription :subscription', 'activity_81' => ':user updated subscription :subscription', 'activity_82' => ':user archived subscription :subscription', @@ -5221,6 +5221,27 @@ $lang = array( 'charges' => 'Charges', 'email_report' => 'Email Report', 'payment_type_Pay Later' => 'Pay Later', + 'nordigen_handler_subtitle' => 'will gain access for your selected bank account. After selecting your institution you are redirected to theire front-page to complete the request with your account credentials.', + 'nordigen_handler_error_heading_unknown' => 'An Error has occured', + 'nordigen_handler_error_contents_unknown' => 'An unknown Error has occured! Reason:', + 'nordigen_handler_error_heading_token_invalid' => 'Invalid Token', + 'nordigen_handler_error_contents_token_invalid' => 'The provided token was invalid. Please restart the flow, with a valid one_time_token. Contact support for help, if this issue persists.', + 'nordigen_handler_error_heading_account_config_invalid' => 'Missing Credentials', + 'nordigen_handler_error_contents_account_config_invalid' => 'The provided credentials for nordigen are eighter missing or invalid. Contact support for help, if this issue persists.', + 'nordigen_handler_error_heading_not_available' => 'Not Available', + 'nordigen_handler_error_contents_not_available' => 'This flow is not available for your account. Considder upgrading to enterprise version. Contact support for help, if this issue persists.', + 'nordigen_handler_error_heading_institution_invalid' => 'Invalid Institution', + 'nordigen_handler_error_contents_institution_invalid' => 'The provided institution-id is invalid or no longer valid. You can go to the bank selection page by clicking the button below or cancel the flow by clicking on the \'X\' above.', + 'nordigen_handler_error_heading_ref_invalid' => 'Invalid Reference', + 'nordigen_handler_error_contents_ref_invalid' => 'Nordigen did not provide a valid reference. Please run flow again and contact support, if this issue persists.', + 'nordigen_handler_error_heading_not_found' => 'Invalid Requisition', + 'nordigen_handler_error_contents_not_found' => 'Nordigen did not provide a valid reference. Please run flow again and contact support, if this issue persists.', + 'nordigen_handler_error_heading_requisition_invalid_status' => 'Not Ready', + 'nordigen_handler_error_contents_requisition_invalid_status' => 'You may called this site to early. Please finish authorization and refresh this page. Contact support for help, if this issue persists.', + 'nordigen_handler_error_heading_requisition_no_accounts' => 'No Accounts selected', + 'nordigen_handler_error_contents_requisition_no_accounts' => 'The service has not returned any valid accounts. Considder restarting the flow.', + 'nordigen_handler_restart' => 'Restart flow.', + 'nordigen_handler_return' => 'Return to application.', ); return $lang; diff --git a/resources/views/bank/nordigen/handler.blade.php b/resources/views/bank/nordigen/handler.blade.php index f32b380d7854..ed281ae5a32f 100644 --- a/resources/views/bank/nordigen/handler.blade.php +++ b/resources/views/bank/nordigen/handler.blade.php @@ -25,7 +25,7 @@ // Redirect URL that is being used when modal is being closed. redirectUrl: "{{ $redirectUrl }}" || new URL("", window.location.origin).href, // Text that will be displayed on the left side under the logo. Text is limited to 100 characters, and rest will be truncated. @turbo124 replace with a translated version like ctrans() - text: "{{ ($account ?? false) && !$account->isPaid() ? 'Invoice Ninja' : (isset($company) && !is_null($company) ? $company->name : 'Invoice Ninja') }} will gain access for your selected bank account. After selecting your institution you are redirected to theire front-page to complete the request with your account credentials.", + text: "{{ ($account ?? false) && !$account->isPaid() ? 'Invoice Ninja' : (isset($company) && !is_null($company) ? $company->name : 'Invoice Ninja') }} {{ ctrans('texts.nordigen_handler_subtitle', [], $lang ?? 'en') }}", // 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. @@ -79,58 +79,57 @@ contents.style["opacity"] = "80%"; let restartFlow = false; // return, restart, refresh - heading.innerHTML = "An Error has occured"; - contents.innerHTML = "An unknown Error has occured! Reason: " + failedReason; + 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": - heading.innerHTML = "Invalid Token"; - contents.innerHTML = "The provided token was invalid. Please restart the flow, with a valid one_time_token. Contact support for help, if this issue persists."; + heading.innerHTML = "{{ ctrans('texts.nordigen_handler_error_heading_token_invalid', [], $lang ?? 'en') }}"; + contents.innerHTML = "{{ ctrans('texts.nordigen_handler_error_contents_token_invalid', [], $lang ?? 'en') }}"; break; case "account-config-invalid": - heading.innerHTML = "Missing Credentials"; - contents.innerHTML = "The provided credentials for nordigen are eighter missing or invalid. Contact support for help, if this issue persists."; + heading.innerHTML = "{{ ctrans('texts.nordigen_handler_error_heading_account_config_invalid', [], $lang ?? 'en') }}"; + contents.innerHTML = "{{ ctrans('texts.nordigen_handler_error_contents_account_config_invalid', [], $lang ?? 'en') }}"; break; case "not-available": - heading.innerHTML = "Not Available"; - contents.innerHTML = "This flow is not available for your account. Considder upgrading to enterprise version. Contact support for help, if this issue persists."; + heading.innerHTML = "{{ ctrans('texts.nordigen_handler_error_heading_not_available', [], $lang ?? 'en') }}"; + contents.innerHTML = "{{ ctrans('texts.nordigen_handler_error_contents_not_available', [], $lang ?? 'en') }}"; break; case "institution-invalid": restartFlow = true; - heading.innerHTML = "Invalid Institution"; - contents.innerHTML = "The provided institution-id is invalid or no longer valid. You can go to the bank selection page by clicking the button below or cancel the flow by clicking on the 'X' above."; + 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; // Confirm Screen Errors case "ref-invalid": - heading.innerHTML = "Invalid Reference"; - contents.innerHTML = "Nordigen did not provide a valid reference. Please run flow again and contact support, if this issue persists."; + heading.innerHTML = "{{ ctrans('texts.nordigen_handler_error_heading_ref_invalid', [], $lang ?? 'en') }}"; + contents.innerHTML = "{{ ctrans('texts.nordigen_handler_error_contents_ref_invalid', [], $lang ?? 'en') }}"; break; case "requisition-not-found": - heading.innerHTML = "Invalid Requisition"; - contents.innerHTML = "Nordigen did not provide a valid reference. Please run flow again and contact support, if this issue persists."; + heading.innerHTML = "{{ ctrans('texts.nordigen_handler_error_heading_not_found', [], $lang ?? 'en') }}"; + contents.innerHTML = "{{ ctrans('texts.nordigen_handler_error_contents_not_found', [], $lang ?? 'en') }}"; break; case "requisition-invalid-status": - heading.innerHTML = "Not Ready"; - contents.innerHTML = "You may called this site to early. Please finish authorization and refresh this page. Contact support for help, if this issue persists."; + heading.innerHTML = "{{ ctrans('texts.nordigen_handler_error_heading_requisition_invalid_status') }}"; + contents.innerHTML = "{{ ctrans('texts.nordigen_handler_error_contents_requisition_invalid_status', [], $lang ?? 'en') }}"; break; case "requisition-no-accounts": - heading.innerHTML = "No Accounts selected"; - contents.innerHTML = "The service has not returned any valid accounts. Considder restarting the flow."; + heading.innerHTML = "{{ ctrans('texts.nordigen_handler_error_heading_requisition_no_accounts', [], $lang ?? 'en') }}"; + contents.innerHTML = "{{ ctrans('texts.nordigen_handler_error_contents_requisition_no_accounts', [], $lang ?? 'en') }}"; break; case "unknown": break; default: console.warn('Invalid or missing failed_reason code: ' + failedReason); break; - } wrapper.appendChild(contents); const restartUrl = new URL(window.location.pathname, window.location.origin); // no searchParams const returnButton = document.createElement('div'); returnButton.className = "mt-4"; - returnButton.innerHTML = `${restartFlow ? 'Restart flow.' : 'Return to application.'}` - wrapper.appendChild(returnButton); + returnButton.innerHTML = `${restartFlow ? "{{ ctrans('texts.nordigen_handler_restart', [], $lang ?? 'en') }}" : "{{ ctrans('texts.nordigen_handler_return', [], $lang ?? 'en') }}"}` + wrapper.ald(returnButton); } From 2e7efb125ed36ddca5b259957d8c0a44e46bb195 Mon Sep 17 00:00:00 2001 From: paulwer Date: Fri, 15 Dec 2023 14:54:17 +0100 Subject: [PATCH 45/69] fixes --- resources/views/bank/nordigen/handler.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/bank/nordigen/handler.blade.php b/resources/views/bank/nordigen/handler.blade.php index ed281ae5a32f..bd0be707e2a9 100644 --- a/resources/views/bank/nordigen/handler.blade.php +++ b/resources/views/bank/nordigen/handler.blade.php @@ -110,7 +110,7 @@ contents.innerHTML = "{{ ctrans('texts.nordigen_handler_error_contents_not_found', [], $lang ?? 'en') }}"; break; case "requisition-invalid-status": - heading.innerHTML = "{{ ctrans('texts.nordigen_handler_error_heading_requisition_invalid_status') }}"; + heading.innerHTML = "{{ ctrans('texts.nordigen_handler_error_heading_requisition_invalid_status', [], $lang ?? 'en') }}"; contents.innerHTML = "{{ ctrans('texts.nordigen_handler_error_contents_requisition_invalid_status', [], $lang ?? 'en') }}"; break; case "requisition-no-accounts": From 1e365519171b9582761fdae9d3ae0d63398a4e9b Mon Sep 17 00:00:00 2001 From: paulwer Date: Fri, 15 Dec 2023 16:39:05 +0100 Subject: [PATCH 46/69] fixes --- resources/views/bank/nordigen/handler.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/bank/nordigen/handler.blade.php b/resources/views/bank/nordigen/handler.blade.php index bd0be707e2a9..6795d4344bdc 100644 --- a/resources/views/bank/nordigen/handler.blade.php +++ b/resources/views/bank/nordigen/handler.blade.php @@ -129,7 +129,7 @@ const returnButton = document.createElement('div'); returnButton.className = "mt-4"; returnButton.innerHTML = `${restartFlow ? "{{ ctrans('texts.nordigen_handler_restart', [], $lang ?? 'en') }}" : "{{ ctrans('texts.nordigen_handler_return', [], $lang ?? 'en') }}"}` - wrapper.ald(returnButton); + wrapper.appendChild(returnButton); } From c3ff64a0e63b106d4aecf856ba40df17b38e88a2 Mon Sep 17 00:00:00 2001 From: paulwer Date: Fri, 15 Dec 2023 16:51:24 +0100 Subject: [PATCH 47/69] change debitor to participant --- .../Transformer/TransactionTransformer.php | 106 +++++++++--------- app/Models/BankTransaction.php | 8 +- .../BankTransactionTransformer.php | 4 +- ...3_11_26_082959_add_bank_integration_id.php | 4 +- 4 files changed, 61 insertions(+), 61 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php index bd905bb42eab..05350d87398b 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php @@ -64,73 +64,73 @@ use Log; class TransactionTransformer implements BankRevenueInterface { - use AppSetup; + use AppSetup; - public function transform($transactionResponse) - { - $data = []; + public function transform($transactionResponse) + { + $data = []; - if (!array_key_exists('transactions', $transactionResponse) || !array_key_exists('booked', $transactionResponse["transactions"])) - throw new \Exception('invalid dataset'); + if (!array_key_exists('transactions', $transactionResponse) || !array_key_exists('booked', $transactionResponse["transactions"])) + throw new \Exception('invalid dataset'); - foreach ($transactionResponse["transactions"]["booked"] as $transaction) { - $data[] = $this->transformTransaction($transaction); - } - - return $data; + foreach ($transactionResponse["transactions"]["booked"] as $transaction) { + $data[] = $this->transformTransaction($transaction); } - public function transformTransaction($transaction) - { + return $data; + } - if (!array_key_exists('transactionId', $transaction) || !array_key_exists('transactionAmount', $transaction)) - throw new \Exception('invalid dataset'); + public function transformTransaction($transaction) + { - // description could be in varios places - $description = ''; - if (array_key_exists('remittanceInformationStructured', $transaction)) - $description = $transaction["remittanceInformationStructured"]; - else if (array_key_exists('remittanceInformationStructuredArray', $transaction)) - $description = implode(' \r\n', $transaction["remittanceInformationStructuredArray"]); - else if (array_key_exists('remittanceInformationUnstructured', $transaction)) - $description = $transaction["remittanceInformationUnstructured"]; - else - Log::warning("Missing description for the following transaction: " . json_encode($transaction)); + if (!array_key_exists('transactionId', $transaction) || !array_key_exists('transactionAmount', $transaction)) + throw new \Exception('invalid dataset'); - return [ - 'transaction_id' => $transaction["transactionId"], - 'amount' => abs((int) $transaction["transactionAmount"]["amount"]), - 'currency_id' => $this->convertCurrency($transaction["transactionAmount"]["currency"]), - 'category_id' => 0, // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc - 'category_type' => array_key_exists('additionalInformation', $transaction) ? $transaction["additionalInformation"] : '', // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc - 'date' => $transaction["bookingDate"], - 'description' => $description, - 'debitor' => array_key_exists('debtorAccount', $transaction) && array_key_exists('iban', $transaction["debtorAccount"]) ? $transaction['debtorAccount']['iban'] : null, - 'debitor_name' => array_key_exists('debtorName', $transaction) ? $transaction['debtorName'] : null, - 'base_type' => (int) $transaction["transactionAmount"]["amount"] <= 0 ? 'DEBIT' : 'CREDIT', - ]; + // description could be in varios places + $description = ''; + if (array_key_exists('remittanceInformationStructured', $transaction)) + $description = $transaction["remittanceInformationStructured"]; + else if (array_key_exists('remittanceInformationStructuredArray', $transaction)) + $description = implode(' \r\n', $transaction["remittanceInformationStructuredArray"]); + else if (array_key_exists('remittanceInformationUnstructured', $transaction)) + $description = $transaction["remittanceInformationUnstructured"]; + else + Log::warning("Missing description for the following transaction: " . json_encode($transaction)); + return [ + 'transaction_id' => $transaction["transactionId"], + 'amount' => abs((int) $transaction["transactionAmount"]["amount"]), + 'currency_id' => $this->convertCurrency($transaction["transactionAmount"]["currency"]), + 'category_id' => 0, // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc + 'category_type' => array_key_exists('additionalInformation', $transaction) ? $transaction["additionalInformation"] : '', // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc + 'date' => $transaction["bookingDate"], + 'description' => $description, + 'participant' => array_key_exists('debtorAccount', $transaction) && array_key_exists('iban', $transaction["debtorAccount"]) ? $transaction['debtorAccount']['iban'] : null, + 'participant_name' => array_key_exists('debtorName', $transaction) ? $transaction['debtorName'] : null, + 'base_type' => (int) $transaction["transactionAmount"]["amount"] <= 0 ? 'DEBIT' : 'CREDIT', + ]; + + } + + private function convertCurrency(string $code) + { + + $currencies = Cache::get('currencies'); + + if (!$currencies) { + $this->buildCache(true); } - private function convertCurrency(string $code) - { + $currency = $currencies->filter(function ($item) use ($code) { + return $item->code == $code; + })->first(); - $currencies = Cache::get('currencies'); + if ($currency) + return $currency->id; - if (!$currencies) { - $this->buildCache(true); - } + return 1; - $currency = $currencies->filter(function ($item) use ($code) { - return $item->code == $code; - })->first(); - - if ($currency) - return $currency->id; - - return 1; - - } + } } diff --git a/app/Models/BankTransaction.php b/app/Models/BankTransaction.php index 378945656705..d39c83a62985 100644 --- a/app/Models/BankTransaction.php +++ b/app/Models/BankTransaction.php @@ -34,8 +34,8 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property string|null $date * @property int $bank_account_id * @property string|null $description - * @property string|null $debitor - * @property string|null $debitor_name + * @property string|null $participant + * @property string|null $participant_name * @property string $invoice_ids * @property int|null $expense_id * @property int|null $vendor_id @@ -87,8 +87,8 @@ class BankTransaction extends BaseModel 'expense_id', 'vendor_id', 'amount', - 'debitor', - 'debitor_name' + 'participant', + 'participant_name' ]; diff --git a/app/Transformers/BankTransactionTransformer.php b/app/Transformers/BankTransactionTransformer.php index ca122a183be6..15654f9654e1 100644 --- a/app/Transformers/BankTransactionTransformer.php +++ b/app/Transformers/BankTransactionTransformer.php @@ -63,8 +63,8 @@ class BankTransactionTransformer extends EntityTransformer 'bank_account_id' => (int) $bank_transaction->bank_account_id, 'status_id' => (string) $bank_transaction->status_id, 'description' => (string) $bank_transaction->description ?: '', - 'debitor' => (string) $bank_transaction->debitor ?: '', - 'debitor_name' => (string) $bank_transaction->debitor_name ?: '', + 'participant' => (string) $bank_transaction->participant ?: '', + 'participant_name' => (string) $bank_transaction->participant_name ?: '', 'base_type' => (string) $bank_transaction->base_type ?: '', 'invoice_ids' => (string) $bank_transaction->invoice_ids ?: '', 'expense_id' => (string) $bank_transaction->expense_id ?: '', diff --git a/database/migrations/2023_11_26_082959_add_bank_integration_id.php b/database/migrations/2023_11_26_082959_add_bank_integration_id.php index 3630b4d0d08c..ea3517bcd28f 100644 --- a/database/migrations/2023_11_26_082959_add_bank_integration_id.php +++ b/database/migrations/2023_11_26_082959_add_bank_integration_id.php @@ -28,8 +28,8 @@ return new class extends Migration { // MAYBE migration of account->bank_account_id etc Schema::table('bank_transactions', function (Blueprint $table) { - $table->string('debitor')->nullable(); // iban, credit-card info or else - $table->string('debitor_name')->nullable(); // name + $table->string('participant')->nullable(); // iban, credit-card info or else + $table->string('participant_name')->nullable(); // name }); } From a4e835119924c4a9d9a9a10b149660c3cd665ea7 Mon Sep 17 00:00:00 2001 From: paulwer Date: Sat, 16 Dec 2023 14:33:09 +0100 Subject: [PATCH 48/69] remove unnecessary code and change sync-behavior to suppress sync error based on wrong from_date --- .../Bank/ProcessBankTransactionsNordigen.php | 58 ++++++------------- 1 file changed, 17 insertions(+), 41 deletions(-) diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index acd010886e69..962bcb4cfd85 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -34,10 +34,6 @@ class ProcessBankTransactionsNordigen implements ShouldQueue private ?string $from_date; - private bool $stop_loop = true; - - private int $skip = 0; - public Company $company; public Nordigen $nordigen; public $nordigen_account; @@ -48,7 +44,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue public function __construct(BankIntegration $bank_integration) { $this->bank_integration = $bank_integration; - $this->from_date = $bank_integration->from_date; + $this->from_date = $bank_integration->from_date ?: now()->subDays(90); $this->company = $this->bank_integration->company; } @@ -71,9 +67,6 @@ class ProcessBankTransactionsNordigen implements ShouldQueue set_time_limit(0); - //Loop through everything until we are up to date - $this->from_date = $this->from_date ?: '2021-01-01'; - // UPDATE ACCOUNT try { $this->updateAccount(); @@ -94,27 +87,27 @@ class ProcessBankTransactionsNordigen implements ShouldQueue return; // UPDATE TRANSACTIONS - do { + try { + $this->processTransactions(); + } catch (\Exception $e) { + // reset from_date in case this was the error (self-heal) and perform a max sync of data we can fetch (next-time) @todo we should analyze the error for this + $this->bank_integration->from_date = now()->subDays(90); + $this->bank_integration->save(); - try { - $this->processTransactions(); - } catch (\Exception $e) { - nlog("{$this->bank_integration->account->key} - exited abnormally => " . $e->getMessage()); + nlog("{$this->bank_integration->account->key} - exited abnormally => " . $e->getMessage()); - $content = [ - "Processing transactions for account: {$this->bank_integration->account->key} failed", - "Exception Details => ", - $e->getMessage(), - ]; + $content = [ + "Processing transactions for account: {$this->bank_integration->account->key} failed", + "Exception Details => ", + $e->getMessage(), + ]; - $this->bank_integration->company->notification(new GenericNinjaAdminNotification($content))->ninja(); - - throw $e; - } + $this->bank_integration->company->notification(new GenericNinjaAdminNotification($content))->ninja(); + throw $e; } - while ($this->stop_loop); + // Perform Matching BankMatchingService::dispatch($this->company->id, $this->company->db); } @@ -148,18 +141,13 @@ class ProcessBankTransactionsNordigen implements ShouldQueue //Get transaction count object $transactions = $this->nordigen->getTransactions($this->bank_integration->nordigen_account_id, $this->from_date); - Log::Info($transactions); - - //Get int count - $count = sizeof($transactions); - //if no transactions, update the from_date and move on if (count($transactions) == 0) { $this->bank_integration->from_date = now()->subDays(5); $this->bank_integration->disabled_upstream = false; $this->bank_integration->save(); - $this->stop_loop = false; + return; } @@ -175,7 +163,6 @@ class ProcessBankTransactionsNordigen implements ShouldQueue $now = now(); - foreach ($transactions as $transaction) { if (BankTransaction::where('transaction_id', $transaction['transaction_id'])->where('company_id', $this->company->id)->where('bank_integration_id', $this->bank_integration->id)->withTrashed()->exists()) @@ -194,17 +181,6 @@ class ProcessBankTransactionsNordigen implements ShouldQueue } - - // $this->skip = $this->skip + 500; - - // if ($count < 500) { - // $this->stop_loop = false; - // $this->bank_integration->from_date = now()->subDays(5); - // $this->bank_integration->save(); - - // } - - $this->stop_loop = false; $this->bank_integration->from_date = now()->subDays(5); $this->bank_integration->save(); From 4f6e0f53f745e37ce6a8b24d343bf28d302cb4c0 Mon Sep 17 00:00:00 2001 From: paulwer Date: Sat, 16 Dec 2023 15:35:38 +0100 Subject: [PATCH 49/69] fix composer build --- composer.lock | 196 +++++++++++++++++++++++++------------------------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/composer.lock b/composer.lock index 31846193c9df..d54b77039138 100644 --- a/composer.lock +++ b/composer.lock @@ -437,16 +437,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.293.7", + "version": "3.294.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "3bf86ba8b9bbea2b298f89e6f5edc58de276690b" + "reference": "63c720229a9c9cdedff6bac98d6e72be8cc241f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3bf86ba8b9bbea2b298f89e6f5edc58de276690b", - "reference": "3bf86ba8b9bbea2b298f89e6f5edc58de276690b", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/63c720229a9c9cdedff6bac98d6e72be8cc241f1", + "reference": "63c720229a9c9cdedff6bac98d6e72be8cc241f1", "shasum": "" }, "require": { @@ -522,9 +522,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.293.7" + "source": "https://github.com/aws/aws-sdk-php/tree/3.294.1" }, - "time": "2023-12-08T19:11:21+00:00" + "time": "2023-12-15T19:25:52+00:00" }, { "name": "bacon/bacon-qr-code", @@ -728,16 +728,16 @@ }, { "name": "carbonphp/carbon-doctrine-types", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", - "reference": "67a77972b9f398ae7068dabacc39c08aeee170d5" + "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/67a77972b9f398ae7068dabacc39c08aeee170d5", - "reference": "67a77972b9f398ae7068dabacc39c08aeee170d5", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", + "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", "shasum": "" }, "require": { @@ -769,7 +769,7 @@ "keywords": ["carbon", "date", "datetime", "doctrine", "time"], "support": { "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", - "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.0.0" + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.1.0" }, "funding": [ { @@ -785,7 +785,7 @@ "type": "tidelift" } ], - "time": "2023-10-01T14:29:01+00:00" + "time": "2023-12-11T17:09:12+00:00" }, { "name": "checkout/checkout-sdk-php", @@ -3457,16 +3457,16 @@ }, { "name": "imdhemy/google-play-billing", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/imdhemy/google-play-billing.git", - "reference": "a227174a71bc5d7b3e5f9aa4fcad2c4a9a11a8a4" + "reference": "bb94f3b6ddb021605815e528f31b8c930c41677c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/imdhemy/google-play-billing/zipball/a227174a71bc5d7b3e5f9aa4fcad2c4a9a11a8a4", - "reference": "a227174a71bc5d7b3e5f9aa4fcad2c4a9a11a8a4", + "url": "https://api.github.com/repos/imdhemy/google-play-billing/zipball/bb94f3b6ddb021605815e528f31b8c930c41677c", + "reference": "bb94f3b6ddb021605815e528f31b8c930c41677c", "shasum": "" }, "require": { @@ -3500,22 +3500,22 @@ "description": "Google Play Billing", "support": { "issues": "https://github.com/imdhemy/google-play-billing/issues", - "source": "https://github.com/imdhemy/google-play-billing/tree/1.5.0" + "source": "https://github.com/imdhemy/google-play-billing/tree/1.5.1" }, - "time": "2023-09-17T12:33:33+00:00" + "time": "2023-12-15T10:25:05+00:00" }, { "name": "imdhemy/laravel-purchases", - "version": "1.9.0", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/imdhemy/laravel-in-app-purchases.git", - "reference": "4471f5dc211931b847ac0bf88f78bd4fa9e3760d" + "reference": "b74e09b78fb3e0f1b1630dbcfd23d9f6fe251b90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/imdhemy/laravel-in-app-purchases/zipball/4471f5dc211931b847ac0bf88f78bd4fa9e3760d", - "reference": "4471f5dc211931b847ac0bf88f78bd4fa9e3760d", + "url": "https://api.github.com/repos/imdhemy/laravel-in-app-purchases/zipball/b74e09b78fb3e0f1b1630dbcfd23d9f6fe251b90", + "reference": "b74e09b78fb3e0f1b1630dbcfd23d9f6fe251b90", "shasum": "" }, "require": { @@ -3569,7 +3569,7 @@ ], "support": { "issues": "https://github.com/imdhemy/laravel-in-app-purchases/issues", - "source": "https://github.com/imdhemy/laravel-in-app-purchases/tree/1.9.0" + "source": "https://github.com/imdhemy/laravel-in-app-purchases/tree/1.9.1" }, "funding": [ { @@ -3577,7 +3577,7 @@ "type": "github" } ], - "time": "2023-09-19T06:01:35+00:00" + "time": "2023-12-15T10:35:56+00:00" }, { "name": "intervention/image", @@ -3827,47 +3827,47 @@ }, { "name": "jms/serializer", - "version": "3.28.0", + "version": "3.29.1", "source": { "type": "git", "url": "https://github.com/schmittjoh/serializer.git", - "reference": "5a5a03a71a28a480189c5a0ca95893c19f1d120c" + "reference": "111451f43abb448ce297361a8ab96a9591e848cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/5a5a03a71a28a480189c5a0ca95893c19f1d120c", - "reference": "5a5a03a71a28a480189c5a0ca95893c19f1d120c", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/111451f43abb448ce297361a8ab96a9591e848cd", + "reference": "111451f43abb448ce297361a8ab96a9591e848cd", "shasum": "" }, "require": { - "doctrine/annotations": "^1.13 || ^2.0", - "doctrine/instantiator": "^1.0.3 || ^2.0", + "doctrine/annotations": "^1.14 || ^2.0", + "doctrine/instantiator": "^1.3.1 || ^2.0", "doctrine/lexer": "^2.0 || ^3.0", "jms/metadata": "^2.6", - "php": "^7.2||^8.0", - "phpstan/phpdoc-parser": "^0.4 || ^0.5 || ^1.0" + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.20" }, "require-dev": { "doctrine/coding-standard": "^12.0", - "doctrine/orm": "~2.1", - "doctrine/persistence": "^1.3.3|^2.0|^3.0", - "doctrine/phpcr-odm": "^1.3|^2.0", + "doctrine/orm": "^2.14 || ^3.0", + "doctrine/persistence": "^2.5.2 || ^3.0", + "doctrine/phpcr-odm": "^1.5.2 || ^2.0", "ext-pdo_sqlite": "*", - "jackalope/jackalope-doctrine-dbal": "^1.1.5", - "ocramius/proxy-manager": "^1.0|^2.0", + "jackalope/jackalope-doctrine-dbal": "^1.3", + "ocramius/proxy-manager": "^1.0 || ^2.0", "phpbench/phpbench": "^1.0", "phpstan/phpstan": "^1.0.2", - "phpunit/phpunit": "^8.5.21||^9.0||^10.0", - "psr/container": "^1.0|^2.0", - "symfony/dependency-injection": "^3.0|^4.0|^5.0|^6.0", - "symfony/expression-language": "^3.2|^4.0|^5.0|^6.0", - "symfony/filesystem": "^3.0|^4.0|^5.0|^6.0", - "symfony/form": "^3.0|^4.0|^5.0|^6.0", - "symfony/translation": "^3.0|^4.0|^5.0|^6.0", - "symfony/uid": "^5.1|^6.0", - "symfony/validator": "^3.1.9|^4.0|^5.0|^6.0", - "symfony/yaml": "^3.3|^4.0|^5.0|^6.0", - "twig/twig": "~1.34|~2.4|^3.0" + "phpunit/phpunit": "^8.5.21 || ^9.0 || ^10.0", + "psr/container": "^1.0 || ^2.0", + "symfony/dependency-injection": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/expression-language": "^3.2 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/filesystem": "^4.2 || ^5.0 || ^6.0 || ^7.0", + "symfony/form": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/translation": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/uid": "^5.1 || ^6.0 || ^7.0", + "symfony/validator": "^3.1.9 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "twig/twig": "^1.34 || ^2.4 || ^3.0" }, "suggest": { "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", @@ -3909,7 +3909,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/serializer/issues", - "source": "https://github.com/schmittjoh/serializer/tree/3.28.0" + "source": "https://github.com/schmittjoh/serializer/tree/3.29.1" }, "funding": [ { @@ -3917,7 +3917,7 @@ "type": "github" } ], - "time": "2023-08-03T14:43:08+00:00" + "time": "2023-12-14T15:25:09+00:00" }, { "name": "josemmo/facturae-php", @@ -4060,16 +4060,16 @@ }, { "name": "laravel/framework", - "version": "v10.35.0", + "version": "v10.37.3", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "91ec2d92d2f6007e9084fe06438b99c91845da69" + "reference": "996375dd61f8c6e4ac262b57ed485655d71fcbdc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/91ec2d92d2f6007e9084fe06438b99c91845da69", - "reference": "91ec2d92d2f6007e9084fe06438b99c91845da69", + "url": "https://api.github.com/repos/laravel/framework/zipball/996375dd61f8c6e4ac262b57ed485655d71fcbdc", + "reference": "996375dd61f8c6e4ac262b57ed485655d71fcbdc", "shasum": "" }, "require": { @@ -4253,7 +4253,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-12-05T14:50:33+00:00" + "time": "2023-12-13T20:10:58+00:00" }, { "name": "laravel/prompts", @@ -4914,16 +4914,16 @@ }, { "name": "league/csv", - "version": "9.12.0", + "version": "9.13.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "c1dc31e23eb3cd0f7b537ee1a669d5f58d5cfe21" + "reference": "3690cc71bfe8dc3b6daeef356939fac95348f0a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/c1dc31e23eb3cd0f7b537ee1a669d5f58d5cfe21", - "reference": "c1dc31e23eb3cd0f7b537ee1a669d5f58d5cfe21", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/3690cc71bfe8dc3b6daeef356939fac95348f0a8", + "reference": "3690cc71bfe8dc3b6daeef356939fac95348f0a8", "shasum": "" }, "require": { @@ -4938,11 +4938,11 @@ "ext-xdebug": "*", "friendsofphp/php-cs-fixer": "^v3.22.0", "phpbench/phpbench": "^1.2.15", - "phpstan/phpstan": "^1.10.46", + "phpstan/phpstan": "^1.10.50", "phpstan/phpstan-deprecation-rules": "^1.1.4", "phpstan/phpstan-phpunit": "^1.3.15", "phpstan/phpstan-strict-rules": "^1.5.2", - "phpunit/phpunit": "^10.4.2", + "phpunit/phpunit": "^10.5.3", "symfony/var-dumper": "^6.4.0" }, "suggest": { @@ -4995,7 +4995,7 @@ "type": "github" } ], - "time": "2023-12-01T17:54:07+00:00" + "time": "2023-12-16T11:03:20+00:00" }, { "name": "league/flysystem", @@ -7901,16 +7901,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.4", + "version": "1.24.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496" + "reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6bd0c26f3786cd9b7c359675cb789e35a8e07496", - "reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fedf211ff14ec8381c9bf5714e33a7a552dd1acc", + "reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc", "shasum": "" }, "require": { @@ -7938,9 +7938,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.4" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.5" }, - "time": "2023-11-26T18:29:22+00:00" + "time": "2023-12-16T09:33:33+00:00" }, { "name": "pragmarx/google2fa", @@ -8532,16 +8532,16 @@ }, { "name": "pusher/pusher-php-server", - "version": "7.2.3", + "version": "7.2.4", "source": { "type": "git", "url": "https://github.com/pusher/pusher-http-php.git", - "reference": "416e68dd5f640175ad5982131c42a7a666d1d8e9" + "reference": "de2f72296808f9cafa6a4462b15a768ff130cddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/416e68dd5f640175ad5982131c42a7a666d1d8e9", - "reference": "416e68dd5f640175ad5982131c42a7a666d1d8e9", + "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/de2f72296808f9cafa6a4462b15a768ff130cddb", + "reference": "de2f72296808f9cafa6a4462b15a768ff130cddb", "shasum": "" }, "require": { @@ -8585,9 +8585,9 @@ ], "support": { "issues": "https://github.com/pusher/pusher-http-php/issues", - "source": "https://github.com/pusher/pusher-http-php/tree/7.2.3" + "source": "https://github.com/pusher/pusher-http-php/tree/7.2.4" }, - "time": "2023-05-17T16:00:06+00:00" + "time": "2023-12-15T10:58:53+00:00" }, { "name": "ralouphie/getallheaders", @@ -9325,16 +9325,16 @@ }, { "name": "setasign/fpdi", - "version": "v2.5.0", + "version": "v2.6.0", "source": { "type": "git", "url": "https://github.com/Setasign/FPDI.git", - "reference": "ecf0459643ec963febfb9a5d529dcd93656006a4" + "reference": "a6db878129ec6c7e141316ee71872923e7f1b7ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Setasign/FPDI/zipball/ecf0459643ec963febfb9a5d529dcd93656006a4", - "reference": "ecf0459643ec963febfb9a5d529dcd93656006a4", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/a6db878129ec6c7e141316ee71872923e7f1b7ad", + "reference": "a6db878129ec6c7e141316ee71872923e7f1b7ad", "shasum": "" }, "require": { @@ -9346,8 +9346,8 @@ }, "require-dev": { "phpunit/phpunit": "~5.7", - "setasign/fpdf": "~1.8", - "setasign/tfpdf": "~1.31", + "setasign/fpdf": "~1.8.6", + "setasign/tfpdf": "~1.33", "squizlabs/php_codesniffer": "^3.5", "tecnickcom/tcpdf": "~6.2" }, @@ -9379,7 +9379,7 @@ "keywords": ["fpdf", "fpdi", "pdf"], "support": { "issues": "https://github.com/Setasign/FPDI/issues", - "source": "https://github.com/Setasign/FPDI/tree/v2.5.0" + "source": "https://github.com/Setasign/FPDI/tree/v2.6.0" }, "funding": [ { @@ -9387,7 +9387,7 @@ "type": "tidelift" } ], - "time": "2023-09-28T10:46:27+00:00" + "time": "2023-12-11T16:03:32+00:00" }, { "name": "shopify/shopify-api", @@ -14939,16 +14939,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.48", + "version": "1.10.50", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "087ed4b5f4a7a6e8f3bbdfbfe98ce5c181380bc6" + "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/087ed4b5f4a7a6e8f3bbdfbfe98ce5c181380bc6", - "reference": "087ed4b5f4a7a6e8f3bbdfbfe98ce5c181380bc6", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4", + "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4", "shasum": "" }, "require": { @@ -14987,7 +14987,7 @@ "type": "tidelift" } ], - "time": "2023-12-08T14:34:28+00:00" + "time": "2023-12-13T10:59:42+00:00" }, { "name": "phpunit/php-code-coverage", @@ -15279,16 +15279,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.2", + "version": "10.5.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "5aedff46afba98dddecaa12349ec044d9103d4fe" + "reference": "6fce887c71076a73f32fd3e0774a6833fc5c7f19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5aedff46afba98dddecaa12349ec044d9103d4fe", - "reference": "5aedff46afba98dddecaa12349ec044d9103d4fe", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6fce887c71076a73f32fd3e0774a6833fc5c7f19", + "reference": "6fce887c71076a73f32fd3e0774a6833fc5c7f19", "shasum": "" }, "require": { @@ -15348,7 +15348,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.2" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.3" }, "funding": [ { @@ -15364,7 +15364,7 @@ "type": "tidelift" } ], - "time": "2023-12-05T14:54:33+00:00" + "time": "2023-12-13T07:25:23+00:00" }, { "name": "sebastian/cli-parser", @@ -16399,16 +16399,16 @@ }, { "name": "spatie/laravel-ignition", - "version": "2.3.1", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "bf21cd15aa47fa4ec5d73bbc932005c70261efc8" + "reference": "4800661a195e15783477d99f7f8f669a49793996" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/bf21cd15aa47fa4ec5d73bbc932005c70261efc8", - "reference": "bf21cd15aa47fa4ec5d73bbc932005c70261efc8", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/4800661a195e15783477d99f7f8f669a49793996", + "reference": "4800661a195e15783477d99f7f8f669a49793996", "shasum": "" }, "require": { @@ -16478,7 +16478,7 @@ "type": "github" } ], - "time": "2023-10-09T12:55:26+00:00" + "time": "2023-12-15T13:44:49+00:00" }, { "name": "spaze/phpstan-stripe", From e4d0c9f901b52cf894e0280719c2a936e3643de8 Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 18 Dec 2023 15:48:25 +0100 Subject: [PATCH 50/69] update deps, fixes & stop processing archived bank_integrations --- .../Controllers/Bank/NordigenController.php | 4 +-- .../Controllers/Bank/YodleeController.php | 2 +- .../Controllers/BankIntegrationController.php | 4 +-- app/Jobs/Ninja/BankTransactionSync.php | 4 +-- composer.lock | 36 +++++++++---------- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index 8c1fc41b6663..e0b065bdcd32 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -296,7 +296,7 @@ class NordigenController extends BaseController $existing_bank_integration->bank_account_status = $account['account_status']; $existing_bank_integration->disabled_upstream = false; $existing_bank_integration->auto_sync = true; - $bank_integration->from_date = now()->subDays(90); // default max-fetch interval of nordigen is 90 days + $existing_bank_integration->from_date = now()->subDays(90); // default max-fetch interval of nordigen is 90 days $existing_bank_integration->save(); @@ -306,7 +306,7 @@ class NordigenController extends BaseController } // perform update in background - $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->each(function ($bank_integration) { + $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->where('is_deleted', false)->each(function ($bank_integration) { ProcessBankTransactionsNordigen::dispatch($bank_integration); diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index ac9b0b478195..b56dd6f08f5f 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -98,7 +98,7 @@ class YodleeController extends BaseController } - $company->account->bank_integrations->each(function ($bank_integration) use ($company) { // TODO: filter to yodlee only + $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->where('is_deleted', false)->each(function ($bank_integration) use ($company) { // TODO: filter to yodlee only ProcessBankTransactionsYodlee::dispatch($company->account, $bank_integration); diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 8c1862b1516c..41a623835506 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -340,13 +340,13 @@ class BankIntegrationController extends BaseController $account = auth()->user()->account(); if (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise') { - $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->where('is_deleted', false)->cursor()->each(function ($bank_integration) use ($account) { (new ProcessBankTransactionsYodlee($account, $bank_integration))->handle(); }); } if (config("ninja.nordigen.secret_id") && config("ninja.nordigen.secret_key") && (Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) { - $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->cursor()->each(function ($bank_integration) { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->where('is_deleted', false)->cursor()->each(function ($bank_integration) { Log::info($bank_integration); (new ProcessBankTransactionsNordigen($bank_integration))->handle(); diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php index 446552c8793f..04a08f8531c8 100644 --- a/app/Jobs/Ninja/BankTransactionSync.php +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -55,7 +55,7 @@ class BankTransactionSync implements ShouldQueue Account::with('bank_integrations')->whereNotNull('bank_integration_account_id')->cursor()->each(function ($account) { if ($account->isPaid() && $account->plan == 'enterprise') { - $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->where('is_deleted', false)->cursor()->each(function ($bank_integration) use ($account) { (new ProcessBankTransactionsYodlee($account, $bank_integration))->handle(); }); } @@ -69,7 +69,7 @@ class BankTransactionSync implements ShouldQueue Account::with('bank_integrations')->cursor()->each(function ($account) { if ((Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) { - $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->cursor()->each(function ($bank_integration) { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->where('is_deleted', false)->cursor()->each(function ($bank_integration) { (new ProcessBankTransactionsNordigen($bank_integration))->handle(); }); } diff --git a/composer.lock b/composer.lock index d54b77039138..1509883b6ad0 100644 --- a/composer.lock +++ b/composer.lock @@ -972,16 +972,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.3.7", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "76e46335014860eec1aa5a724799a00a2e47cc85" + "reference": "b66d11b7479109ab547f9405b97205640b17d385" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/76e46335014860eec1aa5a724799a00a2e47cc85", - "reference": "76e46335014860eec1aa5a724799a00a2e47cc85", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/b66d11b7479109ab547f9405b97205640b17d385", + "reference": "b66d11b7479109ab547f9405b97205640b17d385", "shasum": "" }, "require": { @@ -993,7 +993,7 @@ "phpstan/phpstan": "^0.12.55", "psr/log": "^1.0", "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { @@ -1020,7 +1020,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.3.7" + "source": "https://github.com/composer/ca-bundle/tree/1.4.0" }, "funding": [ { @@ -1036,7 +1036,7 @@ "type": "tidelift" } ], - "time": "2023-08-30T09:31:38+00:00" + "time": "2023-12-18T12:05:55+00:00" }, { "name": "dasprid/enum", @@ -2416,16 +2416,16 @@ }, { "name": "google/apiclient-services", - "version": "v0.327.0", + "version": "v0.328.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "51a11d4ff70dd9f60334525e71bf4cf592e6d282" + "reference": "211ba786d30a4ab21e012d2b7dbefc49b4fc6a3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/51a11d4ff70dd9f60334525e71bf4cf592e6d282", - "reference": "51a11d4ff70dd9f60334525e71bf4cf592e6d282", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/211ba786d30a4ab21e012d2b7dbefc49b4fc6a3d", + "reference": "211ba786d30a4ab21e012d2b7dbefc49b4fc6a3d", "shasum": "" }, "require": { @@ -2448,9 +2448,9 @@ "keywords": ["google"], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.327.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.328.0" }, - "time": "2023-12-11T00:52:16+00:00" + "time": "2023-12-18T01:00:18+00:00" }, { "name": "google/auth", @@ -8795,16 +8795,16 @@ }, { "name": "razorpay/razorpay", - "version": "2.8.7", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/razorpay/razorpay-php.git", - "reference": "2180c8c3c39678623f5cb8f639c39a706de14c44" + "reference": "a3d7c2bcb416091edd6a76eb5a7600eaf00ac837" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/razorpay/razorpay-php/zipball/2180c8c3c39678623f5cb8f639c39a706de14c44", - "reference": "2180c8c3c39678623f5cb8f639c39a706de14c44", + "url": "https://api.github.com/repos/razorpay/razorpay-php/zipball/a3d7c2bcb416091edd6a76eb5a7600eaf00ac837", + "reference": "a3d7c2bcb416091edd6a76eb5a7600eaf00ac837", "shasum": "" }, "require": { @@ -8847,7 +8847,7 @@ "issues": "https://github.com/Razorpay/razorpay-php/issues", "source": "https://github.com/Razorpay/razorpay-php" }, - "time": "2023-09-11T08:31:26+00:00" + "time": "2023-12-18T04:19:46+00:00" }, { "name": "rmccue/requests", From 1060dad7d7747a88239c3d374b9b5c1ee3b54b68 Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 18 Dec 2023 15:58:16 +0100 Subject: [PATCH 51/69] better logging for jobs --- app/Jobs/Bank/ProcessBankTransactionsNordigen.php | 14 +++++++------- app/Jobs/Bank/ProcessBankTransactionsYodlee.php | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index 962bcb4cfd85..ff59383a1766 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -67,14 +67,16 @@ class ProcessBankTransactionsNordigen implements ShouldQueue set_time_limit(0); + nlog("Nordigen: Processing transactions for account: {$this->bank_integration->account->key}"); + // UPDATE ACCOUNT try { $this->updateAccount(); } catch (\Exception $e) { - nlog("{$this->bank_integration->account->key} - exited abnormally => " . $e->getMessage()); + nlog("Nordigen: {$this->bank_integration->nordigen_account_id} - exited abnormally => " . $e->getMessage()); $content = [ - "Processing transactions for account: {$this->bank_integration->account->key} failed", + "Processing transactions for account: {$this->bank_integration->nordigen_account_id} failed", "Exception Details => ", $e->getMessage(), ]; @@ -94,10 +96,10 @@ class ProcessBankTransactionsNordigen implements ShouldQueue $this->bank_integration->from_date = now()->subDays(90); $this->bank_integration->save(); - nlog("{$this->bank_integration->account->key} - exited abnormally => " . $e->getMessage()); + nlog("Nordigen: {$this->bank_integration->nordigen_account_id} - exited abnormally => " . $e->getMessage()); $content = [ - "Processing transactions for account: {$this->bank_integration->account->key} failed", + "Processing transactions for account: {$this->bank_integration->nordigen_account_id} failed", "Exception Details => ", $e->getMessage(), ]; @@ -114,13 +116,11 @@ class ProcessBankTransactionsNordigen implements ShouldQueue private function updateAccount() { - - Log::info("try to execute updateAccount"); if (!$this->nordigen->isAccountActive($this->bank_integration->nordigen_account_id)) { $this->bank_integration->disabled_upstream = true; $this->bank_integration->save(); $this->stop_loop = false; - Log::info("account inactive"); + Log::info("Nordigen: account inactive: " . $this->bank_integration->nordigen_account_id); // @turbo124 @todo send email for expired account return; } diff --git a/app/Jobs/Bank/ProcessBankTransactionsYodlee.php b/app/Jobs/Bank/ProcessBankTransactionsYodlee.php index f5344169e211..0d2122c6733c 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsYodlee.php +++ b/app/Jobs/Bank/ProcessBankTransactionsYodlee.php @@ -71,16 +71,16 @@ class ProcessBankTransactionsYodlee implements ShouldQueue //Loop through everything until we are up to date $this->from_date = $this->from_date ?: '2021-01-01'; - nlog("Processing transactions for account: {$this->bank_integration->account->key}"); + nlog("Yodlee: Processing transactions for account: {$this->bank_integration->account->key}"); do { try { $this->processTransactions(); } catch (\Exception $e) { - nlog("{$this->account->bank_integration_account_id} - exited abnormally => " . $e->getMessage()); + nlog("Yodlee: {$this->bank_integration->bank_account_id} - exited abnormally => " . $e->getMessage()); $content = [ - "Processing transactions for account: {$this->bank_integration->account->key} failed", + "Processing transactions for account: {$this->bank_integration->bank_account_id} failed", "Exception Details => ", $e->getMessage(), ]; @@ -164,7 +164,7 @@ class ProcessBankTransactionsYodlee implements ShouldQueue $now = now(); foreach ($transactions as $transaction) { - if (BankTransaction::query()->where('transaction_id', $transaction['transaction_id'])->where('company_id', $this->company->id)->withTrashed()->exists()) { + if (BankTransaction::query()->where('transaction_id', $transaction['transaction_id'])->where('company_id', $this->company->id)->where('bank_integration_id', $this->bank_integration->id)->withTrashed()->exists()) { // @turbo124 was not scoped to bank_integration_id => from my pov this should be present, because when an account was historized (is_deleted) a transaction can occur multiple (in the archived bank_integration and in the new one continue; } From e4aa8f72bc88e20805c69aa23b3524b8ea277cea Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 18 Dec 2023 16:08:41 +0100 Subject: [PATCH 52/69] some changes related to logging & cleanup --- app/Helpers/Bank/Nordigen/Nordigen.php | 2 - .../Controllers/Bank/NordigenController.php | 91 +++---------------- .../Controllers/Bank/YodleeController.php | 1 - .../Controllers/BankIntegrationController.php | 2 - .../Bank/ProcessBankTransactionsNordigen.php | 2 +- 5 files changed, 15 insertions(+), 83 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index cc83f304770a..08e8563cc0ec 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -112,8 +112,6 @@ class Nordigen { $transactionResponse = $this->client->account($accountId)->getAccountTransactions($dateFrom); - Log::info($transactionResponse); - $it = new TransactionTransformer(); return $it->transform($transactionResponse); } diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index e0b065bdcd32..f760ff4cfbb1 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -27,7 +27,10 @@ use Nordigen\NordigenPHP\Exceptions\NordigenExceptions\NordigenException; class NordigenController extends BaseController { - + /** + * VIEW: Connect Nordigen Bank Integration + * @param ConnectNordigenBankIntegrationRequest $request + */ public function connect(ConnectNordigenBankIntegrationRequest $request) { $data = $request->all(); @@ -73,24 +76,19 @@ class NordigenController extends BaseController $nordigen = new Nordigen(); // show bank_selection_screen, when institution_id is not present - if (!array_key_exists("institution_id", $data)) { - $data = [ + 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" - ]; - - return view('bank.nordigen.handler', $data); - } + ]); // redirect to requisition flow try { $requisition = $nordigen->createRequisition(config('ninja.app_url') . '/nordigen/confirm', $data['institution_id'], $request->token); } 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 - Log::error($e); - Log::info((string) $e->getResponse()->getBody()); $responseBody = (string) $e->getResponse()->getBody(); if (str_contains($responseBody, '"institution_id"')) // provided institution_id was wrong @@ -109,7 +107,10 @@ class NordigenController extends BaseController 'failed_reason' => "token-invalid", "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=token-invalid", ]); - else + else { + nlog("Unknown Error from nordigen: " . $e); + nlog($responseBody); + return view('bank.nordigen.handler', [ 'lang' => $lang, 'company' => $company, @@ -117,6 +118,7 @@ class NordigenController extends BaseController 'failed_reason' => "unknown", "redirectUrl" => $context["redirect"] . "?action=nordigen_connect&status=failed&reason=unknown", ]); + } } // save cache @@ -127,72 +129,11 @@ class NordigenController extends BaseController } /** - * Process Nordigen Institutions GETTER. - * @param ConfirmNordigenBankIntegrationRequest $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"), - * ), - * ) + * VIEW: Confirm Nordigen Bank Integration (redirect after nordigen flow) + * @param ConnectNordigenBankIntegrationRequest $request */ - - /* - { - "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(ConfirmNordigenBankIntegrationRequest $request) { - $data = $request->all(); $lang = $data['lang'] ?? 'en'; @@ -204,7 +145,6 @@ class NordigenController extends BaseController "redirectUrl" => ($context && array_key_exists("redirect", $context) ? $context["redirect"] : config('ninja.app_url')) . "?action=nordigen_connect&status=failed&reason=ref-invalid", ]); - $company = Company::where('company_key', $context["company_key"])->firstOrFail(); $account = $company->account; @@ -317,7 +257,6 @@ class NordigenController extends BaseController // Successfull Response => Redirect return response()->redirectTo($context["redirect"] . "?action=nordigen_connect&status=success&bank_integrations=" . implode(',', $bank_integration_ids)); - } /** @@ -386,8 +325,6 @@ class NordigenController extends BaseController }*/ public function institutions(Request $request) { - $account = auth()->user()->account; - if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) return response()->json(['message' => 'Not yet authenticated with Nordigen Bank Integration service'], 400); diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index b56dd6f08f5f..87e13a2d9203 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -105,7 +105,6 @@ class YodleeController extends BaseController }); } - /** * Process Yodlee Refresh Webhook. * diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 41a623835506..c2b35490ef24 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -347,8 +347,6 @@ class BankIntegrationController extends BaseController if (config("ninja.nordigen.secret_id") && config("ninja.nordigen.secret_key") && (Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) { $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->where('is_deleted', false)->cursor()->each(function ($bank_integration) { - Log::info($bank_integration); - (new ProcessBankTransactionsNordigen($bank_integration))->handle(); }); } diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index ff59383a1766..454c288236a4 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -120,7 +120,7 @@ class ProcessBankTransactionsNordigen implements ShouldQueue $this->bank_integration->disabled_upstream = true; $this->bank_integration->save(); $this->stop_loop = false; - Log::info("Nordigen: account inactive: " . $this->bank_integration->nordigen_account_id); + nlog("Nordigen: account inactive: " . $this->bank_integration->nordigen_account_id); // @turbo124 @todo send email for expired account return; } From 5b4dfe38fe4ac9bc3be1ff53974f3eab0204feec Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 18 Dec 2023 16:16:20 +0100 Subject: [PATCH 53/69] minor cleanup --- .../Controllers/Bank/NordigenController.php | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index f760ff4cfbb1..b64773dae62a 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -293,36 +293,6 @@ class NordigenController extends BaseController * ), * ) */ - - /* - { - "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 institutions(Request $request) { if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) From 54a8d13e280bf10eac31d53696652c7d324b7a3b Mon Sep 17 00:00:00 2001 From: paulwer Date: Mon, 18 Dec 2023 16:20:14 +0100 Subject: [PATCH 54/69] cleanup --- .../Nordigen/ConnectNordigenBankIntegrationRequest.php | 4 ---- app/Jobs/Bank/ProcessBankTransactionsNordigen.php | 5 ----- app/Jobs/Bank/ProcessBankTransactionsYodlee.php | 2 -- app/Jobs/Ninja/BankTransactionSync.php | 2 +- 4 files changed, 1 insertion(+), 12 deletions(-) diff --git a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php index f4834d05d385..b3c0ebf89be1 100644 --- a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php +++ b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php @@ -55,7 +55,6 @@ class ConnectNordigenBankIntegrationRequest extends Request $input["redirect"] = isset($context['is_react']) && $context['is_react'] ? config('ninja.react_url') : config('ninja.app_url'); $this->replace($input); - } } public function getTokenContent() @@ -74,15 +73,12 @@ class ConnectNordigenBankIntegrationRequest extends Request MultiDB::findAndSetDbByCompanyKey($this->getTokenContent()['company_key']); return User::findOrFail($this->getTokenContent()['user_id']); - } public function getCompany() { - MultiDB::findAndSetDbByCompanyKey($this->getTokenContent()['company_key']); return Company::where('company_key', $this->getTokenContent()['company_key'])->firstOrFail(); - } } diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index 454c288236a4..7862775c5bab 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -56,7 +56,6 @@ class ProcessBankTransactionsNordigen implements ShouldQueue */ public function handle() { - if ($this->bank_integration->integration_type != BankIntegration::INTEGRATION_TYPE_NORDIGEN) throw new \Exception("Invalid BankIntegration Type"); @@ -111,7 +110,6 @@ class ProcessBankTransactionsNordigen implements ShouldQueue // Perform Matching BankMatchingService::dispatch($this->company->id, $this->company->db); - } private function updateAccount() @@ -132,12 +130,10 @@ class ProcessBankTransactionsNordigen implements ShouldQueue $this->bank_integration->balance = $this->nordigen_account['current_balance']; $this->bank_integration->save(); - } private function processTransactions() { - //Get transaction count object $transactions = $this->nordigen->getTransactions($this->bank_integration->nordigen_account_id, $this->from_date); @@ -183,6 +179,5 @@ class ProcessBankTransactionsNordigen implements ShouldQueue $this->bank_integration->from_date = now()->subDays(5); $this->bank_integration->save(); - } } diff --git a/app/Jobs/Bank/ProcessBankTransactionsYodlee.php b/app/Jobs/Bank/ProcessBankTransactionsYodlee.php index 0d2122c6733c..17f7f61a5abb 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsYodlee.php +++ b/app/Jobs/Bank/ProcessBankTransactionsYodlee.php @@ -62,7 +62,6 @@ class ProcessBankTransactionsYodlee implements ShouldQueue */ public function handle() { - if ($this->bank_integration->integration_type != BankIntegration::INTEGRATION_TYPE_YODLEE) throw new \Exception("Invalid BankIntegration Type"); @@ -96,7 +95,6 @@ class ProcessBankTransactionsYodlee implements ShouldQueue private function processTransactions() { - $yodlee = new Yodlee($this->account->bank_integration_account_id); if (!$yodlee->getAccount($this->bank_integration->bank_account_id)) { diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php index 04a08f8531c8..cfe218d91c74 100644 --- a/app/Jobs/Ninja/BankTransactionSync.php +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -45,7 +45,7 @@ class BankTransactionSync implements ShouldQueue */ public function handle() { - //multiDB environment, need to + // multiDB environment, need to @turbo124 do we need any changes here for selfhosted non-multidb envs foreach (MultiDB::$dbs as $db) { MultiDB::setDB($db); From c43c85022b2df610e8f6e9d838cafeea72804c92 Mon Sep 17 00:00:00 2001 From: paulwer Date: Tue, 19 Dec 2023 08:37:04 +0100 Subject: [PATCH 55/69] reactivate softDeleted Accounts & revoke where deleted_at query - softDelete, when reconnecting to avoid double transactions - remove deleted_at query because unnecessary --- app/Http/Controllers/Bank/NordigenController.php | 7 +++---- app/Http/Controllers/Bank/YodleeController.php | 4 +--- app/Http/Controllers/BankIntegrationController.php | 12 ++++-------- app/Jobs/Ninja/BankTransactionSync.php | 4 ++-- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index b64773dae62a..a990b9a8d9fd 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -202,7 +202,7 @@ class NordigenController extends BaseController $nordigen_account = $nordigen->getAccount($nordigenAccountId); - $existing_bank_integration = BankIntegration::where('nordigen_account_id', $nordigen_account['id'])->where('company_id', $company->id)->first(); + $existing_bank_integration = BankIntegration::withTrashed()->where('nordigen_account_id', $nordigen_account['id'])->where('company_id', $company->id)->first(); if (!$existing_bank_integration) { @@ -237,6 +237,7 @@ class NordigenController extends BaseController $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(); @@ -246,10 +247,8 @@ class NordigenController extends BaseController } // perform update in background - $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->where('is_deleted', false)->each(function ($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 diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index 87e13a2d9203..6a180914ee94 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -98,10 +98,8 @@ class YodleeController extends BaseController } - $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->where('is_deleted', false)->each(function ($bank_integration) use ($company) { // TODO: filter to yodlee only - + $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->each(function ($bank_integration) use ($company) { // TODO: filter to yodlee only ProcessBankTransactionsYodlee::dispatch($company->account, $bank_integration); - }); } diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index c2b35490ef24..44f6fdde2f53 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -210,16 +210,12 @@ class BankIntegrationController extends BaseController // Processing transactions for each bank account if (Ninja::isHosted() && $user->account->bank_integration_account_id) $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->each(function ($bank_integration) use ($user_account) { - ProcessBankTransactionsYodlee::dispatch($user_account, $bank_integration); - }); if (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key') && (Ninja::isSelfHost() || (Ninja::isHosted() && $user_account->isPaid() && $user_account->plan == 'enterprise'))) $user_account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->each(function ($bank_integration) { - ProcessBankTransactionsNordigen::dispatch($bank_integration); - }); Cache::put("throttle_polling:{$user_account->key}", true, 300); @@ -272,9 +268,9 @@ class BankIntegrationController extends BaseController $nordigen = new Nordigen(); - BankIntegration::withTrashed()->where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->whereNotNull('nordigen_account_id')->each(function (BankIntegration $bank_integration) use ($nordigen) { + BankIntegration::where("integration_type", BankIntegration::INTEGRATION_TYPE_NORDIGEN)->whereNotNull('nordigen_account_id')->each(function (BankIntegration $bank_integration) use ($nordigen) { $account = $nordigen->getAccount($bank_integration->nordigen_account_id); - + Log::info($bank_integration); if (!$account) { $bank_integration->disabled_upstream = true; @@ -340,13 +336,13 @@ class BankIntegrationController extends BaseController $account = auth()->user()->account(); if (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise') { - $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->where('is_deleted', false)->cursor()->each(function ($bank_integration) use ($account) { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { (new ProcessBankTransactionsYodlee($account, $bank_integration))->handle(); }); } if (config("ninja.nordigen.secret_id") && config("ninja.nordigen.secret_key") && (Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) { - $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->where('is_deleted', false)->cursor()->each(function ($bank_integration) { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->cursor()->each(function ($bank_integration) { (new ProcessBankTransactionsNordigen($bank_integration))->handle(); }); } diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php index cfe218d91c74..93c8a4277c75 100644 --- a/app/Jobs/Ninja/BankTransactionSync.php +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -55,7 +55,7 @@ class BankTransactionSync implements ShouldQueue Account::with('bank_integrations')->whereNotNull('bank_integration_account_id')->cursor()->each(function ($account) { if ($account->isPaid() && $account->plan == 'enterprise') { - $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->where('is_deleted', false)->cursor()->each(function ($bank_integration) use ($account) { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account) { (new ProcessBankTransactionsYodlee($account, $bank_integration))->handle(); }); } @@ -69,7 +69,7 @@ class BankTransactionSync implements ShouldQueue Account::with('bank_integrations')->cursor()->each(function ($account) { if ((Ninja::isSelfHost() || (Ninja::isHosted() && $account->isPaid() && $account->plan == 'enterprise'))) { - $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->where('is_deleted', false)->cursor()->each(function ($bank_integration) { + $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->cursor()->each(function ($bank_integration) { (new ProcessBankTransactionsNordigen($bank_integration))->handle(); }); } From 1a68dcacea62d25971a45ed78c7c70a57849aadd Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 20 Dec 2023 12:22:24 +0100 Subject: [PATCH 56/69] fix https://github.com/invoiceninja/invoiceninja/pull/9004#discussion_r1432302185 --- app/Helpers/Bank/Nordigen/Nordigen.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Nordigen.php b/app/Helpers/Bank/Nordigen/Nordigen.php index 08e8563cc0ec..5c7185efc567 100644 --- a/app/Helpers/Bank/Nordigen/Nordigen.php +++ b/app/Helpers/Bank/Nordigen/Nordigen.php @@ -63,7 +63,14 @@ class Nordigen public function getRequisition(string $requisitionId) { - return $this->client->requisition->getRequisition($requisitionId); + try { + return $this->client->requisition->getRequisition($requisitionId); + } catch (\Exception $e) { + if (strpos($e->getMessage(), "Invalid Requisition ID") !== false) + return false; + + throw $e; + } } // TODO: return null on not found @@ -81,7 +88,7 @@ class Nordigen return $it->transform($out); } catch (\Exception $e) { if (strpos($e->getMessage(), "Invalid Account ID") !== false) - return null; + return false; throw $e; } From 2ad973359b2e14cdd447a8a9f96c10f997ab1f87 Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 20 Dec 2023 12:42:47 +0100 Subject: [PATCH 57/69] fix https://github.com/invoiceninja/invoiceninja/pull/9004#discussion_r1432302929 --- .../Transformer/TransactionTransformer.php | 106 +++++++++--------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php b/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php index 05350d87398b..6df825131224 100644 --- a/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php +++ b/app/Helpers/Bank/Nordigen/Transformer/TransactionTransformer.php @@ -64,73 +64,73 @@ use Log; class TransactionTransformer implements BankRevenueInterface { - use AppSetup; + use AppSetup; - public function transform($transactionResponse) - { - $data = []; + public function transform($transactionResponse) + { + $data = []; - if (!array_key_exists('transactions', $transactionResponse) || !array_key_exists('booked', $transactionResponse["transactions"])) - throw new \Exception('invalid dataset'); + if (!array_key_exists('transactions', $transactionResponse) || !array_key_exists('booked', $transactionResponse["transactions"])) + throw new \Exception('invalid dataset'); - foreach ($transactionResponse["transactions"]["booked"] as $transaction) { - $data[] = $this->transformTransaction($transaction); + foreach ($transactionResponse["transactions"]["booked"] as $transaction) { + $data[] = $this->transformTransaction($transaction); + } + + return $data; } - return $data; - } + public function transformTransaction($transaction) + { - public function transformTransaction($transaction) - { + if (!array_key_exists('transactionId', $transaction) || !array_key_exists('transactionAmount', $transaction)) + throw new \Exception('invalid dataset'); - if (!array_key_exists('transactionId', $transaction) || !array_key_exists('transactionAmount', $transaction)) - throw new \Exception('invalid dataset'); + // description could be in varios places + $description = ''; + if (array_key_exists('remittanceInformationStructured', $transaction)) + $description = $transaction["remittanceInformationStructured"]; + else if (array_key_exists('remittanceInformationStructuredArray', $transaction)) + $description = implode(' \r\n', $transaction["remittanceInformationStructuredArray"]); + else if (array_key_exists('remittanceInformationUnstructured', $transaction)) + $description = $transaction["remittanceInformationUnstructured"]; + else + Log::warning("Missing description for the following transaction: " . json_encode($transaction)); - // description could be in varios places - $description = ''; - if (array_key_exists('remittanceInformationStructured', $transaction)) - $description = $transaction["remittanceInformationStructured"]; - else if (array_key_exists('remittanceInformationStructuredArray', $transaction)) - $description = implode(' \r\n', $transaction["remittanceInformationStructuredArray"]); - else if (array_key_exists('remittanceInformationUnstructured', $transaction)) - $description = $transaction["remittanceInformationUnstructured"]; - else - Log::warning("Missing description for the following transaction: " . json_encode($transaction)); + return [ + 'transaction_id' => $transaction["transactionId"], + 'amount' => abs((int) $transaction["transactionAmount"]["amount"]), + 'currency_id' => $this->convertCurrency($transaction["transactionAmount"]["currency"]), + 'category_id' => null, // nordigen has no categories + 'category_type' => array_key_exists('additionalInformation', $transaction) ? $transaction["additionalInformation"] : '', // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc + 'date' => $transaction["bookingDate"], + 'description' => $description, + 'participant' => array_key_exists('debtorAccount', $transaction) && array_key_exists('iban', $transaction["debtorAccount"]) ? $transaction['debtorAccount']['iban'] : null, + 'participant_name' => array_key_exists('debtorName', $transaction) ? $transaction['debtorName'] : null, + 'base_type' => (int) $transaction["transactionAmount"]["amount"] <= 0 ? 'DEBIT' : 'CREDIT', + ]; - return [ - 'transaction_id' => $transaction["transactionId"], - 'amount' => abs((int) $transaction["transactionAmount"]["amount"]), - 'currency_id' => $this->convertCurrency($transaction["transactionAmount"]["currency"]), - 'category_id' => 0, // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc - 'category_type' => array_key_exists('additionalInformation', $transaction) ? $transaction["additionalInformation"] : '', // TODO: institution specific keys like: GUTSCHRIFT, ABSCHLUSS, MONATSABSCHLUSS etc - 'date' => $transaction["bookingDate"], - 'description' => $description, - 'participant' => array_key_exists('debtorAccount', $transaction) && array_key_exists('iban', $transaction["debtorAccount"]) ? $transaction['debtorAccount']['iban'] : null, - 'participant_name' => array_key_exists('debtorName', $transaction) ? $transaction['debtorName'] : null, - 'base_type' => (int) $transaction["transactionAmount"]["amount"] <= 0 ? 'DEBIT' : 'CREDIT', - ]; - - } - - private function convertCurrency(string $code) - { - - $currencies = Cache::get('currencies'); - - if (!$currencies) { - $this->buildCache(true); } - $currency = $currencies->filter(function ($item) use ($code) { - return $item->code == $code; - })->first(); + private function convertCurrency(string $code) + { - if ($currency) - return $currency->id; + $currencies = Cache::get('currencies'); - return 1; + if (!$currencies) { + $this->buildCache(true); + } - } + $currency = $currencies->filter(function ($item) use ($code) { + return $item->code == $code; + })->first(); + + if ($currency) + return $currency->id; + + return 1; + + } } From 44e8b88d2149adc1e25274e2c27165c02750be3b Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 20 Dec 2023 16:04:31 +0100 Subject: [PATCH 58/69] ignore comment on testing --- tests/Feature/Bank/YodleeApiTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index a2cb1b31eda1..246707832a27 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -166,7 +166,7 @@ class YodleeApiTest extends TestCase // foreach($accounts as $account) // { - // if(!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $this->company->id)->exists()) // TODO: maybe filter to only yodlee-integrations + // if(!BankIntegration::where('bank_account_id', $account['id'])->where('company_id', $this->company->id)->exists()) // { // $bank_integration = new BankIntegration(); // $bank_integration->company_id = $this->company->id; From e5fd758ca5c0b10d77ff52bf61ac29b935e2ff88 Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 20 Dec 2023 16:18:33 +0100 Subject: [PATCH 59/69] fix https://github.com/invoiceninja/invoiceninja/pull/9004#discussion_r1432308641 --- routes/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api.php b/routes/api.php index ea565393a676..6a27b8073b7b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -400,7 +400,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::get('statics', StaticController::class); // Route::post('apple_pay/upload_file','ApplyPayController::class, 'upload'); - Route::post('api/v1/yodlee/status/{account_number}', [YodleeController::class, 'accountStatus']); // @todo @turbo124 check route-path?! + Route::post('yodlee/status/{account_number}', [YodleeController::class, 'accountStatus']); // @todo @turbo124 check route-path?! Route::get('nordigen/institutions', [NordigenController::class, 'institutions'])->name('nordigen.institutions'); }); From cb311c52f8b8a71338b4082efc5ac2ea05e12a81 Mon Sep 17 00:00:00 2001 From: paulwer Date: Wed, 20 Dec 2023 16:42:29 +0100 Subject: [PATCH 60/69] cleanups --- .../Controllers/Bank/NordigenController.php | 4 ++-- .../ConfirmNordigenBankIntegrationRequest.php | 20 +++++++++++++++++++ .../ConnectNordigenBankIntegrationRequest.php | 11 ++-------- .../views/bank/nordigen/handler.blade.php | 2 -- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/app/Http/Controllers/Bank/NordigenController.php b/app/Http/Controllers/Bank/NordigenController.php index a990b9a8d9fd..f7f5f8e56d87 100644 --- a/app/Http/Controllers/Bank/NordigenController.php +++ b/app/Http/Controllers/Bank/NordigenController.php @@ -135,9 +135,9 @@ class NordigenController extends BaseController public function confirm(ConfirmNordigenBankIntegrationRequest $request) { $data = $request->all(); + $context = $request->getTokenContent(); $lang = $data['lang'] ?? 'en'; - $context = Cache::get($data["ref"]); if (!$context || $context["context"] != "nordigen" || !array_key_exists("requisitionId", $context)) return view('bank.nordigen.handler', [ 'lang' => $lang, @@ -145,7 +145,7 @@ class NordigenController extends BaseController "redirectUrl" => ($context && array_key_exists("redirect", $context) ? $context["redirect"] : config('ninja.app_url')) . "?action=nordigen_connect&status=failed&reason=ref-invalid", ]); - $company = Company::where('company_key', $context["company_key"])->firstOrFail(); + $company = $request->getCompany(); $account = $company->account; if (!(config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key'))) diff --git a/app/Http/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php b/app/Http/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php index a529b46ed1cf..12c812669640 100644 --- a/app/Http/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php +++ b/app/Http/Requests/Nordigen/ConfirmNordigenBankIntegrationRequest.php @@ -12,6 +12,9 @@ namespace App\Http\Requests\Nordigen; use App\Http\Requests\Request; +use App\Libraries\MultiDB; +use App\Models\Company; +use Cache; class ConfirmNordigenBankIntegrationRequest extends Request { @@ -37,4 +40,21 @@ class ConfirmNordigenBankIntegrationRequest extends Request 'lang' => 'string', ]; } + public function getTokenContent() + { + if ($this->state) { + $this->token = $this->state; + } + + $data = Cache::get($this->token); + + return $data; + } + + public function getCompany() + { + MultiDB::findAndSetDbByCompanyKey($this->getTokenContent()['company_key']); + + 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 b3c0ebf89be1..bcf87a93b997 100644 --- a/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php +++ b/app/Http/Requests/Nordigen/ConnectNordigenBankIntegrationRequest.php @@ -40,7 +40,7 @@ class ConnectNordigenBankIntegrationRequest extends Request return [ 'lang' => 'string', 'institution_id' => 'string', - 'redirect' => 'string', // TODO: @turbo124 @todo validate, that this is a url without / at the end + 'redirect' => 'string', ]; } @@ -52,7 +52,7 @@ class ConnectNordigenBankIntegrationRequest extends Request if (!array_key_exists('redirect', $input)) { $context = $this->getTokenContent(); - $input["redirect"] = isset($context['is_react']) && $context['is_react'] ? config('ninja.react_url') : config('ninja.app_url'); + $input["redirect"] = isset($context["is_react"]) && $context['is_react'] ? redirect(config('ninja.react_url') . "/#/settings/bank_accounts") : redirect(config('ninja.app_url')); $this->replace($input); } @@ -68,13 +68,6 @@ class ConnectNordigenBankIntegrationRequest extends Request return $data; } - public function getContact() - { - MultiDB::findAndSetDbByCompanyKey($this->getTokenContent()['company_key']); - - return User::findOrFail($this->getTokenContent()['user_id']); - } - public function getCompany() { MultiDB::findAndSetDbByCompanyKey($this->getTokenContent()['company_key']); diff --git a/resources/views/bank/nordigen/handler.blade.php b/resources/views/bank/nordigen/handler.blade.php index 6795d4344bdc..309a401e7263 100644 --- a/resources/views/bank/nordigen/handler.blade.php +++ b/resources/views/bank/nordigen/handler.blade.php @@ -18,8 +18,6 @@