From f6821e9fcc2e48a27940f8c2ada47f2f016a412d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 28 Jul 2022 14:09:13 +1000 Subject: [PATCH 001/169] Yodlee init --- app/Helpers/Bank/BankExpenseInterface.php | 17 +++++ app/Helpers/Bank/BankRevenueInterface.php | 17 +++++ app/Helpers/Bank/Yodlee/Yodlee.php | 22 ++++++ .../Controllers/Bank/YodleeController.php | 33 +++++++++ app/Http/Controllers/ImportController.php | 2 +- app/Http/Middleware/QueryLogging.php | 7 +- .../Requests/Yodlee/YodleeAuthRequest.php | 70 +++++++++++++++++++ app/Repositories/BaseRepository.php | 4 +- resources/views/bank/yodlee/auth.blade.php | 58 +++++++++++++++ routes/web.php | 3 + 10 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 app/Helpers/Bank/BankExpenseInterface.php create mode 100644 app/Helpers/Bank/BankRevenueInterface.php create mode 100644 app/Helpers/Bank/Yodlee/Yodlee.php create mode 100644 app/Http/Controllers/Bank/YodleeController.php create mode 100644 app/Http/Requests/Yodlee/YodleeAuthRequest.php create mode 100644 resources/views/bank/yodlee/auth.blade.php diff --git a/app/Helpers/Bank/BankExpenseInterface.php b/app/Helpers/Bank/BankExpenseInterface.php new file mode 100644 index 000000000000..dcd26285d80b --- /dev/null +++ b/app/Helpers/Bank/BankExpenseInterface.php @@ -0,0 +1,17 @@ + $yodlee->getAccessToken() + ]; + + return view('bank.yodlee.auth', $data); + + } + +} diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index 6447ae292375..4b84997bf602 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -105,7 +105,7 @@ class ImportController extends Controller public function import(ImportRequest $request) { $data = $request->all(); -nlog($data); + if (empty($data['hash'])) { // Create a reference $data['hash'] = $hash = Str::random(32); diff --git a/app/Http/Middleware/QueryLogging.php b/app/Http/Middleware/QueryLogging.php index ee85701612a3..b8ed33bc94b3 100644 --- a/app/Http/Middleware/QueryLogging.php +++ b/app/Http/Middleware/QueryLogging.php @@ -35,9 +35,9 @@ class QueryLogging { // Enable query logging for development - if (! Ninja::isHosted() || ! config('beacon.enabled')) { - return $next($request); - } + // if (! Ninja::isHosted() || ! config('beacon.enabled')) { + // return $next($request); + // } $timeStart = microtime(true); DB::enableQueryLog(); @@ -52,6 +52,7 @@ class QueryLogging nlog("Query count = {$count}"); nlog($queries); + nlog($request->url()); if ($count > 175) { nlog("Query count = {$count}"); diff --git a/app/Http/Requests/Yodlee/YodleeAuthRequest.php b/app/Http/Requests/Yodlee/YodleeAuthRequest.php new file mode 100644 index 000000000000..bda7259e4e06 --- /dev/null +++ b/app/Http/Requests/Yodlee/YodleeAuthRequest.php @@ -0,0 +1,70 @@ +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(); + + } +} \ No newline at end of file diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php index 3d1f623e5d0f..56be71528527 100644 --- a/app/Repositories/BaseRepository.php +++ b/app/Repositories/BaseRepository.php @@ -142,7 +142,7 @@ class BaseRepository $invitation_class = sprintf('App\\Models\\%sInvitation', $resource); - $invitation = $invitation_class::where('key', $invitation['key'])->first(); + $invitation = $invitation_class::with('company')->where('key', $invitation['key'])->first(); return $invitation; } @@ -176,7 +176,7 @@ class BaseRepository if(array_key_exists('client_id', $data)) $model->client_id = $data['client_id']; - $client = Client::where('id', $model->client_id)->withTrashed()->firstOrFail(); + $client = Client::with('group_settings')->where('id', $model->client_id)->withTrashed()->firstOrFail(); $state = []; diff --git a/resources/views/bank/yodlee/auth.blade.php b/resources/views/bank/yodlee/auth.blade.php new file mode 100644 index 000000000000..1abc1ea503f7 --- /dev/null +++ b/resources/views/bank/yodlee/auth.blade.php @@ -0,0 +1,58 @@ +@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/web.php b/routes/web.php index 83ad812b4f1b..fc04c0a72f0f 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\YodleeController; use App\Http\Controllers\BaseController; use App\Http\Controllers\ClientPortal\ApplePayDomainController; use App\Http\Controllers\Gateways\Checkout3dsController; @@ -52,6 +53,8 @@ Route::middleware('url_db')->group(function () { Route::get('stripe/signup/{token}', [StripeConnectController::class, 'initialize'])->name('stripe_connect.initialization'); Route::get('stripe/completed', [StripeConnectController::class, 'completed'])->name('stripe_connect.return'); +Route::get('yodlee/onboard/{token}', [YodleeController::class, 'auth'])->name('yodlee.auth'); + 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'); From d98fd30add805df28ad391f838c092106a985cfc Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 28 Jul 2022 15:07:35 +1000 Subject: [PATCH 002/169] Auth tokens --- app/Helpers/Bank/Yodlee/Yodlee.php | 67 +++++++++++++++++++++++++++- config/ninja.php | 6 ++- tests/Feature/Bank/YodleeApiTest.php | 40 +++++++++++++++++ 3 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 tests/Feature/Bank/YodleeApiTest.php diff --git a/app/Helpers/Bank/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index 5dc13ed34a87..1a4b47692629 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -9,13 +9,78 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\Helpers\Yodlee; +namespace App\Helpers\Bank\Yodlee; +use Illuminate\Support\Facades\Http; + class Yodlee { + public bool $test_mode; + + private string $api_endpoint = ''; + + private string $test_api_endpoint = 'https://sandbox.api.yodlee.com/ysl'; + + protected string $fast_track_url = 'https://fl4.sandbox.yodlee.com/authenticate/restserver/fastlink'; + + protected string $client_id; + + protected string $client_secret; + + protected string $admin_name; + + public function __construct(bool $test_mode = false) + { + $this->test_mode = $test_mode; + + if($this->test_mode) + $this->api_endpoint = $this->test_api_endpoint; + + $this->client_id = config('ninja.yodlee.client_id'); + + $this->client_secret = config('ninja.yodlee.client_secret'); + + $this->admin_name = config('ninja.yodlee.admin_name'); + + } + public function getAccessToken() { + return $this->bankRequest('/auth/token', 'post'); + } + + private function bankRequest(string $uri, string $verb, array $data = []) + { + nlog($this->getHeaders()); + nlog($this->buildBody()); + nlog($this->api_endpoint . $uri); + + $response = Http::withHeaders($this->getHeaders())->asForm()->{$verb}($this->api_endpoint . $uri, $this->buildBody()); + + if($response->successful()) + return $response->object(); + + if($response->failed()) + return $response->body(); + + } + + private function getHeaders($data = []) + { + return array_merge($data, [ + 'Api-Version' => '1.1', + 'loginName' => $this->admin_name + ]); + } + + private function buildBody() + { + + return [ + 'clientId' => $this->client_id, + 'secret' => $this->client_secret, + ]; } diff --git a/config/ninja.php b/config/ninja.php index d0670923e0b2..d6ad129dac02 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -200,5 +200,9 @@ return [ 'twilio_account_sid' => env('TWILIO_ACCOUNT_SID',false), 'twilio_auth_token' => env('TWILIO_AUTH_TOKEN',false), 'twilio_verify_sid' => env('TWILIO_VERIFY_SID',false), - + 'yodlee' => [ + 'client_id' => env('YODLEE_CLIENT_ID',false), + 'client_secret' => env('YODLEE_CLIENT_SECRET', false), + 'admin_name' => env('YODLEE_LOGIN_ADMIN_NAME', false), + ], ]; diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php new file mode 100644 index 000000000000..12b7289df5db --- /dev/null +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -0,0 +1,40 @@ +getAccessToken(); + + nlog($access_token); + + $this->assertNotNull($access_token); + } + +} From b44e1a02ca3755028215ebf1352b89c68039ff60 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 28 Jul 2022 16:29:42 +1000 Subject: [PATCH 003/169] Auth tokens --- app/Helpers/Bank/Yodlee/Yodlee.php | 74 +++++++++++++++++-- .../Controllers/Bank/YodleeController.php | 6 +- resources/views/bank/yodlee/auth.blade.php | 4 +- tests/Feature/Bank/YodleeApiTest.php | 10 +++ 4 files changed, 83 insertions(+), 11 deletions(-) diff --git a/app/Helpers/Bank/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index 1a4b47692629..1f09fd1052ef 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -22,7 +22,7 @@ class Yodlee private string $test_api_endpoint = 'https://sandbox.api.yodlee.com/ysl'; - protected string $fast_track_url = 'https://fl4.sandbox.yodlee.com/authenticate/restserver/fastlink'; + public string $fast_track_url = 'https://fl4.sandbox.yodlee.com/authenticate/restserver/fastlink'; protected string $client_id; @@ -47,16 +47,68 @@ class Yodlee public function getAccessToken() { - return $this->bankRequest('/auth/token', 'post'); + $response = $this->bankFormRequest('/auth/token', 'post'); + + return $response->token->accessToken; + } + + + public function createUser() + { + // { + // "user": { + // "preferences": { + // "dateFormat": "string", + // "timeZone": "string", + // "currency": "USD", + // "locale": "en_US" + // }, + // "address": { + // "zip": "string", + // "country": "string", + // "address3": "string", + // "address2": "string", + // "city": "string", + // "address1": "string", + // "state": "string" + // }, + // "loginName": "string", + // "name": { + // "middle": "string", + // "last": "string", + // "fullName": "string", + // "first": "string" + // }, + // "email": "string", + // "segmentName": "string" + // } + // } + + $user['user'] = [ + 'loginName' => 'test123', + ]; + + return $this->bankRequest('/user/register', 'post', $user); + } private function bankRequest(string $uri, string $verb, array $data = []) { - nlog($this->getHeaders()); - nlog($this->buildBody()); - nlog($this->api_endpoint . $uri); - $response = Http::withHeaders($this->getHeaders())->asForm()->{$verb}($this->api_endpoint . $uri, $this->buildBody()); + $response = Http::withHeaders($this->getHeaders(['loginName' => $this->admin_name]))->{$verb}($this->api_endpoint . $uri, $this->buildBody()); + + if($response->successful()) + return $response->object(); + + if($response->failed()) + return $response->body(); + + } + + private function bankFormRequest(string $uri, string $verb, array $data = []) + { + + $response = Http::withHeaders($this->getFormHeaders(['loginName' => $this->admin_name]))->asForm()->{$verb}($this->api_endpoint . $uri, $this->buildBody()); if($response->successful()) return $response->object(); @@ -70,7 +122,15 @@ class Yodlee { return array_merge($data, [ 'Api-Version' => '1.1', - 'loginName' => $this->admin_name + 'ContentType' => 'application/json' + ]); + } + + + private function getFormHeaders($data = []) + { + return array_merge($data, [ + 'Api-Version' => '1.1', ]); } diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index ea898994686c..26cd5f7c994e 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -11,6 +11,7 @@ namespace App\Http\Controllers\Bank; +use App\Helpers\Bank\Yodlee\Yodlee; use App\Http\Controllers\BaseController; use Illuminate\Http\Request; @@ -20,10 +21,11 @@ class YodleeController extends BaseController public function auth(Request $request) { - $yodlee = new Yodlee(); + $yodlee = new Yodlee(true); $data = [ - 'access_token' => $yodlee->getAccessToken() + 'access_token' => $yodlee->getAccessToken(), + 'fasttrack_url' => $yodlee->fast_track_url ]; return view('bank.yodlee.auth', $data); diff --git a/resources/views/bank/yodlee/auth.blade.php b/resources/views/bank/yodlee/auth.blade.php index 1abc1ea503f7..3cda43d55b0b 100644 --- a/resources/views/bank/yodlee/auth.blade.php +++ b/resources/views/bank/yodlee/auth.blade.php @@ -27,10 +27,10 @@ 'click', function() { window.fastlink.open({ - fastLinkURL: 'https://fl4.sandbox.yodlee.com/authenticate/restserver/fastlink', + fastLinkURL: '{{ $fasttrack_url }}', accessToken: 'Bearer {{ $access_token }}', params: { - configName : '' + configName : 'Example2' }, onSuccess: function (data) { // will be called on success. For list of possible message, refer to onSuccess(data) Method. diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index 12b7289df5db..dd3f9446223d 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -37,4 +37,14 @@ class YodleeApiTest extends TestCase $this->assertNotNull($access_token); } + public function testCreateUser() + { + $yodlee = new Yodlee(true); + + $create_user = $yodlee->createUser(); + + nlog($create_user); + + } + } From 15b53ca6175c96506591b6fa67167714f8e60f0f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 28 Jul 2022 17:33:47 +1000 Subject: [PATCH 004/169] Stubs for yodlee --- app/Helpers/Bank/Yodlee/Yodlee.php | 57 +++++++------------ .../Controllers/Bank/YodleeController.php | 2 +- resources/views/bank/yodlee/auth.blade.php | 8 ++- tests/Feature/Bank/YodleeApiTest.php | 29 ++++++++-- 4 files changed, 54 insertions(+), 42 deletions(-) diff --git a/app/Helpers/Bank/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index 1f09fd1052ef..db9ea1e2370f 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -45,9 +45,12 @@ class Yodlee } - public function getAccessToken() + public function getAccessToken($user = false) { - $response = $this->bankFormRequest('/auth/token', 'post'); + if(!$user) + $user = $this->admin_name; + + $response = $this->bankFormRequest('/auth/token', 'post', [], ['loginName' => $user]); return $response->token->accessToken; } @@ -55,47 +58,31 @@ class Yodlee public function createUser() { - // { - // "user": { - // "preferences": { - // "dateFormat": "string", - // "timeZone": "string", - // "currency": "USD", - // "locale": "en_US" - // }, - // "address": { - // "zip": "string", - // "country": "string", - // "address3": "string", - // "address2": "string", - // "city": "string", - // "address1": "string", - // "state": "string" - // }, - // "loginName": "string", - // "name": { - // "middle": "string", - // "last": "string", - // "fullName": "string", - // "first": "string" - // }, - // "email": "string", - // "segmentName": "string" - // } - // } + + $token = $this->getAccessToken(); $user['user'] = [ 'loginName' => 'test123', ]; - return $this->bankRequest('/user/register', 'post', $user); + return $this->bankRequest('/user/register', 'post', $user, ['Authorization' => $token]); } - private function bankRequest(string $uri, string $verb, array $data = []) + public function getAccounts($token) { - $response = Http::withHeaders($this->getHeaders(['loginName' => $this->admin_name]))->{$verb}($this->api_endpoint . $uri, $this->buildBody()); + $response = $this->bankRequest('/accounts', 'get', [], ['Authorization' => $token]); + + return $response; + + } + + + private function bankRequest(string $uri, string $verb, array $data = [], array $headers = []) + { + + $response = Http::withHeaders($this->getHeaders($headers))->{$verb}($this->api_endpoint . $uri, $this->buildBody()); if($response->successful()) return $response->object(); @@ -105,10 +92,10 @@ class Yodlee } - private function bankFormRequest(string $uri, string $verb, array $data = []) + private function bankFormRequest(string $uri, string $verb, array $data = [], array $headers) { - $response = Http::withHeaders($this->getFormHeaders(['loginName' => $this->admin_name]))->asForm()->{$verb}($this->api_endpoint . $uri, $this->buildBody()); + $response = Http::withHeaders($this->getFormHeaders($headers))->asForm()->{$verb}($this->api_endpoint . $uri, $this->buildBody()); if($response->successful()) return $response->object(); diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index 26cd5f7c994e..ead225b706dd 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -24,7 +24,7 @@ class YodleeController extends BaseController $yodlee = new Yodlee(true); $data = [ - 'access_token' => $yodlee->getAccessToken(), + 'access_token' => $yodlee->getAccessToken('sbMem62e1e69547bfb1'), 'fasttrack_url' => $yodlee->fast_track_url ]; diff --git a/resources/views/bank/yodlee/auth.blade.php b/resources/views/bank/yodlee/auth.blade.php index 3cda43d55b0b..66002fffdcec 100644 --- a/resources/views/bank/yodlee/auth.blade.php +++ b/resources/views/bank/yodlee/auth.blade.php @@ -30,22 +30,28 @@ fastLinkURL: '{{ $fasttrack_url }}', accessToken: 'Bearer {{ $access_token }}', params: { - configName : 'Example2' + configName : 'Aggregation' }, onSuccess: function (data) { // will be called on success. For list of possible message, refer to onSuccess(data) Method. + console.log('success'); console.log(data); }, onError: function (data) { // will be called on error. For list of possible message, refer to onError(data) Method. + console.log('error'); + console.log(data); }, onClose: function (data) { // will be called called to close FastLink. For list of possible message, refer to onClose(data) Method. + console.log('onclose'); console.log(data); }, onEvent: function (data) { // will be called on intermittent status update. + console.log('on event'); + console.log(data); } }, diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index dd3f9446223d..ee20bbf43da3 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -23,6 +23,9 @@ class YodleeApiTest extends TestCase protected function setUp(): void { parent::setUp(); + + $this->markTestSkipped('Skip test no company gateways installed'); + } public function testAccessTokenGeneration() @@ -30,21 +33,37 @@ class YodleeApiTest extends TestCase $yodlee = new Yodlee(true); - $access_token = $yodlee->getAccessToken(); + $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb1'); nlog($access_token); $this->assertNotNull($access_token); } - public function testCreateUser() + public function testGetAccounts() { + $yodlee = new Yodlee(true); - $create_user = $yodlee->createUser(); + // $data = [ + // 'providerAccountId' => 11308693, + // ]; + + $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb1'); - nlog($create_user); - + $accounts = $yodlee->getAccounts($access_token); + + nlog($accounts); } + // public function testCreateUser() + // { + // $yodlee = new Yodlee(true); + + // $create_user = $yodlee->createUser(); + + // nlog($create_user); + + // } + } From 3e84dbc357beaf0f535acbaeac0ed650cb552985 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 30 Jul 2022 11:43:24 +1000 Subject: [PATCH 005/169] fixes for authorizatoin --- app/Helpers/Bank/Yodlee/Yodlee.php | 2 +- tests/Feature/Bank/YodleeApiTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Helpers/Bank/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index db9ea1e2370f..c2c6defd5670 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -72,7 +72,7 @@ class Yodlee public function getAccounts($token) { - $response = $this->bankRequest('/accounts', 'get', [], ['Authorization' => $token]); + $response = $this->bankRequest('/accounts', 'get', [], ["Authorization" => "Bearer {$token}"]); return $response; diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index ee20bbf43da3..9aad26050546 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -24,7 +24,7 @@ class YodleeApiTest extends TestCase { parent::setUp(); - $this->markTestSkipped('Skip test no company gateways installed'); + // $this->markTestSkipped('Skip test no company gateways installed'); } From a2b89d14761efd911d03eb73c00d9d12f0fabc15 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 30 Jul 2022 12:10:42 +1000 Subject: [PATCH 006/169] Transactions --- app/Helpers/Bank/Yodlee/Yodlee.php | 25 +++++++++++++++++++++ tests/Feature/Bank/YodleeApiTest.php | 33 +++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/app/Helpers/Bank/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index c2c6defd5670..9dcee2cad7be 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -78,6 +78,31 @@ class Yodlee } + public function getTransactions($token) + { + + //$response = $this->bankRequest('/transactions', 'get', ['categoryId' => '2xx', 'fromDate' => '20xx22-07-29'], ); + + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions", ['categoryId' => '2']); + + + if($response->successful()) + return $response->object(); + + if($response->failed()) + return $response->body(); + + } + + public function getTransactionCategories($token) + { + + $response = $this->bankRequest('/transactions/categories', 'get', [], ["Authorization" => "Bearer {$token}"]); + + return $response; + + } + private function bankRequest(string $uri, string $verb, array $data = [], array $headers = []) { diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index 9aad26050546..099efbd27eb7 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -40,20 +40,43 @@ class YodleeApiTest extends TestCase $this->assertNotNull($access_token); } + public function testGetCategories() + { + + + $yodlee = new Yodlee(true); + + $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb1'); + + $transactions = $yodlee->getTransactionCategories($access_token); + +// nlog($transactions); + + } + public function testGetAccounts() { $yodlee = new Yodlee(true); - - // $data = [ - // 'providerAccountId' => 11308693, - // ]; $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb1'); $accounts = $yodlee->getAccounts($access_token); - nlog($accounts); + // nlog($accounts); + } + + public function testGetTransactions() + { + + $yodlee = new Yodlee(true); + + $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb1'); + + $transactions = $yodlee->getTransactions($access_token); + + nlog($transactions); + } // public function testCreateUser() From 8929334e085aff9558f8d7bfaa9f39c3d3b9f492 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 30 Jul 2022 14:38:21 +1000 Subject: [PATCH 007/169] Transactions --- tests/Feature/Bank/YodleeApiTest.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index 099efbd27eb7..7d43ce711b0b 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -79,14 +79,5 @@ class YodleeApiTest extends TestCase } - // public function testCreateUser() - // { - // $yodlee = new Yodlee(true); - - // $create_user = $yodlee->createUser(); - - // nlog($create_user); - - // } } From 1506323ff7a53110302fffdb2a874eb82d8f404c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 5 Aug 2022 11:28:29 +1000 Subject: [PATCH 008/169] Merge v5 --- app/Http/Middleware/QueryLogging.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Middleware/QueryLogging.php b/app/Http/Middleware/QueryLogging.php index b8ed33bc94b3..6487821cca6b 100644 --- a/app/Http/Middleware/QueryLogging.php +++ b/app/Http/Middleware/QueryLogging.php @@ -35,9 +35,9 @@ class QueryLogging { // Enable query logging for development - // if (! Ninja::isHosted() || ! config('beacon.enabled')) { - // return $next($request); - // } + if (! Ninja::isHosted() || ! config('beacon.enabled')) { + return $next($request); + } $timeStart = microtime(true); DB::enableQueryLog(); From e41503a5f5926fa6c9fa00a22a9a3e05addca31e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 5 Aug 2022 11:37:31 +1000 Subject: [PATCH 009/169] Test yodlee endpoints --- app/Helpers/Bank/Yodlee/Yodlee.php | 5 +- tests/Feature/Bank/YodleeApiTest.php | 276 ++++++++++++++++++++++++++- 2 files changed, 276 insertions(+), 5 deletions(-) diff --git a/app/Helpers/Bank/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index 9dcee2cad7be..c4a4b5e3eafc 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -80,10 +80,9 @@ class Yodlee public function getTransactions($token) { - - //$response = $this->bankRequest('/transactions', 'get', ['categoryId' => '2xx', 'fromDate' => '20xx22-07-29'], ); - $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions", ['categoryId' => '2']); + // $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions", ['categoryId' => '2']); + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions"); if($response->successful()) diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index 7d43ce711b0b..5b0e47f1210e 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -35,11 +35,186 @@ class YodleeApiTest extends TestCase $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb1'); - nlog($access_token); + // nlog($access_token); $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 + ) + + [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 + ) + + [2] => stdClass Object + ( + [id] => 1218 + [name] => Auto Fees/Penalties + ) + + [3] => stdClass Object + ( + [id] => 1260 + [name] => Car Appraisers + ) + + [4] => stdClass Object + ( + [id] => 1261 + [name] => Car Dealers + ) + + [5] => stdClass Object + ( + [id] => 1262 + [name] => Car Dealers and Leasing + ) + + [6] => stdClass Object + ( + [id] => 1263 + [name] => Car Parts and Accessories + ) + + [7] => stdClass Object + ( + [id] => 1264 + [name] => Car Wash and Detail + ) + + [8] => stdClass Object + ( + [id] => 1265 + [name] => Classic and Antique Car + ) + + [9] => stdClass Object + ( + [id] => 1267 + [name] => Maintenance and Repair + ) + + [10] => stdClass Object + ( + [id] => 1268 + [name] => Motorcycles/Mopeds/Scooters + ) + + [11] => stdClass Object + ( + [id] => 1269 + [name] => Oil and Lube + ) + + [12] => stdClass Object + ( + [id] => 1270 + [name] => Motorcycle Repair + ) + + [13] => stdClass Object + ( + [id] => 1271 + [name] => RVs and Motor Homes + ) + + [14] => stdClass Object + ( + [id] => 1272 + [name] => Motorcycle Sales + ) + + [15] => stdClass Object + ( + [id] => 1273 + [name] => Salvage Yards + ) + + [16] => stdClass Object + ( + [id] => 1274 + [name] => Smog Check + ) + + [17] => stdClass Object + ( + [id] => 1275 + [name] => Tires + ) + + [18] => stdClass Object + ( + [id] => 1276 + [name] => Towing + ) + + [19] => stdClass Object + ( + [id] => 1277 + [name] => Transmissions + ) + + [20] => stdClass Object + ( + [id] => 1278 + [name] => Used Cars + ) + + [21] => stdClass Object + ( + [id] => 1240 + [name] => e-Charging + ) + + [22] => stdClass Object + ( + [id] => 1266 + [name] => Gas Stations + ) + + ) + + [highLevelCategoryId] => 10000003 + [highLevelCategoryName] => Automotive Expenses + [defaultCategoryName] => Automotive Expenses + [defaultHighLevelCategoryName] => Automotive Expenses + ) + +*/ + + public function testGetCategories() { @@ -50,10 +225,60 @@ class YodleeApiTest extends TestCase $transactions = $yodlee->getTransactionCategories($access_token); -// nlog($transactions); + // nlog($transactions); } +/** +[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 + ) + + [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 + ) + + ) + + ) +*/ public function testGetAccounts() { @@ -66,6 +291,53 @@ class YodleeApiTest extends TestCase // nlog($accounts); } + +/** +[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 + ) + + [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 + ) + + + */ + public function testGetTransactions() { From 8810596511585e4594d86a2b9c97c50efaa7990b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 5 Aug 2022 12:58:45 +1000 Subject: [PATCH 010/169] Merge v5 --- app/Helpers/Bank/Yodlee/Yodlee.php | 4 +- .../2022_08_05_023357_bank_integration.php | 53 +++++++++++++++++++ tests/Feature/Bank/YodleeApiTest.php | 51 ++++++++++++++++++ 3 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 database/migrations/2022_08_05_023357_bank_integration.php diff --git a/app/Helpers/Bank/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index c4a4b5e3eafc..a057d6033f16 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -81,8 +81,8 @@ class Yodlee public function getTransactions($token) { - // $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions", ['categoryId' => '2']); - $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions"); + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions", ['categoryId' => '2']); + // $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions"); if($response->successful()) diff --git a/database/migrations/2022_08_05_023357_bank_integration.php b/database/migrations/2022_08_05_023357_bank_integration.php new file mode 100644 index 000000000000..65ae46ddd259 --- /dev/null +++ b/database/migrations/2022_08_05_023357_bank_integration.php @@ -0,0 +1,53 @@ +id(); + $table->unsignedInteger('account_id'); + $table->unsignedInteger('company_id'); + $table->unsignedInteger('user_id'); + + $table->string('account_type')->nullable(); + // $table->bigInteger('bank_account_id'); //providerAccountId + // $table->bigInteger('bank_id'); //providerId + $table->text('bank_name'); //providerName + $table->text('account_name')->nullable(); //accountName + $table->text('account_number')->nullable(); //accountNumber + $table->text('account_status')->nullable(); //accountStatus + $table->text('account_type')->nullable(); //CONTAINER + $table->decimal('balance', 20, 6)->default(0); //currentBalance.amount + $table->text('currency')->nullable(); //currentBalance.currency + + $table-> + $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'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +}; diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index 5b0e47f1210e..b1aa03cb14cd 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -277,6 +277,57 @@ class YodleeApiTest extends TestCase ) + ) + [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 + ) + + [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 + ) + + ) + ) */ public function testGetAccounts() From d1530c44772bd0342d7112fa245e316356b05b07 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 5 Aug 2022 13:04:02 +1000 Subject: [PATCH 011/169] Merge v5 --- app/Helpers/Bank/Yodlee/Yodlee.php | 2 +- tests/Feature/Bank/YodleeApiTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Helpers/Bank/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index a057d6033f16..5860cae1c232 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -81,7 +81,7 @@ class Yodlee public function getTransactions($token) { - $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions", ['categoryId' => '2']); + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions", ['categoryType' => 'EXPENSE']); // $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions"); diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index b1aa03cb14cd..fc580b96eea9 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -394,7 +394,7 @@ class YodleeApiTest extends TestCase $yodlee = new Yodlee(true); - $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb1'); + $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb3'); $transactions = $yodlee->getTransactions($access_token); From 6dd9f7302d7dea4fa84c4d68cebc08a59e4fbea8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 5 Aug 2022 13:45:53 +1000 Subject: [PATCH 012/169] Income / Expense TransformeR --- .../Yodlee/Transformer/ExpenseTransformer.php | 80 +++++++++++++++++++ .../Yodlee/Transformer/IncomeTransformer.php | 80 +++++++++++++++++++ app/Helpers/Bank/Yodlee/Yodlee.php | 29 ++++--- tests/Feature/Bank/YodleeApiTest.php | 8 +- 4 files changed, 184 insertions(+), 13 deletions(-) create mode 100644 app/Helpers/Bank/Yodlee/Transformer/ExpenseTransformer.php create mode 100644 app/Helpers/Bank/Yodlee/Transformer/IncomeTransformer.php diff --git a/app/Helpers/Bank/Yodlee/Transformer/ExpenseTransformer.php b/app/Helpers/Bank/Yodlee/Transformer/ExpenseTransformer.php new file mode 100644 index 000000000000..7868fb8f5baf --- /dev/null +++ b/app/Helpers/Bank/Yodlee/Transformer/ExpenseTransformer.php @@ -0,0 +1,80 @@ +bankRequest('/accounts', 'get', [], ["Authorization" => "Bearer {$token}"]); + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/accounts", $params); + + if($response->successful()) + return $response->object(); + + if($response->failed()) + return $response->body(); + return $response; } - public function getTransactions($token) + public function getTransactions($token, $params = []) { - $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions", ['categoryType' => 'EXPENSE']); - // $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions"); - + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions", $params); if($response->successful()) return $response->object(); @@ -93,12 +98,16 @@ class Yodlee } - public function getTransactionCategories($token) + public function getTransactionCategories($token, $params = []) { - $response = $this->bankRequest('/transactions/categories', 'get', [], ["Authorization" => "Bearer {$token}"]); + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions/categories", $params); - return $response; + if($response->successful()) + return $response->object(); + + if($response->failed()) + return $response->body(); } diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index fc580b96eea9..61fea12fb02b 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -221,7 +221,7 @@ class YodleeApiTest extends TestCase $yodlee = new Yodlee(true); - $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb1'); + $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb2'); $transactions = $yodlee->getTransactionCategories($access_token); @@ -394,9 +394,11 @@ class YodleeApiTest extends TestCase $yodlee = new Yodlee(true); - $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb3'); + $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb1'); - $transactions = $yodlee->getTransactions($access_token); + nlog($access_token); + + $transactions = $yodlee->getTransactions($access_token, ['categoryId' => 2, 'fromDate' => '2000-01-01']); nlog($transactions); From 1be1a804374b3eb0ef0975abbae078e7ca730a84 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 5 Aug 2022 14:25:06 +1000 Subject: [PATCH 013/169] Stubs for bank integration --- .../Yodlee/Transformer/AccountTransformer.php | 74 ++++++++++++++++++ .../Controllers/BankIntegrationController.php | 42 ++++++++++ app/Models/BankIntegration.php | 49 ++++++++++++ .../BankIntegrationTransformer.php | 77 +++++++++++++++++++ .../2022_08_05_023357_bank_integration.php | 17 ++-- 5 files changed, 249 insertions(+), 10 deletions(-) create mode 100644 app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php create mode 100644 app/Http/Controllers/BankIntegrationController.php create mode 100644 app/Models/BankIntegration.php create mode 100644 app/Transformers/BankIntegrationTransformer.php diff --git a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php new file mode 100644 index 000000000000..fe08a7ed7463 --- /dev/null +++ b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php @@ -0,0 +1,74 @@ + 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 + ) + + [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 +{ + + public static function transform(array $account) + { + return [ + ]; + } +} + + diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php new file mode 100644 index 000000000000..108bf98f5b6a --- /dev/null +++ b/app/Http/Controllers/BankIntegrationController.php @@ -0,0 +1,42 @@ + 'timestamp', + 'created_at' => 'timestamp', + 'deleted_at' => 'timestamp', + ]; + + public function getEntityType() + { + return self::class; + } + + public function company() + { + return $this->belongsTo(Company::class); + } + + public function user() + { + return $this->belongsTo(User::class)->withTrashed(); + } + + public function account() + { + return $this->belongsTo(Account::class)->withTrashed(); + } + +} \ No newline at end of file diff --git a/app/Transformers/BankIntegrationTransformer.php b/app/Transformers/BankIntegrationTransformer.php new file mode 100644 index 000000000000..7f06a9a95697 --- /dev/null +++ b/app/Transformers/BankIntegrationTransformer.php @@ -0,0 +1,77 @@ + (string) $this->encodePrimaryKey($bank_integration->id), + 'provider_bank_name' => $bank_integration->provider_bank_name ?: '', + 'bank_account_id' => $bank_integration->bank_account_id ?: '', + 'bank_account_name' => $bank_integration->bank_account_name ?: '', + 'bank_account_number' => $bank_integration->bank_account_number ?: '', + 'bank_account_status' => $bank_integration->bank_account_status ?: '', + 'bank_account_type' => $bank_integration->bank_account_type ?: '', + 'balance' => (float)$bank_integration->balance ?: 0, + 'currency' => $bank_integration->currency ?: '', + ]; + } + + public function includeAccount(BankIntegration $bank_integration) + { + $transformer = new AccountTransformer($this->serializer); + + return $this->includeItem($bank_integration->account, $transformer, Account::class); + } + + public function includeCompany(BankIntegration $bank_integration) + { + $transformer = new CompanyTransformer($this->serializer); + + return $this->includeItem($bank_integration->company, $transformer, Company::class); + } + +} diff --git a/database/migrations/2022_08_05_023357_bank_integration.php b/database/migrations/2022_08_05_023357_bank_integration.php index 65ae46ddd259..650ae4354b4d 100644 --- a/database/migrations/2022_08_05_023357_bank_integration.php +++ b/database/migrations/2022_08_05_023357_bank_integration.php @@ -14,24 +14,21 @@ return new class extends Migration public function up() { - Schema::table('bank_integration', function (Blueprint $table) { + Schema::create('bank_integrations', function (Blueprint $table) { $table->id(); $table->unsignedInteger('account_id'); $table->unsignedInteger('company_id'); $table->unsignedInteger('user_id'); - $table->string('account_type')->nullable(); - // $table->bigInteger('bank_account_id'); //providerAccountId - // $table->bigInteger('bank_id'); //providerId - $table->text('bank_name'); //providerName - $table->text('account_name')->nullable(); //accountName - $table->text('account_number')->nullable(); //accountNumber - $table->text('account_status')->nullable(); //accountStatus - $table->text('account_type')->nullable(); //CONTAINER + $table->text('provider_bank_name'); //providerName ie Chase + $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-> $table->timestamps(6); $table->softDeletes('deleted_at', 6); From 0dc50bb171f457202b94599de38984649470ce59 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 5 Aug 2022 19:05:59 +1000 Subject: [PATCH 014/169] Stubbing bank integration controllers --- app/Factory/BankIntegrationFactory.php | 37 ++ .../Controllers/BankIntegrationController.php | 403 +++++++++++++++++- .../Controllers/OpenAPI/BankIntegration.php | 18 + .../CreateBankIntegrationRequest.php | 28 ++ .../DestroyBankIntegrationRequest.php | 27 ++ .../EditBankIntegrationRequest.php | 27 ++ .../ShowBankIntegrationRequest.php | 27 ++ .../StoreBankIntegrationRequest.php | 52 +++ .../UpdateBankIntegrationRequest.php | 51 +++ .../UploadBankIntegrationRequest.php | 38 ++ app/Policies/BankIntegrationPolicy.php | 31 ++ app/Providers/AuthServiceProvider.php | 9 +- .../BankIntegrationRepository.php | 33 ++ routes/api.php | 3 + 14 files changed, 767 insertions(+), 17 deletions(-) create mode 100644 app/Factory/BankIntegrationFactory.php create mode 100644 app/Http/Controllers/OpenAPI/BankIntegration.php create mode 100644 app/Http/Requests/BankIntegration/CreateBankIntegrationRequest.php create mode 100644 app/Http/Requests/BankIntegration/DestroyBankIntegrationRequest.php create mode 100644 app/Http/Requests/BankIntegration/EditBankIntegrationRequest.php create mode 100644 app/Http/Requests/BankIntegration/ShowBankIntegrationRequest.php create mode 100644 app/Http/Requests/BankIntegration/StoreBankIntegrationRequest.php create mode 100644 app/Http/Requests/BankIntegration/UpdateBankIntegrationRequest.php create mode 100644 app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php create mode 100644 app/Policies/BankIntegrationPolicy.php create mode 100644 app/Repositories/BankIntegrationRepository.php diff --git a/app/Factory/BankIntegrationFactory.php b/app/Factory/BankIntegrationFactory.php new file mode 100644 index 000000000000..df0b1734c00f --- /dev/null +++ b/app/Factory/BankIntegrationFactory.php @@ -0,0 +1,37 @@ +account_id = $account_id; + $bank_integration->user_id = $user_id; + $bank_integration->company_id = $company_id; + + $bank_integration->provider_bank_name = ''; + $bank_integration->bank_account_id = ''; + $bank_integration->bank_account_name = ''; + $bank_integration->bank_account_number = ''; + $bank_integration->bank_account_status = ''; + $bank_integration->bank_account_type = ''; + $bank_integration->balance = 0; + $bank_integration->currency = ''; + + return $bank_integration; + } +} diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 108bf98f5b6a..7c5adfadd44c 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -11,20 +11,18 @@ namespace App\Http\Controllers; -use App\Http\Requests\Activity\DownloadHistoricalEntityRequest; -use App\Models\Activity; -use App\Transformers\ActivityTransformer; -use App\Utils\HostedPDF\NinjaPdf; -use App\Utils\Ninja; -use App\Utils\PhantomJS\Phantom; -use App\Utils\Traits\Pdf\PageNumbering; -use App\Utils\Traits\Pdf\PdfMaker; -use Illuminate\Http\JsonResponse; +use App\Factory\BankIntegrationFactory; +use App\Http\Requests\BankIntegration\CreateBankIntegrationRequest; +use App\Http\Requests\BankIntegration\DestroyBankIntegrationRequest; +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\Models\BankIntegration; +use App\Repositories\BankIntegrationRepository; +use App\Transformers\BankIntegrationTransformer; use Illuminate\Http\Request; -use Illuminate\Http\Response; -use Illuminate\Support\Facades\Storage; -use stdClass; -use Symfony\Component\HttpFoundation\StreamedResponse; + class BankIntegrationController extends BaseController { @@ -33,10 +31,387 @@ class BankIntegrationController extends BaseController protected $entity_transformer = BankIntegrationTransformer::class; - public function __construct() + protected $bank_integration_repo; + + public function __construct(BankIntegrationRepository $bank_integration_repo) { parent::__construct(); + + $this->bank_integration_repo = $bank_integration_repo; + } + + /** + * @OA\Get( + * path="/api/v1/bank_integrations", + * operationId="getBankIntegrations", + * tags={"bank_integrations"}, + * summary="Gets a list of bank_integrations", + * description="Lists all bank integrations", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter(ref="#/components/parameters/index"), + * @OA\Parameter( + * name="rows", + * in="query", + * description="The number of bank integrations to return", + * example="50", + * required=false, + * @OA\Schema( + * type="number", + * format="integer", + * ), + * ), + * @OA\Response( + * response=200, + * description="A list of bank integrations", + * @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/BankIntegration"), + * ), + * @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"), + * ), + * ) + * @param Request $request + * @return Response|mixed + */ + public function index(Request $request) + { + + $bank_integrations = BankIntegration::query()->company(); + + return $this->listResponse($bank_integrations); + + } + + /** + * Display the specified resource. + * + * @param ShowBankIntegrationRequest $request + * @param BankIntegration $bank_integration + * @return Response + * + * + * @OA\Get( + * path="/api/v1/bank_integrations/{id}", + * operationId="showBankIntegration", + * tags={"bank_integrations"}, + * summary="Shows a bank_integration", + * description="Displays a bank_integration by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The BankIntegration Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the bank_integration object", + * @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/BankIntegration"), + * ), + * @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"), + * ), + * ) + */ + public function show(ShowBankIntegrationRequest $request, BankIntegration $bank_integration) + { + return $this->itemResponse($bank_integration); } + /** + * Show the form for editing the specified resource. + * + * @param EditBankIntegrationRequest $request + * @param BankIntegration $bank_integration + * @return Response + * + * + * @OA\Get( + * path="/api/v1/bank_integrations/{id}/edit", + * operationId="editBankIntegration", + * tags={"bank_integrations"}, + * summary="Shows a bank_integration for editing", + * description="Displays a bank_integration by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The BankIntegration Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the bank_integration object", + * @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/BankIntegration"), + * ), + * @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"), + * ), + * ) + */ + public function edit(EditBankIntegrationRequest $request, BankIntegration $bank_integration) + { + return $this->itemResponse($bank_integration); + } + + /** + * Update the specified resource in storage. + * + * @param UpdateBankIntegrationRequest $request + * @param BankIntegration $bank_integration + * @return Response + * + * + * + * @OA\Put( + * path="/api/v1/bank_integrations/{id}", + * operationId="updateBankIntegration", + * tags={"bank_integrations"}, + * summary="Updates a bank_integration", + * description="Handles the updating of a bank_integration by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The BankIntegration Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the bank_integration object", + * @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/BankIntegration"), + * ), + * @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"), + * ), + * ) + */ + public function update(UpdateBankIntegrationRequest $request, BankIntegration $bank_integration) + { + + //stubs for updating the model + $bank_integration = $this->bank_integration_repo->save($request->all(), $bank_integration); + + return $this->itemResponse($bank_integration->fresh()); + } + + /** + * Show the form for creating a new resource. + * + * @param CreateBankIntegrationRequest $request + * @return Response + * + * + * + * @OA\Get( + * path="/api/v1/bank_integrations/create", + * operationId="getBankIntegrationsCreate", + * tags={"bank_integrations"}, + * summary="Gets a new blank bank_integration object", + * description="Returns a blank object with default values", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @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="A blank bank_integration object", + * @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/BankIntegration"), + * ), + * @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"), + * ), + * ) + */ + public function create(CreateBankIntegrationRequest $request) + { + $bank_integration = BankIntegrationFactory::create(auth()->user()->company()->id, auth()->user()->id, auth()->user()->account_id); + + return $this->itemResponse($bank_integration); + } + + /** + * Store a newly created resource in storage. + * + * @param StoreBankIntegrationRequest $request + * @return Response + * + * + * + * @OA\Post( + * path="/api/v1/bank_integrations", + * operationId="storeBankIntegration", + * tags={"bank_integrations"}, + * summary="Adds a bank_integration", + * description="Adds an bank_integration to a company", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @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="Returns the saved bank_integration object", + * @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/BankIntegration"), + * ), + * @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"), + * ), + * ) + */ + public function store(StoreBankIntegrationRequest $request) + { + //stub to store the model + $bank_integration = $this->bank_integration_repo->save($request->all(), BankIntegrationFactory::create(auth()->user()->company()->id, auth()->user()->id, auth()->user()->account_id)); + + return $this->itemResponse($bank_integration); + } + + /** + * Remove the specified resource from storage. + * + * @param DestroyBankIntegrationRequest $request + * @param BankIntegration $bank_integration + * @return Response + * + * + * @throws \Exception + * @OA\Delete( + * path="/api/v1/bank_integrations/{id}", + * operationId="deleteBankIntegration", + * tags={"bank_integrations"}, + * summary="Deletes a bank_integration", + * description="Handles the deletion of a bank_integration by id", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="id", + * in="path", + * description="The BankIntegration Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns a HTTP status", + * @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\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"), + * ), + * ) + */ + public function destroy(DestroyBankIntegrationRequest $request, BankIntegration $bank_integration) + { + $this->bank_integration_repo->delete($bank_integration); + + return $this->itemResponse($bank_integration->fresh()); + } } \ No newline at end of file diff --git a/app/Http/Controllers/OpenAPI/BankIntegration.php b/app/Http/Controllers/OpenAPI/BankIntegration.php new file mode 100644 index 000000000000..4731d13e1938 --- /dev/null +++ b/app/Http/Controllers/OpenAPI/BankIntegration.php @@ -0,0 +1,18 @@ +user()->can('create', BankIntegration::class); + } +} diff --git a/app/Http/Requests/BankIntegration/DestroyBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/DestroyBankIntegrationRequest.php new file mode 100644 index 000000000000..b2550cd7dabe --- /dev/null +++ b/app/Http/Requests/BankIntegration/DestroyBankIntegrationRequest.php @@ -0,0 +1,27 @@ +user()->can('edit', $this->bank_integration); + } +} diff --git a/app/Http/Requests/BankIntegration/EditBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/EditBankIntegrationRequest.php new file mode 100644 index 000000000000..6c5b668e4eaa --- /dev/null +++ b/app/Http/Requests/BankIntegration/EditBankIntegrationRequest.php @@ -0,0 +1,27 @@ +user()->can('edit', $this->bank_integration); + } +} diff --git a/app/Http/Requests/BankIntegration/ShowBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/ShowBankIntegrationRequest.php new file mode 100644 index 000000000000..9d08c25f1fa8 --- /dev/null +++ b/app/Http/Requests/BankIntegration/ShowBankIntegrationRequest.php @@ -0,0 +1,27 @@ +user()->can('view', $this->bank_integration); + } +} diff --git a/app/Http/Requests/BankIntegration/StoreBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/StoreBankIntegrationRequest.php new file mode 100644 index 000000000000..2f1902f43ac3 --- /dev/null +++ b/app/Http/Requests/BankIntegration/StoreBankIntegrationRequest.php @@ -0,0 +1,52 @@ +user()->can('create', BankIntegration::class); + } + + public function rules() + { + + $rules = []; + + return $rules; + } + + public function prepareForValidation() + { + $input = $this->all(); + + $this->replace($input); + } + + public function messages() + { + return []; + } + +} diff --git a/app/Http/Requests/BankIntegration/UpdateBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/UpdateBankIntegrationRequest.php new file mode 100644 index 000000000000..5bb712be1164 --- /dev/null +++ b/app/Http/Requests/BankIntegration/UpdateBankIntegrationRequest.php @@ -0,0 +1,51 @@ +user()->can('edit', $this->bank_integration); + } + + public function rules() + { + /* Ensure we have a client name, and that all emails are unique*/ + $rules = []; + + return $rules; + } + + public function messages() + { + return [ ]; + } + + public function prepareForValidation() + { + $input = $this->all(); + + $this->replace($input); + } + +} diff --git a/app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php new file mode 100644 index 000000000000..b9354432c455 --- /dev/null +++ b/app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php @@ -0,0 +1,38 @@ +user()->can('edit', $this->bank_integration); + } + + public function rules() + { + $rules = []; + + if ($this->input('documents')) { + $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + } + + return $rules; + } +} diff --git a/app/Policies/BankIntegrationPolicy.php b/app/Policies/BankIntegrationPolicy.php new file mode 100644 index 000000000000..c2dfb135c07d --- /dev/null +++ b/app/Policies/BankIntegrationPolicy.php @@ -0,0 +1,31 @@ +isAdmin(); + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 670d21b75d7e..9f996a513d2f 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -12,6 +12,7 @@ namespace App\Providers; use App\Models\Activity; +use App\Models\Bank; use App\Models\Client; use App\Models\Company; use App\Models\CompanyGateway; @@ -40,6 +41,7 @@ use App\Models\User; use App\Models\Vendor; use App\Models\Webhook; use App\Policies\ActivityPolicy; +use App\Policies\BankIntegrationPolicy; use App\Policies\ClientPolicy; use App\Policies\CompanyGatewayPolicy; use App\Policies\CompanyPolicy; @@ -79,7 +81,7 @@ class AuthServiceProvider extends ServiceProvider */ protected $policies = [ Activity::class => ActivityPolicy::class, - Subscription::class => SubscriptionPolicy::class, + Bank::class => BankIntegrationPolicy::class, Client::class => ClientPolicy::class, Company::class => CompanyPolicy::class, CompanyToken::class => CompanyTokenPolicy::class, @@ -95,17 +97,18 @@ class AuthServiceProvider extends ServiceProvider PaymentTerm::class => PaymentTermPolicy::class, Product::class => ProductPolicy::class, Project::class => ProjectPolicy::class, + PurchaseOrder::class => PurchaseOrderPolicy::class, Quote::class => QuotePolicy::class, RecurringExpense::class => RecurringExpensePolicy::class, RecurringInvoice::class => RecurringInvoicePolicy::class, RecurringQuote::class => RecurringQuotePolicy::class, - Webhook::class => WebhookPolicy::class, + Subscription::class => SubscriptionPolicy::class, Task::class => TaskPolicy::class, TaskStatus::class => TaskStatusPolicy::class, TaxRate::class => TaxRatePolicy::class, User::class => UserPolicy::class, Vendor::class => VendorPolicy::class, - PurchaseOrder::class => PurchaseOrderPolicy::class, + Webhook::class => WebhookPolicy::class, ]; /** diff --git a/app/Repositories/BankIntegrationRepository.php b/app/Repositories/BankIntegrationRepository.php new file mode 100644 index 000000000000..f3fd21b2cee7 --- /dev/null +++ b/app/Repositories/BankIntegrationRepository.php @@ -0,0 +1,33 @@ +save(); + + } + +} diff --git a/routes/api.php b/routes/api.php index f5150ec20004..423b8934ab65 100644 --- a/routes/api.php +++ b/routes/api.php @@ -15,6 +15,7 @@ use App\Http\Controllers\AccountController; use App\Http\Controllers\ActivityController; use App\Http\Controllers\Auth\ForgotPasswordController; use App\Http\Controllers\Auth\LoginController; +use App\Http\Controllers\BankIntegrationController; use App\Http\Controllers\BaseController; use App\Http\Controllers\ChartController; use App\Http\Controllers\ClientController; @@ -106,6 +107,8 @@ Route::group(['middleware' => ['throttle:10,1','api_secret_check','email_db']], Route::group(['middleware' => ['throttle:300,1', 'api_db', 'token_auth', 'locale'], 'prefix' => 'api/v1', 'as' => 'api.'], function () { Route::put('accounts/{account}', [AccountController::class, 'update'])->name('account.update'); + Route::resource('bank_integrations', BankIntegrationController::class); // name = (clients. index / create / show / update / destroy / edit + Route::post('check_subdomain', [SubdomainController::class, 'index'])->name('check_subdomain'); Route::get('ping', [PingController::class, 'index'])->name('ping'); Route::get('health_check', [PingController::class, 'health'])->name('health_check'); From edfbcb9514ebbcdfdf2efb68a050a91d1306e927 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 6 Aug 2022 16:58:48 +1000 Subject: [PATCH 015/169] Working on Fastlink --- app/Helpers/Bank/Yodlee/Yodlee.php | 42 ++++++++++++++++++- .../2022_08_05_023357_bank_integration.php | 4 ++ resources/views/bank/yodlee/auth.blade.php | 1 + tests/Feature/Bank/YodleeApiTest.php | 11 +++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/app/Helpers/Bank/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index c6f58df515ee..cbab6f997410 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -65,7 +65,47 @@ class Yodlee 'loginName' => 'test123', ]; - return $this->bankRequest('/user/register', 'post', $user, ['Authorization' => $token]); +/* +{ + "user": { + "preferences": { + "dateFormat": "string", + "timeZone": "string", + "currency": "USD", + "locale": "en_US" + }, + "address": { + "zip": "string", + "country": "string", + "address3": "string", + "address2": "string", + "city": "string", + "address1": "string", + "state": "string" + }, + "loginName": "string", + "name": { + "middle": "string", + "last": "string", + "fullName": "string", + "first": "string" + }, + "email": "string", + "segmentName": "string" + } +} +*/ + + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->post($this->api_endpoint. "/user/register", $user); + + if($response->successful()) + return $response->object(); + + if($response->failed()) + return $response->body(); + + + return $response; } diff --git a/database/migrations/2022_08_05_023357_bank_integration.php b/database/migrations/2022_08_05_023357_bank_integration.php index 650ae4354b4d..e3f0c326930e 100644 --- a/database/migrations/2022_08_05_023357_bank_integration.php +++ b/database/migrations/2022_08_05_023357_bank_integration.php @@ -36,6 +36,10 @@ return new class extends Migration $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade')->onUpdate('cascade'); $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade'); }); + + Schema::table('accounts', function (Blueprint $table) { + $table->text('bank_integration_account_id')->nullable(); + }); } /** diff --git a/resources/views/bank/yodlee/auth.blade.php b/resources/views/bank/yodlee/auth.blade.php index 66002fffdcec..ffc005d53d95 100644 --- a/resources/views/bank/yodlee/auth.blade.php +++ b/resources/views/bank/yodlee/auth.blade.php @@ -27,6 +27,7 @@ 'click', function() { window.fastlink.open({ + flow: 'edit',//flow changes depending on what we are doing sometimes it could be add/edit etc etc fastLinkURL: '{{ $fasttrack_url }}', accessToken: 'Bearer {{ $access_token }}', params: { diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index 61fea12fb02b..9267bead54d1 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -28,6 +28,17 @@ class YodleeApiTest extends TestCase } + public function testCreateUser() + { + + $yodlee = new Yodlee(true); + + $user = $yodlee->createUser(); + + nlog($user); + + } + public function testAccessTokenGeneration() { From c5269832f94f1c0f943ba29662fa2a5421f908c0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 6 Aug 2022 18:11:43 +1000 Subject: [PATCH 016/169] Padding --- app/Http/Controllers/Bank/YodleeController.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index ead225b706dd..f90f2f2dff8f 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -21,6 +21,11 @@ class YodleeController extends BaseController public function auth(Request $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 + $yodlee = new Yodlee(true); $data = [ From 090f8f5dabe3a0c5932d962dc9570d7f79b4683c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 Aug 2022 08:26:27 +1000 Subject: [PATCH 017/169] Refactor for Yodlee API' --- app/Helpers/Bank/Yodlee/Yodlee.php | 75 +++++++++++-------- .../Controllers/BankIntegrationController.php | 53 +++++++++++++ .../AdminBankIntegrationRequest.php | 28 +++++++ tests/Feature/Bank/YodleeApiTest.php | 36 +++++---- 4 files changed, 141 insertions(+), 51 deletions(-) create mode 100644 app/Http/Requests/BankIntegration/AdminBankIntegrationRequest.php diff --git a/app/Helpers/Bank/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index cbab6f997410..8ff9275cce0f 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -16,7 +16,7 @@ use Illuminate\Support\Facades\Http; class Yodlee { - public bool $test_mode; + public bool $test_mode = false; private string $api_endpoint = 'https://production.api.yodlee.com/ysl'; @@ -30,12 +30,12 @@ class Yodlee protected string $admin_name; - public function __construct(bool $test_mode = false) - { - $this->test_mode = $test_mode; + protected ?string $bank_account_id; - if($this->test_mode) - $this->api_endpoint = $this->test_api_endpoint; + public function __construct(?string $bank_account_id = null) + { + + $this->bank_account_id = $bank_account_id; $this->client_id = config('ninja.yodlee.client_id'); @@ -45,13 +45,34 @@ class Yodlee } - public function getAccessToken($user = false) + public function setTestMode() { - if(!$user) + $this->test_mode = true; + + return $this; + } + + public function getEndpoint() + { + + return $this->test_mode ? $this->test_api_endpoint : $this->api_endpoint; + + } + + /** + * If we do not pass in a user + * we pass in the admin username instead + */ + public function getAccessToken($is_admin = false) + { + if($is_admin) $user = $this->admin_name; + else + $user = $this->bank_account_id ?: $this->admin_name; $response = $this->bankFormRequest('/auth/token', 'post', [], ['loginName' => $user]); - +//catch failures here + nlog($response); return $response->token->accessToken; } @@ -59,7 +80,7 @@ class Yodlee public function createUser() { - $token = $this->getAccessToken(); + $token = $this->getAccessToken(true); $user['user'] = [ 'loginName' => 'test123', @@ -96,7 +117,7 @@ class Yodlee } */ - $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->post($this->api_endpoint. "/user/register", $user); + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->post($this->getEndpoint(). "/user/register", $user); if($response->successful()) return $response->object(); @@ -109,10 +130,12 @@ class Yodlee } - public function getAccounts($token, $params = []) + public function getAccounts($params = []) { - $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/accounts", $params); + $token = $this->getAccessToken(); + + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->getEndpoint(). "/accounts", $params); if($response->successful()) return $response->object(); @@ -125,10 +148,11 @@ class Yodlee } - public function getTransactions($token, $params = []) + public function getTransactions($params = []) { + $token = $this->getAccessToken(); - $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions", $params); + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->getEndpoint(). "/transactions", $params); if($response->successful()) return $response->object(); @@ -138,24 +162,11 @@ class Yodlee } - public function getTransactionCategories($token, $params = []) + public function getTransactionCategories($params = []) { + $token = $this->getAccessToken(); - $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->api_endpoint. "/transactions/categories", $params); - - if($response->successful()) - return $response->object(); - - if($response->failed()) - return $response->body(); - - } - - - private function bankRequest(string $uri, string $verb, array $data = [], array $headers = []) - { - - $response = Http::withHeaders($this->getHeaders($headers))->{$verb}($this->api_endpoint . $uri, $this->buildBody()); + $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->getEndpoint(). "/transactions/categories", $params); if($response->successful()) return $response->object(); @@ -168,7 +179,7 @@ class Yodlee private function bankFormRequest(string $uri, string $verb, array $data = [], array $headers) { - $response = Http::withHeaders($this->getFormHeaders($headers))->asForm()->{$verb}($this->api_endpoint . $uri, $this->buildBody()); + $response = Http::withHeaders($this->getFormHeaders($headers))->asForm()->{$verb}($this->getEndpoint() . $uri, $this->buildBody()); if($response->successful()) return $response->object(); diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 7c5adfadd44c..093202a3934f 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -12,6 +12,8 @@ namespace App\Http\Controllers; use App\Factory\BankIntegrationFactory; +use App\Helpers\Bank\Yodlee\Yodlee; +use App\Http\Requests\BankIntegration\AdminBankIntegrationRequest; use App\Http\Requests\BankIntegration\CreateBankIntegrationRequest; use App\Http\Requests\BankIntegration\DestroyBankIntegrationRequest; use App\Http\Requests\BankIntegration\EditBankIntegrationRequest; @@ -414,4 +416,55 @@ class BankIntegrationController extends BaseController return $this->itemResponse($bank_integration->fresh()); } + + /** + * Return the remote list of accounts stored on the third part provider. + * + * @return Response + * + * + * + * @OA\Post( + * path="/api/v1/bank_integrations/remote_accounts", + * operationId="getRemoteAccounts", + * tags={"bank_integrations"}, + * summary="Gets the list of accounts from the remote server", + * description="Adds an bank_integration to a company", + * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), + * @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="Returns the saved bank_integration object", + * @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/BankIntegration"), + * ), + * @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"), + * ), + * ) + */ + public function remote_accounts(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 + + $bank_account_id = auth()->user()->account->bank_integration_account_id; + + $yodlee = new Yodlee(); + $yodlee->setTestMode(true); + + $yodlee->getAccounts($bank_account_id); + } } \ No newline at end of file diff --git a/app/Http/Requests/BankIntegration/AdminBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/AdminBankIntegrationRequest.php new file mode 100644 index 000000000000..b3a7aab38d81 --- /dev/null +++ b/app/Http/Requests/BankIntegration/AdminBankIntegrationRequest.php @@ -0,0 +1,28 @@ +user()->isAdmin(); + } +} diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index 9267bead54d1..f8746e0a9156 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -31,20 +31,23 @@ class YodleeApiTest extends TestCase public function testCreateUser() { - $yodlee = new Yodlee(true); + $yodlee = new Yodlee(); + $yodlee->setTestMode(); - $user = $yodlee->createUser(); + // $user = $yodlee->createUser(); - nlog($user); + // nlog($user); + $this->assertNotNull($yodlee); } public function testAccessTokenGeneration() { - $yodlee = new Yodlee(true); + $yodlee = new Yodlee('sbMem62e1e69547bfb1'); + $yodlee->setTestMode(); - $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb1'); + $access_token = $yodlee->getAccessToken(true); // nlog($access_token); @@ -230,11 +233,10 @@ class YodleeApiTest extends TestCase { - $yodlee = new Yodlee(true); - - $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb2'); + $yodlee = new Yodlee('sbMem62e1e69547bfb2'); + $yodlee->setTestMode(); - $transactions = $yodlee->getTransactionCategories($access_token); + $transactions = $yodlee->getTransactionCategories(); // nlog($transactions); @@ -344,11 +346,10 @@ class YodleeApiTest extends TestCase public function testGetAccounts() { - $yodlee = new Yodlee(true); - - $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb1'); + $yodlee = new Yodlee('sbMem62e1e69547bfb1'); + $yodlee->setTestMode(); - $accounts = $yodlee->getAccounts($access_token); + $accounts = $yodlee->getAccounts(); // nlog($accounts); } @@ -403,13 +404,10 @@ class YodleeApiTest extends TestCase public function testGetTransactions() { - $yodlee = new Yodlee(true); - - $access_token = $yodlee->getAccessToken('sbMem62e1e69547bfb1'); + $yodlee = new Yodlee('sbMem62e1e69547bfb1'); + $yodlee->setTestMode(); - nlog($access_token); - - $transactions = $yodlee->getTransactions($access_token, ['categoryId' => 2, 'fromDate' => '2000-01-01']); + $transactions = $yodlee->getTransactions(['categoryId' => 2, 'fromDate' => '2000-01-01']); nlog($transactions); From dc176aa3f8f051c1e122aab30ef55d6ac140b095 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 Aug 2022 08:32:28 +1000 Subject: [PATCH 018/169] Clean up for Yodlee Tests --- tests/Feature/Bank/YodleeApiTest.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index f8746e0a9156..032eee142779 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -24,21 +24,20 @@ class YodleeApiTest extends TestCase { parent::setUp(); - // $this->markTestSkipped('Skip test no company gateways installed'); + if(!config('ninja.yodlee.client_id')) + $this->markTestSkipped('Skip test no Yodlee API credentials found'); } - public function testCreateUser() + public function testYodleeInstance() { $yodlee = new Yodlee(); $yodlee->setTestMode(); - // $user = $yodlee->createUser(); - - // nlog($user); - $this->assertNotNull($yodlee); + + $this->assertInstanceOf(Yodlee::class, $yodlee); } public function testAccessTokenGeneration() @@ -49,8 +48,6 @@ class YodleeApiTest extends TestCase $access_token = $yodlee->getAccessToken(true); - // nlog($access_token); - $this->assertNotNull($access_token); } @@ -237,8 +234,10 @@ class YodleeApiTest extends TestCase $yodlee->setTestMode(); $transactions = $yodlee->getTransactionCategories(); +// +// nlog($transactions); - // nlog($transactions); + $this->assertIsArray($transactions->transactionCategory); } @@ -351,7 +350,7 @@ class YodleeApiTest extends TestCase $accounts = $yodlee->getAccounts(); - // nlog($accounts); + $this->assertIsArray($accounts->account); } @@ -409,7 +408,8 @@ class YodleeApiTest extends TestCase $transactions = $yodlee->getTransactions(['categoryId' => 2, 'fromDate' => '2000-01-01']); - nlog($transactions); + $this->assertIsArray($transactions->transaction); + //nlog($transactions); } From b7fbfe6531faa12ad38b8296f56625942340aaa2 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 Aug 2022 09:15:31 +1000 Subject: [PATCH 019/169] Transform accounts --- .../Yodlee/Transformer/AccountTransformer.php | 104 ++++++++++-------- app/Helpers/Bank/Yodlee/Yodlee.php | 10 +- tests/Feature/Bank/YodleeApiTest.php | 4 +- 3 files changed, 72 insertions(+), 46 deletions(-) diff --git a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php index fe08a7ed7463..5ed0b020799b 100644 --- a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php +++ b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php @@ -12,61 +12,79 @@ namespace App\Helpers\Bank\Yodlee\Transformer; /** -[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 - ) +[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 + ) - ) + ) - ) +) ) */ class AccountTransformer { - public static function transform(array $account) + public function transform($yodlee_account){ + + $data = []; + + foreach($yodlee_account->account as $account) + { + $data[] = $this->transformAccount($account); + } + + return $data; + } + + public function transformAccount($account) { + nlog($account); return [ + 'id' => $account->id, + 'account_type' => $account->CONTAINER, + 'account_name' => $account->accountName, + 'account_status' => $account->accountStatus, + 'account_number' => $account->accountNumber, + '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/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index 8ff9275cce0f..86554ec2711a 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -11,6 +11,7 @@ namespace App\Helpers\Bank\Yodlee; +use App\Helpers\Bank\Yodlee\Transformer\AccountTransformer; use Illuminate\Support\Facades\Http; class Yodlee @@ -137,8 +138,13 @@ class Yodlee $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->getEndpoint(). "/accounts", $params); - if($response->successful()) - return $response->object(); + + if($response->successful()){ + + $at = new AccountTransformer(); + return $at->transform($response->object()); + // return $response->object(); + } if($response->failed()) return $response->body(); diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index 032eee142779..9df4e6e9bb2d 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -350,7 +350,9 @@ class YodleeApiTest extends TestCase $accounts = $yodlee->getAccounts(); - $this->assertIsArray($accounts->account); +nlog($accounts); + + $this->assertIsArray($accounts); } From 38704c6ce58a362378ca17b233c9113c2c5c8e31 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 Aug 2022 12:46:41 +1000 Subject: [PATCH 020/169] Transaction transformers --- .../Bank/AccountTransformerInterface.php | 17 +++++ app/Helpers/Bank/BankRevenueInterface.php | 2 +- .../Yodlee/Transformer/AccountTransformer.php | 9 ++- .../Yodlee/Transformer/IncomeTransformer.php | 72 ++++++++++++++++++- app/Helpers/Bank/Yodlee/Yodlee.php | 11 ++- .../Controllers/BankIntegrationController.php | 36 ++++++++-- routes/api.php | 4 +- tests/Feature/Bank/YodleeApiTest.php | 25 ++++++- 8 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 app/Helpers/Bank/AccountTransformerInterface.php diff --git a/app/Helpers/Bank/AccountTransformerInterface.php b/app/Helpers/Bank/AccountTransformerInterface.php new file mode 100644 index 000000000000..1fd5ebf4685c --- /dev/null +++ b/app/Helpers/Bank/AccountTransformerInterface.php @@ -0,0 +1,17 @@ + stdClass Object @@ -59,10 +61,11 @@ namespace App\Helpers\Bank\Yodlee\Transformer; ) */ -class AccountTransformer +class AccountTransformer implements AccountTransformerInterface { - public function transform($yodlee_account){ + public function transform($yodlee_account) + { $data = []; @@ -76,7 +79,7 @@ class AccountTransformer public function transformAccount($account) { - nlog($account); + return [ 'id' => $account->id, 'account_type' => $account->CONTAINER, diff --git a/app/Helpers/Bank/Yodlee/Transformer/IncomeTransformer.php b/app/Helpers/Bank/Yodlee/Transformer/IncomeTransformer.php index abaf79fdc81a..8e33d2243986 100644 --- a/app/Helpers/Bank/Yodlee/Transformer/IncomeTransformer.php +++ b/app/Helpers/Bank/Yodlee/Transformer/IncomeTransformer.php @@ -10,6 +10,8 @@ */ namespace App\Helpers\Bank\Yodlee\Transformer; + +use App\Helpers\Bank\BankRevenueInterface; /** "date": "string", @@ -70,11 +72,77 @@ namespace App\Helpers\Bank\Yodlee\Transformer; "holdingDescription": "string", "isin": "string", "status": "POSTED" - */ + +( +[CONTAINER] => bank +[id] => 103953585 +[amount] => stdClass Object + ( + [amount] => 480.66 + [currency] => USD + ) -class IncomeTransformer +[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 { + public function transform($transaction) + { + + $data = []; + + foreach($transaction->transaction as $transaction) + { + $data[] = $this->transformTransaction($transaction); + } + + return $data; + } + + public function transformTransaction($transaction) + { + + return [ + 'id' => $transaction->id, + 'amount' => $transaction->amount->amount, + 'currency' => $transaction->amount->currency, + 'account_type' => $transaction->CONTAINER, + 'category_id' => $transaction->categoryId, + 'category_type' => $transaction->categoryType, + 'date' => $transaction->date, + 'account_id' => $transaction->accountId, + 'description' => $transaction->description->original, + ]; + } + } diff --git a/app/Helpers/Bank/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index 86554ec2711a..fa905d16737a 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -12,6 +12,7 @@ namespace App\Helpers\Bank\Yodlee; use App\Helpers\Bank\Yodlee\Transformer\AccountTransformer; +use App\Helpers\Bank\Yodlee\Transformer\IncomeTransformer; use Illuminate\Support\Facades\Http; class Yodlee @@ -73,7 +74,7 @@ class Yodlee $response = $this->bankFormRequest('/auth/token', 'post', [], ['loginName' => $user]); //catch failures here - nlog($response); + // nlog($response); return $response->token->accessToken; } @@ -160,8 +161,12 @@ class Yodlee $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->getEndpoint(). "/transactions", $params); - if($response->successful()) - return $response->object(); + if($response->successful()){ + // return $response->object(); + $it = new IncomeTransformer(); + return $it->transform($response->object()); + + } if($response->failed()) return $response->body(); diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 093202a3934f..7989a4795386 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -455,16 +455,44 @@ class BankIntegrationController extends BaseController * ), * ) */ - public function remote_accounts(AdminBankIntegrationRequest $request) + public function remoteAccounts(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 $bank_account_id = auth()->user()->account->bank_integration_account_id; - $yodlee = new Yodlee(); - $yodlee->setTestMode(true); + if(!$bank_account_id) + return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); + + $yodlee = new Yodlee($bank_account_id); + + $accounts = $yodlee->getAccounts(); + + return response()->json($accounts, 200); + } + + public function getTransactions(AdminBankIntegrationRequest $request) + { + //handle API failures we have only accounts for success + + $bank_account_id = auth()->user()->account->bank_integration_account_id; + + if(!$bank_account_id) + return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); + + $yodlee = new Yodlee($bank_account_id); + + $data = [ + 'CONTAINER' => 'bank', + 'categoryType' => 'INCOME, UNCATEGORIZE', + 'top' => 500, + 'fromDate' => '2000-10-10', /// YYYY-MM-DD + ]; + + $transactions = $yodlee->getTransactions($data); + + return response()->json($transactions, 200) - $yodlee->getAccounts($bank_account_id); } } \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 018c9e70b84c..962f6d8a48d5 100644 --- a/routes/api.php +++ b/routes/api.php @@ -107,7 +107,9 @@ Route::group(['middleware' => ['throttle:10,1','api_secret_check','email_db']], Route::group(['middleware' => ['throttle:300,1', 'api_db', 'token_auth', 'locale'], 'prefix' => 'api/v1', 'as' => 'api.'], function () { Route::put('accounts/{account}', [AccountController::class, 'update'])->name('account.update'); Route::resource('bank_integrations', BankIntegrationController::class); // name = (clients. index / create / show / update / destroy / edit - + Route::post('bank_integrations/remote_accounts', [BankIntegrationController::class, 'remoteAccounts'])->name('bank_integrations.remote_accounts'); + Route::post('bank_integrations/transactions', [BankIntegrationController::class, 'getTransactions'])->name('bank_integrations.transactions'); + Route::post('check_subdomain', [SubdomainController::class, 'index'])->name('check_subdomain'); Route::get('ping', [PingController::class, 'index'])->name('ping'); Route::get('health_check', [PingController::class, 'health'])->name('health_check'); diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index 9df4e6e9bb2d..4a8ebb5f7a7a 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -234,7 +234,7 @@ class YodleeApiTest extends TestCase $yodlee->setTestMode(); $transactions = $yodlee->getTransactionCategories(); -// + // nlog($transactions); $this->assertIsArray($transactions->transactionCategory); @@ -350,8 +350,6 @@ class YodleeApiTest extends TestCase $accounts = $yodlee->getAccounts(); -nlog($accounts); - $this->assertIsArray($accounts); } @@ -416,4 +414,25 @@ nlog($accounts); } + public function testGetTransactionsWithParams() + { + + $yodlee = new Yodlee('sbMem62e1e69547bfb1'); + $yodlee->setTestMode(); + + $data = [ + 'CONTAINER' => 'bank', + 'categoryType' => 'INCOME, UNCATEGORIZE', + 'top' => 500, + 'fromDate' => '2000-10-10', /// YYYY-MM-DD + ]; + + $accounts = $yodlee->getTransactions($data); + + + nlog($accounts); + } + + + } From 7433671945fde4fce9f757ea6106a4fc5ad6930c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 Aug 2022 12:59:56 +1000 Subject: [PATCH 021/169] Update test --- 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 4a8ebb5f7a7a..656684f2ee93 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -421,8 +421,8 @@ class YodleeApiTest extends TestCase $yodlee->setTestMode(); $data = [ + 'basetype' => 'DEBIT', //CREDIT 'CONTAINER' => 'bank', - 'categoryType' => 'INCOME, UNCATEGORIZE', 'top' => 500, 'fromDate' => '2000-10-10', /// YYYY-MM-DD ]; From 0228f5aec38fdf57cb1c5f68d3e47882f37f0fd9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 Aug 2022 14:04:39 +1000 Subject: [PATCH 022/169] minor fixes --- 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 656684f2ee93..74ce98f6fc8d 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -408,7 +408,7 @@ class YodleeApiTest extends TestCase $transactions = $yodlee->getTransactions(['categoryId' => 2, 'fromDate' => '2000-01-01']); - $this->assertIsArray($transactions->transaction); + $this->assertIsArray($transactions); //nlog($transactions); } From 357cb17ebfff1408b57f039bf28210cb7f2280c9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 Aug 2022 17:56:21 +1000 Subject: [PATCH 023/169] Working on bank integration --- app/Helpers/Bank/Yodlee/Transformer/IncomeTransformer.php | 3 +++ app/Http/Controllers/BankIntegrationController.php | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/Helpers/Bank/Yodlee/Transformer/IncomeTransformer.php b/app/Helpers/Bank/Yodlee/Transformer/IncomeTransformer.php index 8e33d2243986..0ffe92486780 100644 --- a/app/Helpers/Bank/Yodlee/Transformer/IncomeTransformer.php +++ b/app/Helpers/Bank/Yodlee/Transformer/IncomeTransformer.php @@ -140,6 +140,9 @@ class IncomeTransformer implements BankRevenueInterface 'date' => $transaction->date, 'account_id' => $transaction->accountId, 'description' => $transaction->description->original, + 'invoice_id' => '', + 'expense_id' => '', + 'payment_id' => '', ]; } diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 7989a4795386..29ac2592ccba 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -478,10 +478,13 @@ class BankIntegrationController extends BaseController $bank_account_id = auth()->user()->account->bank_integration_account_id; + $bank_account_id = 'sbMem62e1e69547bfb1'; + if(!$bank_account_id) return response()->json(['message' => 'Not yet authenticated with Bank Integration service'], 400); $yodlee = new Yodlee($bank_account_id); + $yodlee->setTestMode(); $data = [ 'CONTAINER' => 'bank', @@ -492,7 +495,7 @@ class BankIntegrationController extends BaseController $transactions = $yodlee->getTransactions($data); - return response()->json($transactions, 200) + return response()->json($transactions, 200, [], JSON_PRETTY_PRINT); } } \ No newline at end of file From 3d397b40e3dca1ad0613f0aeef3c289084c70b63 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 Aug 2022 19:07:35 +1000 Subject: [PATCH 024/169] Transaction matching service --- .../Controllers/BankIntegrationController.php | 4 ++ app/Services/Bank/BankService.php | 61 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 app/Services/Bank/BankService.php diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index 29ac2592ccba..725bd2275a10 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -22,6 +22,7 @@ use App\Http\Requests\BankIntegration\StoreBankIntegrationRequest; use App\Http\Requests\BankIntegration\UpdateBankIntegrationRequest; use App\Models\BankIntegration; use App\Repositories\BankIntegrationRepository; +use App\Services\Bank\BankService; use App\Transformers\BankIntegrationTransformer; use Illuminate\Http\Request; @@ -495,6 +496,9 @@ class BankIntegrationController extends BaseController $transactions = $yodlee->getTransactions($data); + $transactions = (new BankService(auth()->user()->company()))->match(); + + return response()->json($transactions, 200, [], JSON_PRETTY_PRINT); } diff --git a/app/Services/Bank/BankService.php b/app/Services/Bank/BankService.php new file mode 100644 index 000000000000..f22946b87e8d --- /dev/null +++ b/app/Services/Bank/BankService.php @@ -0,0 +1,61 @@ +company = $company; + + $this->invoices = $this->company->invoices()->whereIn('status_id', [1,2,3]) + ->where('is_deleted', 0) + ->get(); + + } + + public function match($transactions): array + { + + foreach($transactions as $transaction) + { + $this->matchIncome($transaction); + } + + return $transactions; + } + + private function matchExpense() + { + + } + + private function matchIncome($transaction) + { + $description = str_replace(" ", "", $transaction->description); + + $invoice = $this->invoices->where('number', $description)->first(); + + if($invoice) + $transaction['invocie_id'] = $invoice->hashed_id; + + return $transaction; + } + +} From 80abcda83146d43ecdd28f63990a256d70af11e0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 Aug 2022 19:45:06 +1000 Subject: [PATCH 025/169] Working on data matching --- tests/Feature/Bank/YodleeApiTest.php | 48 ++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index 74ce98f6fc8d..29aff207bc53 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -29,6 +29,52 @@ class YodleeApiTest extends TestCase } + public function testDataMatching() + { + + $transaction = collect([ + (object)[ + 'description' => 'tinkertonkton' + ], + (object)[ + 'description' => 'spud' + ], + ]); + + $this->assertEquals(2, $transaction->count()); + + $hit = $transaction->where('description', 'spud')->first(); + + $this->assertNotNull($hit); + + $hit = $transaction->where('description', 'tinkertonkton')->first(); + + $this->assertNotNull($hit); + + $hit = $transaction->contains('description', 'tinkertonkton'); + + $this->assertTrue($hit); + + + $transaction = collect([ + (object)[ + 'description' => 'tinker and spice' + ], + (object)[ + 'description' => 'spud with water' + ], + ]); + + $hit = $transaction->contains('description', 'tinker and spice'); + + $this->assertTrue($hit); + + $transaction->contains(function ($value, $key) { + return str_contains($value->description, 'tinker'); + }); + + } + public function testYodleeInstance() { @@ -435,4 +481,6 @@ class YodleeApiTest extends TestCase + + } From 957cc727f192055274e3c77ed63132d2dd22b540 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 9 Aug 2022 12:21:06 +1000 Subject: [PATCH 026/169] Data matching --- app/Services/Bank/BankService.php | 6 +++++- tests/Feature/Bank/YodleeApiTest.php | 14 +++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/Services/Bank/BankService.php b/app/Services/Bank/BankService.php index f22946b87e8d..9514a0c14c85 100644 --- a/app/Services/Bank/BankService.php +++ b/app/Services/Bank/BankService.php @@ -50,7 +50,11 @@ class BankService { $description = str_replace(" ", "", $transaction->description); - $invoice = $this->invoices->where('number', $description)->first(); + $invoice = $this->invoices->first(function ($value, $key) use ($description){ + + return str_contains($value->number, $description); + + }); if($invoice) $transaction['invocie_id'] = $invoice->hashed_id; diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index 29aff207bc53..91fcc9e3f633 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -33,13 +33,13 @@ class YodleeApiTest extends TestCase { $transaction = collect([ - (object)[ - 'description' => 'tinkertonkton' - ], - (object)[ - 'description' => 'spud' - ], - ]); + (object)[ + 'description' => 'tinkertonkton' + ], + (object)[ + 'description' => 'spud' + ], + ]); $this->assertEquals(2, $transaction->count()); From d80ec520f967a203887b9fe7b3d5295a3c22493a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 10 Aug 2022 11:56:46 +1000 Subject: [PATCH 027/169] Fast link provisioning --- .../Yodlee/Transformer/AccountTransformer.php | 5 +++ app/Helpers/Bank/Yodlee/Yodlee.php | 28 ++++++++++----- .../Controllers/Bank/YodleeController.php | 34 ++++++++++++++++--- app/Http/Livewire/BillingPortalPurchase.php | 21 ++++++++++-- app/Jobs/Mail/NinjaMailer.php | 5 +-- app/Jobs/Util/ReminderJob.php | 2 +- app/Mail/Engine/InvoiceEmailEngine.php | 1 - app/Services/Invoice/UpdateReminder.php | 20 +++++------ .../2022_08_05_023357_bank_integration.php | 23 ++++++++++++- resources/views/bank/yodlee/auth.blade.php | 4 +-- tests/Feature/Bank/YodleeApiTest.php | 10 ++++++ 11 files changed, 118 insertions(+), 35 deletions(-) diff --git a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php index 7065a2e8abc1..d45eb6b60f43 100644 --- a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php +++ b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php @@ -61,6 +61,7 @@ use App\Helpers\Bank\AccountTransformerInterface; ) */ + class AccountTransformer implements AccountTransformerInterface { @@ -86,6 +87,10 @@ class AccountTransformer implements AccountTransformerInterface 'account_name' => $account->accountName, 'account_status' => $account->accountStatus, 'account_number' => $account->accountNumber, + 'provider_account_id' => $account->providerAccountId, + 'provider_id' => $account->providerId, + 'provider_name' => $account->providerName, + '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/Yodlee/Yodlee.php b/app/Helpers/Bank/Yodlee/Yodlee.php index fa905d16737a..e67ae4eb7b3e 100644 --- a/app/Helpers/Bank/Yodlee/Yodlee.php +++ b/app/Helpers/Bank/Yodlee/Yodlee.php @@ -14,6 +14,7 @@ namespace App\Helpers\Bank\Yodlee; use App\Helpers\Bank\Yodlee\Transformer\AccountTransformer; use App\Helpers\Bank\Yodlee\Transformer\IncomeTransformer; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Str; class Yodlee { @@ -22,9 +23,15 @@ class Yodlee private string $api_endpoint = 'https://production.api.yodlee.com/ysl'; - private string $test_api_endpoint = 'https://sandbox.api.yodlee.com/ysl'; + // private string $test_api_endpoint = 'https://sandbox.api.yodlee.com/ysl'; + + private string $test_api_endpoint = 'https://development.api.yodlee.com/ysl'; - public string $fast_track_url = 'https://fl4.sandbox.yodlee.com/authenticate/restserver/fastlink'; + //public string $test_fast_track_url = 'https://fl4.sandbox.yodlee.com/authenticate/restserver/fastlink'; + + public string $test_fast_track_url = 'https://fl4.preprod.yodlee.com/authenticate/USDevexPreProd3-449/fastlink?channelAppName=usdevexpreprod3'; + + public string $production_track_url = 'https://fl4.prod.yodlee.com/authenticate/USDevexProd3-331/fastlink?channelAppName=usdevexprod3'; protected string $client_id; @@ -47,6 +54,11 @@ class Yodlee } + public function getFastTrackUrl() + { + return $this->test_mode ? $this->test_fast_track_url : $this->production_track_url; + } + public function setTestMode() { $this->test_mode = true; @@ -56,9 +68,7 @@ class Yodlee public function getEndpoint() { - return $this->test_mode ? $this->test_api_endpoint : $this->api_endpoint; - } /** @@ -73,19 +83,19 @@ class Yodlee $user = $this->bank_account_id ?: $this->admin_name; $response = $this->bankFormRequest('/auth/token', 'post', [], ['loginName' => $user]); -//catch failures here - // nlog($response); + //catch failures here + nlog($response); return $response->token->accessToken; } - public function createUser() + public function createUser($company) { $token = $this->getAccessToken(true); $user['user'] = [ - 'loginName' => 'test123', + 'loginName' => Str::uuid(), ]; /* @@ -139,10 +149,10 @@ class Yodlee $response = Http::withHeaders($this->getHeaders(["Authorization" => "Bearer {$token}"]))->get($this->getEndpoint(). "/accounts", $params); - if($response->successful()){ $at = new AccountTransformer(); + nlog($response->object()); return $at->transform($response->object()); // return $response->object(); } diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index f90f2f2dff8f..03751bbc5879 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -13,12 +13,13 @@ namespace App\Http\Controllers\Bank; use App\Helpers\Bank\Yodlee\Yodlee; use App\Http\Controllers\BaseController; +use App\Http\Requests\Yodlee\YodleeAuthRequest; use Illuminate\Http\Request; class YodleeController extends BaseController { - public function auth(Request $request) + public function auth(YodleeAuthRequest $request) { // create a user at this point @@ -26,11 +27,36 @@ class YodleeController extends BaseController //store the user_account_id on the accounts table - $yodlee = new Yodlee(true); + $yodlee = new Yodlee(); + $yodlee->setTestMode(); + + $company = $request->getCompany(); + + 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); + $yodlee->setTestMode(); + } + + if(!is_string($token)) + dd($token); $data = [ - 'access_token' => $yodlee->getAccessToken('sbMem62e1e69547bfb1'), - 'fasttrack_url' => $yodlee->fast_track_url + 'access_token' => $yodlee->getAccessToken(), + 'fasttrack_url' => $yodlee->getFastTrackUrl(), + 'config_name' => 'testninja', + 'flow' => $flow, ]; return view('bank.yodlee.auth', $data); diff --git a/app/Http/Livewire/BillingPortalPurchase.php b/app/Http/Livewire/BillingPortalPurchase.php index ebe0ca6c2688..e4cfaaeef984 100644 --- a/app/Http/Livewire/BillingPortalPurchase.php +++ b/app/Http/Livewire/BillingPortalPurchase.php @@ -98,6 +98,8 @@ class BillingPortalPurchase extends Component */ public $payment_method_id; + private $user_coupon; + /** * List of steps that frontend form follows. * @@ -436,32 +438,45 @@ class BillingPortalPurchase extends Component */ public function updateQuantity(string $option): int { + $this->handleCoupon(); + if ($this->quantity == 1 && $option == 'decrement') { + $this->price = $this->price * 1; return $this->quantity; } - if ($this->quantity >= $this->subscription->max_seats_limit && $option == 'increment') { + if ($this->quantity > $this->subscription->max_seats_limit && $option == 'increment') { + $this->price = $this->price * $this->subscription->max_seats_limit; return $this->quantity; } if ($option == 'increment') { $this->quantity++; - $this->price = $this->subscription->promo_price * $this->quantity; + $this->price = $this->price * $this->quantity; return $this->quantity; } $this->quantity--; - $this->price = $this->subscription->promo_price * $this->quantity; + $this->price = $this->price * $this->quantity; return $this->quantity; } public function handleCoupon() { + + if($this->steps['discount_applied']){ + $this->price = $this->subscription->promo_price; + return; + } + if ($this->coupon == $this->subscription->promo_code) { $this->price = $this->subscription->promo_price; + $this->quantity = 1; $this->steps['discount_applied'] = true; } + else + $this->price = $this->subscription->price; } public function passwordlessLogin() diff --git a/app/Jobs/Mail/NinjaMailer.php b/app/Jobs/Mail/NinjaMailer.php index 726327dba855..0a0edbd21a2b 100644 --- a/app/Jobs/Mail/NinjaMailer.php +++ b/app/Jobs/Mail/NinjaMailer.php @@ -42,10 +42,7 @@ class NinjaMailer extends Mailable $ninja_mailable = $this->from(config('mail.from.address'), $from_name) ->subject($this->mail_obj->subject) - ->view($this->mail_obj->markdown, $this->mail_obj->data) - ->withSymfonyMessage(function ($message) { - $message->getHeaders()->addTextHeader('Tag', $this->mail_obj->tag); - }); + ->view($this->mail_obj->markdown, $this->mail_obj->data); if (property_exists($this->mail_obj, 'text_view')) { $ninja_mailable->text($this->mail_obj->text_view, $this->mail_obj->data); diff --git a/app/Jobs/Util/ReminderJob.php b/app/Jobs/Util/ReminderJob.php index 4aef5a9a3a60..29f5382e4111 100644 --- a/app/Jobs/Util/ReminderJob.php +++ b/app/Jobs/Util/ReminderJob.php @@ -95,7 +95,7 @@ class ReminderJob implements ShouldQueue (Ninja::isSelfHost() || $invoice->company->account->isPaidHostedClient())) { $invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) { EmailEntity::dispatch($invitation, $invitation->company, $reminder_template); - nlog("Firing reminder email for invoice {$invoice->number}"); + nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}"); }); if ($invoice->invitations->count() > 0) { diff --git a/app/Mail/Engine/InvoiceEmailEngine.php b/app/Mail/Engine/InvoiceEmailEngine.php index b62f402ec0dc..000d2f72748f 100644 --- a/app/Mail/Engine/InvoiceEmailEngine.php +++ b/app/Mail/Engine/InvoiceEmailEngine.php @@ -100,7 +100,6 @@ class InvoiceEmailEngine extends BaseEmailEngine $subject_template = $this->client->getSetting('email_subject_'.$this->reminder_template); } else { $subject_template = EmailTemplateDefaults::getDefaultTemplate('email_subject_'.$this->reminder_template, $this->client->locale()); - // $subject_template = $this->client->getSetting('email_subject_'.$this->reminder_template); } if (iconv_strlen($subject_template) == 0) { diff --git a/app/Services/Invoice/UpdateReminder.php b/app/Services/Invoice/UpdateReminder.php index 642c36b53283..e324613bbea6 100644 --- a/app/Services/Invoice/UpdateReminder.php +++ b/app/Services/Invoice/UpdateReminder.php @@ -51,7 +51,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder1_sent) && $this->settings->schedule_reminder1 == 'after_invoice_date') { - $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder1); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -61,7 +61,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder1_sent) && $this->invoice->due_date && $this->settings->schedule_reminder1 == 'before_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder1)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder1); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -71,7 +71,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder1_sent) && $this->invoice->due_date && $this->settings->schedule_reminder1 == 'after_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder1); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -80,7 +80,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder2_sent) && $this->settings->schedule_reminder2 == 'after_invoice_date') { - $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder2); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -90,7 +90,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder2_sent) && $this->invoice->due_date && $this->settings->schedule_reminder2 == 'before_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder2)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder2); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -100,7 +100,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder2_sent) && $this->invoice->due_date && $this->settings->schedule_reminder2 == 'after_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder2); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -109,7 +109,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder3_sent) && $this->settings->schedule_reminder3 == 'after_invoice_date') { - $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder3); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -119,7 +119,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder3_sent) && $this->invoice->due_date && $this->settings->schedule_reminder3 == 'before_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder3)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder3); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -129,7 +129,7 @@ class UpdateReminder extends AbstractService if (is_null($this->invoice->reminder3_sent) && $this->invoice->due_date && $this->settings->schedule_reminder3 == 'after_due_date') { - $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset); + $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder3); if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) { $date_collection->push($reminder_date); @@ -150,7 +150,7 @@ class UpdateReminder extends AbstractService } if ($date_collection->count() >= 1 && $date_collection->sort()->first()->gte(now())) { - $this->invoice->next_send_date = $date_collection->sort()->first(); + $this->invoice->next_send_date = $date_collection->sort()->first()->addSeconds($offset); } else { $this->invoice->next_send_date = null; } diff --git a/database/migrations/2022_08_05_023357_bank_integration.php b/database/migrations/2022_08_05_023357_bank_integration.php index e3f0c326930e..4c9564945f0f 100644 --- a/database/migrations/2022_08_05_023357_bank_integration.php +++ b/database/migrations/2022_08_05_023357_bank_integration.php @@ -40,6 +40,27 @@ return new class extends Migration Schema::table('accounts', function (Blueprint $table) { $table->text('bank_integration_account_id')->nullable(); }); + + Schema::create('bank_transactions', function (Blueprint $table){ + $table->id(); + $table->unsignedInteger('company_id'); + $table->unsignedInteger('user_id'); + + $table->unsignedBigInteger('transaction_id')->nullable(); + $table->decimal('amount', 20, 6); + $table->string('currency_code'); + $table->string('account_type'); + $table->unsignedInteger('category_id'); + $table->string('category_type'); + $table->date('date'); + $table->unsignedBigInteger('account_id'); + $table->text('description'); + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade'); + + }); + } /** @@ -49,6 +70,6 @@ return new class extends Migration */ public function down() { - // + } }; diff --git a/resources/views/bank/yodlee/auth.blade.php b/resources/views/bank/yodlee/auth.blade.php index ffc005d53d95..ac970ac3c64a 100644 --- a/resources/views/bank/yodlee/auth.blade.php +++ b/resources/views/bank/yodlee/auth.blade.php @@ -27,11 +27,11 @@ 'click', function() { window.fastlink.open({ - flow: 'edit',//flow changes depending on what we are doing sometimes it could be add/edit etc etc + flow: '{{ $flow }}',//flow changes depending on what we are doing sometimes it could be add/edit etc etc fastLinkURL: '{{ $fasttrack_url }}', accessToken: 'Bearer {{ $access_token }}', params: { - configName : 'Aggregation' + configName : '{{ $config_name }}' }, onSuccess: function (data) { // will be called on success. For list of possible message, refer to onSuccess(data) Method. diff --git a/tests/Feature/Bank/YodleeApiTest.php b/tests/Feature/Bank/YodleeApiTest.php index 91fcc9e3f633..4cc37cb00bb9 100644 --- a/tests/Feature/Bank/YodleeApiTest.php +++ b/tests/Feature/Bank/YodleeApiTest.php @@ -73,6 +73,16 @@ class YodleeApiTest extends TestCase return str_contains($value->description, 'tinker'); }); + + $invoice = $transaction->first(function ($value, $key) { + + return str_contains($value->number, 'tinker'); + + }); + + $this->assertNotNull($invoice); + + } public function testYodleeInstance() From b4719201555f112781780b5d52ca0a827bca4fbc Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 10 Aug 2022 19:49:27 +1000 Subject: [PATCH 028/169] Clean up for Yodlee authentication --- .../Controllers/Bank/YodleeController.php | 7 +- ...09_091028_licenses_table_for_self_host.php | 31 +++++++ resources/views/bank/yodlee/auth.blade.php | 86 +++++++++++++++++-- 3 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 database/migrations/2022_08_09_091028_licenses_table_for_self_host.php diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index 03751bbc5879..96b872044f13 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -45,9 +45,10 @@ class YodleeController extends BaseController $company->account->bank_integration_account_id = $token; $company->push(); - $yodlee = new Yodlee($token); - $yodlee->setTestMode(); } + + $yodlee = new Yodlee($token); + $yodlee->setTestMode(); if(!is_string($token)) dd($token); @@ -57,6 +58,8 @@ class YodleeController extends BaseController 'fasttrack_url' => $yodlee->getFastTrackUrl(), 'config_name' => 'testninja', 'flow' => $flow, + 'company' => $company, + 'account' => $company->account, ]; return view('bank.yodlee.auth', $data); diff --git a/database/migrations/2022_08_09_091028_licenses_table_for_self_host.php b/database/migrations/2022_08_09_091028_licenses_table_for_self_host.php new file mode 100644 index 000000000000..f55b5004c02d --- /dev/null +++ b/database/migrations/2022_08_09_091028_licenses_table_for_self_host.php @@ -0,0 +1,31 @@ +unsignedBigInteger('recurring_invoice_id')->nullable(); + }); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +}; diff --git a/resources/views/bank/yodlee/auth.blade.php b/resources/views/bank/yodlee/auth.blade.php index ac970ac3c64a..f0adbf33558f 100644 --- a/resources/views/bank/yodlee/auth.blade.php +++ b/resources/views/bank/yodlee/auth.blade.php @@ -1,25 +1,93 @@ @extends('layouts.ninja') @section('meta_title', ctrans('texts.new_bank_account')) - @push('head') + + + + @endpush @section('body') -