From b24d8431648129ee7081d8ff6d9d5b25725a6d3f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 23 Aug 2024 14:17:37 +1000 Subject: [PATCH] Updates for QB --- .../ImportQuickbooksController.php | 64 +++-- .../Import/Quickbooks/CustomerRepository.php | 11 - .../Import/Quickbooks/InvoiceRepository.php | 10 - .../Import/Quickbooks/ItemRepository.php | 11 - .../Import/Quickbooks/PaymentRepository.php | 10 - .../Import/Quickbooks/Repository.php | 38 --- app/Services/Import/Quickbooks/Auth.php | 64 ----- .../Quickbooks/Contracts/SdkInterface.php | 14 -- .../Import/Quickbooks/QuickbooksService.php | 45 ++-- .../Repositories/CompanyTokensRepository.php | 78 ------- app/Services/Import/Quickbooks/SdkWrapper.php | 218 ++++++++++++------ app/Services/Import/Quickbooks/Service.php | 89 ------- routes/web.php | 1 - 13 files changed, 199 insertions(+), 454 deletions(-) delete mode 100644 app/Repositories/Import/Quickbooks/CustomerRepository.php delete mode 100644 app/Repositories/Import/Quickbooks/InvoiceRepository.php delete mode 100644 app/Repositories/Import/Quickbooks/ItemRepository.php delete mode 100644 app/Repositories/Import/Quickbooks/PaymentRepository.php delete mode 100644 app/Repositories/Import/Quickbooks/Repository.php delete mode 100644 app/Services/Import/Quickbooks/Auth.php delete mode 100644 app/Services/Import/Quickbooks/Contracts/SdkInterface.php delete mode 100644 app/Services/Import/Quickbooks/Repositories/CompanyTokensRepository.php delete mode 100644 app/Services/Import/Quickbooks/Service.php diff --git a/app/Http/Controllers/ImportQuickbooksController.php b/app/Http/Controllers/ImportQuickbooksController.php index 8f93651a7a25..9030d3c33a01 100644 --- a/app/Http/Controllers/ImportQuickbooksController.php +++ b/app/Http/Controllers/ImportQuickbooksController.php @@ -3,28 +3,19 @@ namespace App\Http\Controllers; use App\Http\Requests\Quickbooks\AuthorizedQuickbooksRequest; -use Closure; -use App\Utils\Ninja; -use App\Models\Company; use App\Libraries\MultiDB; -use Illuminate\Support\Str; -use Illuminate\Http\Request; -use Illuminate\Http\Response; -use App\Utils\Traits\MakesHash; -use App\Jobs\Import\QuickbooksIngest; use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\Validator; use App\Http\Requests\Quickbooks\AuthQuickbooksRequest; use App\Services\Import\Quickbooks\QuickbooksService; class ImportQuickbooksController extends BaseController { - private array $import_entities = [ - 'client' => 'Customer', - 'invoice' => 'Invoice', - 'product' => 'Item', - 'payment' => 'Payment' - ]; + // private array $import_entities = [ + // 'client' => 'Customer', + // 'invoice' => 'Invoice', + // 'product' => 'Item', + // 'payment' => 'Payment' + // ]; public function onAuthorized(AuthorizedQuickbooksRequest $request) { @@ -34,12 +25,11 @@ class ImportQuickbooksController extends BaseController $qb = new QuickbooksService($company); $realm = $request->query('realmId'); - $access_token_object = $qb->getAuth()->accessToken($request->query('code'), $realm); - nlog($access_token_object); //OAuth2AccessToken - $company->quickbooks = $access_token_object; - $company->save(); + $access_token_object = $qb->getAuth()->accessTokenFromCode($request->query('code'), $realm); + $qb->getAuth()->saveOAuthToken($access_token_object); + + return redirect(config('ninja.react_url')); - return response()->json(['message' => 'Success'], 200); //todo swapout for redirect to UI } /** @@ -63,29 +53,29 @@ class ImportQuickbooksController extends BaseController public function preimport(string $type, string $hash) { - // Check for authorization otherwise - // Create a reference - $data = [ - 'hash' => $hash, - 'type' => $type - ]; - $this->getData($data); + // // Check for authorization otherwise + // // Create a reference + // $data = [ + // 'hash' => $hash, + // 'type' => $type + // ]; + // $this->getData($data); } protected function getData($data) { - $entity = $this->import_entities[$data['type']]; - $cache_name = "{$data['hash']}-{$data['type']}"; - // TODO: Get or put cache or DB? - if(! Cache::has($cache_name)) { - $contents = call_user_func([$this->service, "fetch{$entity}s"]); - if($contents->isEmpty()) { - return; - } + // $entity = $this->import_entities[$data['type']]; + // $cache_name = "{$data['hash']}-{$data['type']}"; + // // TODO: Get or put cache or DB? + // if(! Cache::has($cache_name)) { + // $contents = call_user_func([$this->service, "fetch{$entity}s"]); + // if($contents->isEmpty()) { + // return; + // } - Cache::put($cache_name, base64_encode($contents->toJson()), 600); - } + // Cache::put($cache_name, base64_encode($contents->toJson()), 600); + // } } /** diff --git a/app/Repositories/Import/Quickbooks/CustomerRepository.php b/app/Repositories/Import/Quickbooks/CustomerRepository.php deleted file mode 100644 index 68b71b75017a..000000000000 --- a/app/Repositories/Import/Quickbooks/CustomerRepository.php +++ /dev/null @@ -1,11 +0,0 @@ -db = $db; - $this->transformer = $transfomer; - } - - public function count(): int - { - return $this->db->totalRecords($this->entity); - } - - public function all(): Collection - { - return $this->get($this->count()); - } - - public function get(int $max = 100): Collection - { - return $this->transformer->transform($this->db->fetchRecords($this->entity, $max), $this->entity); - } - - -} diff --git a/app/Services/Import/Quickbooks/Auth.php b/app/Services/Import/Quickbooks/Auth.php deleted file mode 100644 index edc3d605a8c0..000000000000 --- a/app/Services/Import/Quickbooks/Auth.php +++ /dev/null @@ -1,64 +0,0 @@ -sdk->accessToken($code, $realm); - } - - public function refreshToken(): array - { - // TODO: Get or put token in Cache or DB? - return $this->sdk->refreshToken(); - } - - public function getAuthorizationUrl(): string - { - return $this->sdk->getAuthorizationUrl(); - } - - public function getState(): string - { - return $this->sdk->getState(); - } - - public function getAccessToken(): array - { - $tokens = []; - // $token_store = new CompanyTokensRepository(); - // $tokens = $token_store->get(); - // if(empty($tokens)) { - // $token = $this->sdk->getAccessToken(); - // $access_token = $token->getAccessToken(); - // $realm = $token->getRealmID(); - // $refresh_token = $token->getRefreshToken(); - // $access_token_expires = $token->getAccessTokenExpiresAt(); - // $refresh_token_expires = $token->getRefreshTokenExpiresAt(); - // $tokens = compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires','realm'); - // } - - return $tokens; - } - - public function getRefreshToken(): array - { - return $this->getAccessToken(); - } -} diff --git a/app/Services/Import/Quickbooks/Contracts/SdkInterface.php b/app/Services/Import/Quickbooks/Contracts/SdkInterface.php deleted file mode 100644 index 48ed4b32e84f..000000000000 --- a/app/Services/Import/Quickbooks/Contracts/SdkInterface.php +++ /dev/null @@ -1,14 +0,0 @@ -init() - ->auth(); + $this->init(); } private function init(): self { - $this->sdk = DataService::Configure([ + $config = [ 'ClientID' => config('services.quickbooks.client_id'), 'ClientSecret' => config('services.quickbooks.client_secret'), 'auth_mode' => 'oauth2', 'scope' => "com.intuit.quickbooks.accounting", - 'RedirectURI' => 'https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl', - // 'RedirectURI' => route('quickbooks.authorized'), - ]); + // 'RedirectURI' => 'https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl', + 'RedirectURI' => 'https://above-distinctly-teal.ngrok-free.app/quickbooks/authorized', + 'baseUrl' => $this->testMode ? CoreConstants::SANDBOX_DEVELOPMENT : CoreConstants::QBO_BASEURL, + ]; - // if (env('APP_DEBUG')) { - // $sdk->setLogLocation(storage_path("logs/quickbooks.log")); - // $sdk->enableLog(); - // } + $merged = array_merge($config, $this->ninjaAccessToken()); + nlog($merged); + $this->sdk = DataService::Configure($merged); + + $this->sdk->setLogLocation(storage_path("logs/quickbooks.log")); + $this->sdk->enableLog(); $this->sdk->setMinorVersion("73"); $this->sdk->throwExceptionOnError(true); @@ -52,12 +57,13 @@ class QuickbooksService return $this; } - private function auth(): self + private function ninjaAccessToken() { - $wrapper = new SdkWrapper($this->sdk); - $this->auth = new Auth($wrapper); - - return $this; + return isset($this->company->quickbooks->accessTokenKey) ? [ + 'accessTokenKey' => $this->company->quickbooks->accessTokenKey, + 'refreshTokenKey' => $this->company->quickbooks->refresh_token, + 'QBORealmID' => $this->company->quickbooks->realmID, + ] : []; } public function getSdk(): DataService @@ -65,8 +71,9 @@ class QuickbooksService return $this->sdk; } - public function getAuth(): Auth + public function getAuth(): SdkWrapper { - return $this->auth; + return new SdkWrapper($this->sdk, $this->company); } + } diff --git a/app/Services/Import/Quickbooks/Repositories/CompanyTokensRepository.php b/app/Services/Import/Quickbooks/Repositories/CompanyTokensRepository.php deleted file mode 100644 index 92aef0c8e9e3..000000000000 --- a/app/Services/Import/Quickbooks/Repositories/CompanyTokensRepository.php +++ /dev/null @@ -1,78 +0,0 @@ -company_key = $key ?? auth()->user->company()->company_key ?? null; - $this->store_key .= $key; - $this->setCompanyDbByKey(); - } - - public function save(array $tokens) - { - $this->updateAccessToken($tokens['access_token'], $tokens['access_token_expires']); - $this->updateRefreshToken($tokens['refresh_token'], $tokens['refresh_token_expires'], $tokens['realm']); - } - - - public function findByCompanyKey(): ?Company - { - return Company::where('company_key', $this->company_key)->first(); - } - - public function setCompanyDbByKey() - { - MultiDB::findAndSetDbByCompanyKey($this->company_key); - } - - public function get() - { - return $this->getAccessToken() + $this->getRefreshToken(); - } - - - protected function updateRefreshToken(string $token, string $expires, string $realm) - { - DB::table('companies') - ->where('company_key', $this->company_key) - ->update(['quickbooks_refresh_token' => $token, - 'quickbooks_realm_id' => $realm, - 'quickbooks_refresh_expires' => $expires ]); - } - - protected function updateAccessToken(string $token, string $expires) - { - - Cache::put([$this->store_key => $token], $expires); - } - - protected function getAccessToken() - { - $result = Cache::get($this->store_key); - - return $result ? ['access_token' => $result] : []; - } - - protected function getRefreshToken() - { - $result = (array) DB::table('companies') - ->select('quickbooks_refresh_token', 'quickbooks_realm_id') - ->where('company_key', $this->company_key) - ->where('quickbooks_refresh_expires', '>', now()) - ->first(); - - return $result ? array_combine(['refresh_token','realm'], array_values($result)) : []; - } - -} diff --git a/app/Services/Import/Quickbooks/SdkWrapper.php b/app/Services/Import/Quickbooks/SdkWrapper.php index 1cd8210c48ae..34fa51509a13 100644 --- a/app/Services/Import/Quickbooks/SdkWrapper.php +++ b/app/Services/Import/Quickbooks/SdkWrapper.php @@ -2,77 +2,151 @@ namespace App\Services\Import\Quickbooks; -use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface; +use Carbon\Carbon; +use App\Models\Company; +use QuickBooksOnline\API\DataService\DataService; +use QuickBooksOnline\API\Core\OAuth\OAuth2\OAuth2AccessToken; -final class SdkWrapper implements QuickbooksInterface +class SdkWrapper { public const MAXRESULTS = 10000; - private $sdk; private $entities = ['Customer','Invoice','Payment','Item']; - public function __construct($sdk) + private OAuth2AccessToken $token; + + public function __construct(public DataService $sdk, private Company $company) { - // Prep Data Services - $this->sdk = $sdk; + $this->init(); + } + + private function init(): self + { + + isset($this->company->quickbooks->accessTokenKey) ? $this->setNinjaAccessToken($this->company->quickbooks) : null; + + return $this; + } public function getAuthorizationUrl(): string { - return ($this->sdk->getOAuth2LoginHelper())->getAuthorizationCodeURL(); + return $this->sdk->getOAuth2LoginHelper()->getAuthorizationCodeURL(); } public function getState(): string { - return ($this->sdk->getOAuth2LoginHelper())->getState(); + return $this->sdk->getOAuth2LoginHelper()->getState(); } - public function getAccessToken() + public function getRefreshToken(): string { - return $this->getTokens(); + return $this->accessToken()->getRefreshToken(); + } + + public function company() + { + nlog("getting company info"); + // nlog($this->sdk->getAccessToken()); + + return $this->sdk->getCompanyInfo(); + } + /* + accessTokenKey + tokenType + refresh_token + accessTokenExpiresAt + refreshTokenExpiresAt + accessTokenValidationPeriod + refreshTokenValidationPeriod + clientID + clientSecret + realmID + baseURL + */ + public function accessTokenFromCode(string $code, string $realm): OAuth2AccessToken + { + $token = $this->sdk->getOAuth2LoginHelper()->exchangeAuthorizationCodeForToken($code, $realm); + + $this->setAccessToken($token); + + return $this->accessToken(); + } + + /** + * Set Stored NinjaAccessToken + * + * @param mixed $token_object + * @return self + */ + public function setNinjaAccessToken(mixed $token_object): self + { + $token = new OAuth2AccessToken( + config('services.quickbooks.client_id'), + config('services.quickbooks.client_secret'), + $token_object->accessTokenKey, + $token_object->refresh_token, + 3600, + 8726400 + ); + + $token->setAccessTokenExpiresAt($token_object->accessTokenExpiresAt); + $token->setRefreshTokenExpiresAt($token_object->refreshTokenExpiresAt); + $token->setAccessTokenValidationPeriodInSeconds(3600); + $token->setRefreshTokenValidationPeriodInSeconds(8726400); + + $this->setAccessToken($token); + + if($token_object->accessTokenExpiresAt < time()){ + $new_token = $this->sdk->getOAuth2LoginHelper()->refreshToken(); +nlog("getting new token"); + $this->setAccessToken($new_token); + $this->saveOAuthToken($this->accessToken()); + } + + return $this; + } + + /** + * SetsAccessToken + * + * @param OAuth2AccessToken $token + * @return self + */ + public function setAccessToken(OAuth2AccessToken $token): self + { + // $this->sdk = $this->sdk->updateOAuth2Token($token); + + $this->token = $token; +nlog("set access token"); + +nlog($token); + return $this; } - public function getRefreshToken(): array + public function accessToken(): OAuth2AccessToken { - return $this->getTokens(); + return $this->token; } - public function accessToken(string $code, string $realm): array + public function saveOAuthToken(OAuth2AccessToken $token): void { - $token = ($this->sdk->getOAuth2LoginHelper())->exchangeAuthorizationCodeForToken($code, $realm); + $obj = new \stdClass(); + $obj->accessTokenKey = $token->getAccessToken(); + $obj->refresh_token = $token->getRefreshToken(); + $obj->accessTokenExpiresAt = Carbon::createFromFormat('Y/m/d H:i:s', $token->getAccessTokenExpiresAt())->timestamp; //@phpstan-ignore-line - QB phpdoc wrong types!! + $obj->refreshTokenExpiresAt = Carbon::createFromFormat('Y/m/d H:i:s', $token->getRefreshTokenExpiresAt())->timestamp; //@phpstan-ignore-line - QB phpdoc wrong types!! - return $this->getTokens(); + $obj->realmID = $token->getRealmID(); + $obj->baseURL = $token->getBaseURL(); + + $this->company->quickbooks = $obj; + $this->company->save(); } - private function getTokens() - { - - $token = ($this->sdk->getOAuth2LoginHelper())->getAccessToken(); - return $token; - - // $access_token = $token->getAccessToken(); - // $refresh_token = $token->getRefreshToken(); - // $access_token_expires = $token->getAccessTokenExpiresAt(); - // $refresh_token_expires = $token->getRefreshTokenExpiresAt(); - - // return compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires'); - } - - public function refreshToken(): array - { - $token = ($this->sdk->getOAuth2LoginHelper())->refreshToken(); - $this->sdk = $this->sdk->updateOAuth2Token($token); - - return $this->getTokens(); - } - - public function handleCallbacks(array $data): void - { - - } - - public function totalRecords(string $entity): int + public function totalRecords(string $entity) //returns an array not int { + nlog($this->sdk->Query("select count(*) from $entity")); return $this->sdk->Query("select count(*) from $entity"); } @@ -81,39 +155,39 @@ final class SdkWrapper implements QuickbooksInterface return (array) $this->sdk->Query($query, $start, $limit); } - public function fetchRecords(string $entity, int $max = 1000): array - { + // public function fetchRecords(string $entity, int $max = 1000): array + // { - if(!in_array($entity, $this->entities)) { - return []; - } + // if(!in_array($entity, $this->entities)) { + // return []; + // } - $records = []; - $start = 0; - $limit = 100; - try { - $total = $this->totalRecords($entity); - $total = min($max, $total); + // $records = []; + // $start = 0; + // $limit = 100; + // try { + // $total = $this->totalRecords($entity); + // $total = min($max, $total); - // Step 3 & 4: Get chunks of records until the total required records are retrieved - do { - $limit = min(self::MAXRESULTS, $total - $start); - $recordsChunk = $this->queryData("select * from $entity", $start, $limit); - if(empty($recordsChunk)) { - break; - } + // // Step 3 & 4: Get chunks of records until the total required records are retrieved + // do { + // $limit = min(self::MAXRESULTS, $total - $start); + // $recordsChunk = $this->queryData("select * from $entity", $start, $limit); + // if(empty($recordsChunk)) { + // break; + // } - $records = array_merge($records, $recordsChunk); - $start += $limit; - } while ($start < $total); - if(empty($records)) { - throw new \Exception("No records retrieved!"); - } + // $records = array_merge($records, $recordsChunk); + // $start += $limit; + // } while ($start < $total); + // if(empty($records)) { + // throw new \Exception("No records retrieved!"); + // } - } catch (\Throwable $th) { - nlog("Fetch Quickbooks API Error: {$th->getMessage()}"); - } + // } catch (\Throwable $th) { + // nlog("Fetch Quickbooks API Error: {$th->getMessage()}"); + // } - return $records; - } + // return $records; + // } } diff --git a/app/Services/Import/Quickbooks/Service.php b/app/Services/Import/Quickbooks/Service.php deleted file mode 100644 index 5002bd9e16af..000000000000 --- a/app/Services/Import/Quickbooks/Service.php +++ /dev/null @@ -1,89 +0,0 @@ -sdk = $quickbooks; - } - - public function getOAuth(): Auth - { - return new Auth($this->sdk); - } - - public function getAccessToken(): array - { - return $this->getOAuth()->getAccessToken(); - } - - public function getRefreshToken(): array - { - // TODO: Check if token is Cached otherwise fetch a new one and Cache token and expire - return $this->getAccessToken(); - } - /** - * fetch QuickBooks invoice records - * @param int $max The maximum records to fetch. Default 100 - * @return Illuminate\Support\Collection; - */ - public function fetchInvoices(int $max = 100): Collection - { - return $this->fetchRecords('Invoice', $max) ; - } - - /** - * fetch QuickBooks payment records - * @param int $max The maximum records to fetch. Default 100 - * @return Illuminate\Support\Collection; - */ - public function fetchPayments(int $max = 100): Collection - { - return $this->fetchRecords('Payment', $max) ; - } - - /** - * fetch QuickBooks product records - * @param int $max The maximum records to fetch. Default 100 - * @return Illuminate\Support\Collection; - */ - public function fetchItems(int $max = 100): Collection - { - return $this->fetchRecords('Item', $max) ; - } - - protected function fetchRecords(string $entity, $max = 100): Collection - { - return (self::RepositoryFactory($entity))->get($max); - } - - private static function RepositoryFactory(string $entity): RepositoryInterface - { - return app("\\App\\Repositories\\Import\Quickbooks\\{$entity}Repository"); - } - - /** - * fetch QuickBooks customer records - * @param int $max The maximum records to fetch. Default 100 - * @return Illuminate\Support\Collection; - */ - public function fetchCustomers(int $max = 100): Collection - { - return $this->fetchRecords('Customer', $max) ; - } - - public function totalRecords(string $entity): int - { - return (self::RepositoryFactory($entity))->count(); - } -} diff --git a/routes/web.php b/routes/web.php index 4f22c1181435..c940d0fb1d8c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,7 +6,6 @@ use App\Http\Controllers\Auth\ResetPasswordController; use App\Http\Controllers\Bank\NordigenController; use App\Http\Controllers\Bank\YodleeController; use App\Http\Controllers\BaseController; -use App\Http\Controllers\ImportQuickbooksController; use App\Http\Controllers\ClientPortal\ApplePayDomainController; use App\Http\Controllers\Gateways\Checkout3dsController; use App\Http\Controllers\Gateways\GoCardlessController;