wip: changes

This commit is contained in:
paulwer 2023-12-01 14:30:33 +01:00
parent ef48bd150c
commit b54b626332
17 changed files with 1172 additions and 833 deletions

View File

@ -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();
}
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();
}
return $this->client->account($accountId)->getAccountTransactions($dateFrom);
}
}

View File

@ -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);
}
@ -84,19 +107,27 @@ 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 : '',
];
}
}

View File

@ -9,7 +9,7 @@
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Helpers\Bank\Yodlee\Transformer;
namespace App\Helpers\Bank\Nordigen\Transformer;
/**
"date": "string",

View File

@ -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;
@ -125,8 +125,7 @@ class IncomeTransformer implements BankRevenueInterface
if (!property_exists($transaction, 'transaction'))
return $data;
foreach($transaction->transaction as $transaction)
{
foreach ($transaction->transaction as $transaction) {
$data[] = $this->transformTransaction($transaction);
}

View File

@ -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);
});

View File

@ -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;
@ -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,7 +49,7 @@ 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();
@ -81,11 +80,9 @@ class YodleeController extends BaseController
$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;
@ -110,7 +107,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);
ProcessBankTransactionsYodlee::dispatch($company->account, $bank_integration);
});

View File

@ -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;
@ -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();
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;
@ -554,24 +579,42 @@ class BankIntegrationController extends BaseController
$bank_integration->currency = $account['account_currency'];
$bank_integration->save();
}
}
}
private function refreshAccountsNordigen(Account $account)
{
$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);
if(Cache::get("throttle_polling:{$account->key}"))
return response()->json(BankIntegration::query()->company(), 200);
$nordigen = new Nordigen($account->bank_integration_nordigen_client_id, $account->bank_integration_nordigen_client_secret);
$account->bank_integrations->each(function ($bank_integration) use ($account){
$accounts = $nordigen->getAccounts();
ProcessBankTransactions::dispatch($account->bank_integration_account_id, $bank_integration);
foreach ($accounts as $account) {
});
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'];
Cache::put("throttle_polling:{$account->key}", true, 300);
$bank_integration->save();
}
return response()->json(BankIntegration::query()->company(), 200);
}
}
/**
@ -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,10 +728,17 @@ 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 ProcessBankTransactionsYodlee(auth()->user()->account, $bank_integration))->handle();
(new ProcessBankTransactions(auth()->user()->account->bank_integration_account_id, $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();
});

View File

@ -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,8 +94,8 @@ 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;
@ -104,13 +105,11 @@ class MatchBankTransactions implements ShouldQueue
$_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)
@ -133,8 +132,7 @@ class MatchBankTransactions implements ShouldQueue
$invoices = explode(",", $invoice_hashed_ids);
if(count($invoices) >= 1)
{
if (count($invoices) >= 1) {
foreach ($invoices as $invoice) {
@ -290,21 +288,17 @@ class MatchBankTransactions implements ShouldQueue
$_amount = false;
if(floatval($this->invoice->balance) < floatval($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)
{
} 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];
@ -351,8 +345,7 @@ class MatchBankTransactions implements ShouldQueue
$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'],
@ -410,8 +403,7 @@ class MatchBankTransactions implements ShouldQueue
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;

View File

@ -0,0 +1,166 @@
<?php
/**
* Credit Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Credit Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Jobs\Bank;
use App\Helpers\Bank\Nordigen\Nordigen;
use App\Libraries\MultiDB;
use App\Models\Account;
use App\Models\BankIntegration;
use App\Models\BankTransaction;
use App\Models\Company;
use App\Services\Bank\BankMatchingService;
use Illuminate\Bus\Queueable;
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
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private Account $account;
private BankIntegration $bank_integration;
private ?string $from_date;
private bool $stop_loop = true;
private int $skip = 0;
public Company $company;
/**
* Create a new job instance.
*/
public function __construct(Account $account, BankIntegration $bank_integration)
{
$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_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();
}
}
}

View File

@ -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");
}
/**
@ -70,9 +75,8 @@ class ProcessBankTransactions implements ShouldQueue
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;
}
@ -87,10 +91,9 @@ 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))
{
if (!$yodlee->getAccount($this->bank_integration->bank_account_id)) {
$this->bank_integration->disabled_upstream = true;
$this->bank_integration->save();
$this->stop_loop = false;
@ -135,8 +138,7 @@ class ProcessBankTransactions implements ShouldQueue
$now = now();
foreach($transactions as $transaction)
{
foreach ($transactions as $transaction) {
if (BankTransaction::where('transaction_id', $transaction['transaction_id'])->where('company_id', $this->company->id)->withTrashed()->exists())
continue;

View File

@ -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");
}
}

View File

@ -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',
];
/**
@ -397,8 +399,7 @@ class Account extends BaseModel
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;
}
@ -443,8 +444,7 @@ class Account extends BaseModel
return true;
}
}
catch(\Exception $e){
} catch (\Exception $e) {
\Sentry\captureMessage("I encountered an error with email quotas for account {$this->key} - defaulting to SEND");
}
@ -483,8 +483,7 @@ class Account extends BaseModel
return true;
}
catch(\Exception $e){
} catch (\Exception $e) {
\Sentry\captureMessage("I encountered an error with sending with gmail for account {$this->key}");
}
@ -515,7 +514,8 @@ class Account extends BaseModel
$diff = $plan_expires->diffInDays();
if($diff > 14);
if ($diff > 14)
;
return 0;
return $diff;

View File

@ -22,7 +22,6 @@ class BankIntegration extends BaseModel
use Excludable;
protected $fillable = [
'integration_type',
'bank_account_name',
'provider_name',
'bank_account_number',

View File

@ -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(),

View File

@ -1,5 +1,6 @@
<?php
use App\Models\Account;
use App\Models\BankIntegration;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
@ -13,15 +14,22 @@ return new class extends Migration {
*/
public function up()
{
Schema::table('bank_integration', function (Blueprint $table) {
$table->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');
});
//
}
};

View File

@ -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"
}

View File

@ -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;
@ -78,7 +78,7 @@ class YodleeApiTest extends TestCase
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]);
@ -189,7 +189,7 @@ class YodleeApiTest extends TestCase
// $bank_integration->save();
// ProcessBankTransactions::dispatchSync('sbMem62e1e69547bfb1', $bank_integration);
// ProcessBankTransactionsYodlee::dispatchSync('sbMem62e1e69547bfb1', $bank_integration);
// }
// }