diff --git a/app/Factory/QuickbooksSDKFactory.php b/app/Factory/QuickbooksSDKFactory.php deleted file mode 100644 index fcd9ebf70c..0000000000 --- a/app/Factory/QuickbooksSDKFactory.php +++ /dev/null @@ -1,63 +0,0 @@ -company(); - - $token_store = (new CompanyTokensRepository($company->company_key)); - $tokens = array_filter($token_store->get()); - if(!empty($tokens)) { - $keys = ['refreshTokenKey','QBORealmID']; - if(array_key_exists('access_token', $tokens)) { - $keys = array_merge(['accessTokenKey'] ,$keys); - } - - $tokens = array_combine($keys, array_values($tokens)); - } - } - - $config = $tokens + config('services.quickbooks.settings') + [ - 'state' => Str::random(12) - ]; - $sdk = DataService::Configure($config); - if (env('APP_DEBUG')) { - $sdk->setLogLocation(storage_path("logs/quickbooks.log")); - $sdk->enableLog(); - } - - $sdk->setMinorVersion("73"); - $sdk->throwExceptionOnError(true); - if(array_key_exists('refreshTokenKey', $config) && !array_key_exists('accessTokenKey', $config)) - { - $tokens = ($sdk->getOAuth2LoginHelper())->refreshToken(); - $sdk = $sdk->updateOAuth2Token($tokens); - $tokens = ($sdk->getOAuth2LoginHelper())->getAccessToken(); - $access_token = $tokens->getAccessToken(); - $realm = $tokens->getRealmID(); - $refresh_token = $tokens->getRefreshToken(); - $access_token_expires = $tokens->getAccessTokenExpiresAt(); - $refresh_token_expires = $tokens->getRefreshTokenExpiresAt(); - $tokens = compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires','realm'); - $token_store->save($tokens); - } - - return $sdk; - } -} diff --git a/app/Http/Controllers/ImportQuickbooksController.php b/app/Http/Controllers/ImportQuickbooksController.php index 7419de3e57..09e75dc81f 100644 --- a/app/Http/Controllers/ImportQuickbooksController.php +++ b/app/Http/Controllers/ImportQuickbooksController.php @@ -5,18 +5,21 @@ namespace App\Http\Controllers; 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 Illuminate\Support\Facades\Cache; use App\Jobs\Import\QuickbooksIngest; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Validator; -use App\Services\Import\Quickbooks\Service as QuickbooksService; +use App\Http\Requests\Quickbooks\AuthQuickbooksRequest; +use App\Services\Import\Quickbooks\QuickbooksService; class ImportQuickbooksController extends BaseController { - protected QuickbooksService $service; + // protected QuickbooksService $service; + private $import_entities = [ 'client' => 'Customer', 'invoice' => 'Invoice', @@ -24,79 +27,79 @@ class ImportQuickbooksController extends BaseController 'payment' => 'Payment' ]; - public function __construct(QuickbooksService $service) { - parent::__construct(); + // public function __construct(QuickbooksService $service) { + // parent::__construct(); - $this->service = $service; - $this->middleware( - function (Request $request, Closure $next) { + // $this->service = $service; + // $this->middleware( + // function (Request $request, Closure $next) { - // Check for the required query parameters - if (!$request->has(['code', 'state', 'realmId'])) { - return abort(400,'Unauthorized'); - } + // // Check for the required query parameters + // if (!$request->has(['code', 'state', 'realmId'])) { + // return abort(400,'Unauthorized'); + // } - $rules = [ - 'state' => [ - 'required', - 'valid' => function ($attribute, $value, $fail) { - if (!Cache::has($value)) { - $fail('The state is invalid.'); - } - }, - ] - ]; - // Custom error messages - $messages = [ - 'state.required' => 'The state is required.', - 'state.valid' => 'state token not valid' - ]; - // Perform the validation - $validator = Validator::make($request->all(), $rules, $messages); - if ($validator->fails()) { - // If validation fails, redirect back with errors and input - return redirect('/') - ->withErrors($validator) - ->withInput(); - } + // $rules = [ + // 'state' => [ + // 'required', + // 'valid' => function ($attribute, $value, $fail) { + // if (!Cache::has($value)) { + // $fail('The state is invalid.'); + // } + // }, + // ] + // ]; + // // Custom error messages + // $messages = [ + // 'state.required' => 'The state is required.', + // 'state.valid' => 'state token not valid' + // ]; + // // Perform the validation + // $validator = Validator::make($request->all(), $rules, $messages); + // if ($validator->fails()) { + // // If validation fails, redirect back with errors and input + // return redirect('/') + // ->withErrors($validator) + // ->withInput(); + // } - $token = Cache::pull($request->state); - $request->merge(['company' => Cache::get($token) ]); + // $token = Cache::pull($request->state); + // $request->merge(['company' => Cache::get($token) ]); - return $next($request); - } - )->only('onAuthorized'); - $this->middleware( - function ( Request $request, Closure $next) { - $rules = [ - 'token' => [ - 'required', - 'valid' => function ($attribute, $value, $fail) { - if (!Cache::has($value) || (!Company::where('company_key', (Cache::get($value))['company_key'])->exists() )) { - $fail('The company is invalid.'); - } - }, - ] - ]; - // Custom error messages - $messages = [ - 'token.required' => 'The token is required.', - 'token.valid' => 'Token note valid!' - ]; - // Perform the validation - $validator = Validator::make(['token' => $request->token ], $rules, $messages); - if ($validator->fails()) { - return redirect() - ->back() - ->withErrors($validator) - ->withInput(); - } + // return $next($request); + // } + // )->only('onAuthorized'); + // $this->middleware( + // function ( Request $request, Closure $next) { + // $rules = [ + // 'token' => [ + // 'required', + // 'valid' => function ($attribute, $value, $fail) { + // if (!Cache::has($value) || (!Company::where('company_key', (Cache::get($value))['company_key'])->exists() )) { + // $fail('The company is invalid.'); + // } + // }, + // ] + // ]; + // // Custom error messages + // $messages = [ + // 'token.required' => 'The token is required.', + // 'token.valid' => 'Token note valid!' + // ]; + // // Perform the validation + // $validator = Validator::make(['token' => $request->token ], $rules, $messages); + // if ($validator->fails()) { + // return redirect() + // ->back() + // ->withErrors($validator) + // ->withInput(); + // } - //If validation passes, proceed to the next middleware/controller - return $next($request); - } - )->only('authorizeQuickbooks'); - } + // //If validation passes, proceed to the next middleware/controller + // return $next($request); + // } + // )->only('authorizeQuickbooks'); + // } public function onAuthorized(Request $request) { @@ -114,12 +117,15 @@ class ImportQuickbooksController extends BaseController * * @return bool */ - public function authorizeQuickbooks(Request $request) + public function authorizeQuickbooks(AuthQuickbooksRequest $request, string $token) { - $token = $request->token; - $auth = $this->service->getOAuth(); - $authorizationUrl = $auth->getAuthorizationUrl(); - $state = $auth->getState(); + + MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']); + $company = $request->getCompany(); + $qb = new QuickbooksService($company); + + $authorizationUrl = $qb->getAuth()->getAuthorizationUrl(); + $state = $qb->getAuth()->getState(); Cache::put($state, $token, 190); @@ -186,7 +192,7 @@ class ImportQuickbooksController extends BaseController $this->preimport($type, $hash); } /** @var \App\Models\User $user */ - $user = auth()->user() ?? Auth::loginUsingId(60); + // $user = auth()->user() ?? Auth::loginUsingId(60); $data = ['import_types' => $request->input('import_types') ] + compact('hash'); if (Ninja::isHosted()) { QuickbooksIngest::dispatch( $data , $user->company() ); diff --git a/app/Models/Company.php b/app/Models/Company.php index cdfde34fe8..b1d504ca69 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -118,6 +118,7 @@ use Laracasts\Presenter\PresentableTrait; * @property string|null $smtp_port * @property string|null $smtp_encryption * @property string|null $smtp_local_domain + * @property string|null $quickbooks * @property boolean $smtp_verify_peer * @property-read \App\Models\Account $account * @property-read \Illuminate\Database\Eloquent\Collection $activities @@ -390,6 +391,7 @@ class Company extends BaseModel 'smtp_username' => 'encrypted', 'smtp_password' => 'encrypted', 'e_invoice' => 'object', + 'quickbooks' => 'object', ]; protected $with = []; diff --git a/app/Providers/QuickbooksServiceProvider.php b/app/Providers/QuickbooksServiceProvider.php deleted file mode 100644 index 0a3777eb3f..0000000000 --- a/app/Providers/QuickbooksServiceProvider.php +++ /dev/null @@ -1,84 +0,0 @@ -app->bind(QuickbooksInterface::class, function ($app) { - return new QuickbooksSDKWrapper(QuickbooksSDKFactory::create()); - }); - - // Register SDKWrapper with DataService dependency - $this->app->singleton(QuickbooksService::class, function ($app) { - return new QuickbooksService($app->make(QuickbooksInterface::class)); - }); - - $this->app->singleton(QuickbooksTransformer::class,QuickbooksTransformer::class); - } - /** - * Bootstrap services. - * - * @return void - */ - public function boot() - { - $this->registerRoutes(); - $this->registerConfig(); - } - - protected function registerConfig() { - config()->set( 'services.quickbooks' , - ['settings' => [ - 'auth_mode' => 'oauth2', - 'ClientID' => env('QUICKBOOKS_CLIENT_ID', false), - 'ClientSecret' => env('QUICKBOOKS_CLIENT_SECRET', false), - // TODO use env('QUICKBOOKS_REDIRECT_URI') or route()/ url() - 'RedirectURI' => url("/quickbooks/authorized"), - 'scope' => "com.intuit.quickbooks.accounting", - 'baseUrl' => ucfirst(env('APP_ENV')) - ], - 'debug' => env('APP_DEBUG') || env('APP_ENV') - ] - ); - } - - /** - * Register custom routes. - * - * @return void - */ - protected function registerRoutes() - { - Route::middleware('web') - ->namespace($this->app->getNamespace() . 'Http\Controllers') - ->group(function () { - Route::get('quickbooks/authorize/{token}', [ImportQuickbooksController::class, 'authorizeQuickbooks'])->name('authorize.quickbooks'); - Route::get('quickbooks/authorized', [ImportQuickbooksController::class, 'onAuthorized'])->name('authorized.quickbooks'); - }); - Route::prefix('api/v1') - ->middleware('api') - ->namespace($this->app->getNamespace() . 'Http\Controllers') - ->group(function () { - Route::post('import/quickbooks', [ImportQuickbooksController::class, 'import'])->name('import.quickbooks'); - }); - } -} diff --git a/app/Services/Import/Quickbooks/Auth.php b/app/Services/Import/Quickbooks/Auth.php index a8f08c7cfd..a6df31ba60 100644 --- a/app/Services/Import/Quickbooks/Auth.php +++ b/app/Services/Import/Quickbooks/Auth.php @@ -1,66 +1,72 @@ sdk = $quickbooks; +{ + public function __construct(private DataService $sdk) + { } - public function accessToken(string $code, string $realm ) : array + public function accessToken(string $code, string $realm): array { - // TODO: Get or put token in Cache or DB? + // TODO: Get or put token in Cache or DB? return $this->sdk->accessToken($code, $realm); } - public function refreshToken() : array + public function refreshToken(): array { // TODO: Get or put token in Cache or DB? return $this->sdk->refreshToken(); } - public function getAuthorizationUrl(): string + public function getAuthorizationUrl(): string { return $this->sdk->getAuthorizationUrl(); } - public function getState() : string + public function getState(): string { return $this->sdk->getState(); } public function saveTokens($key, $tokens) { - $token_store = new CompanyTokensRepository($key); - $token_store->save($tokens); + // $token_store = new CompanyTokensRepository($key); + // $token_store->save($tokens); } - public function getAccessToken() : array + public function getAccessToken(): array { - $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'); - } - + $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 + public function getRefreshToken(): array { return $this->getAccessToken(); } -} \ No newline at end of file +} diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index f13090bb63..71148d81f1 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -212,6 +212,7 @@ class CompanyTransformer extends EntityTransformer 'smtp_local_domain' => (string)$company->smtp_local_domain ?? '', 'smtp_verify_peer' => (bool)$company->smtp_verify_peer, 'e_invoice' => $company->e_invoice ?: new \stdClass(), + 'quickbooks' => $company->quickbooks ?: new \stdClass(), ]; } diff --git a/config/app.php b/config/app.php index ecb6886945..8fe0ab11f5 100644 --- a/config/app.php +++ b/config/app.php @@ -201,7 +201,6 @@ return [ App\Providers\ClientPortalServiceProvider::class, App\Providers\NinjaTranslationServiceProvider::class, App\Providers\StaticServiceProvider::class, - App\Providers\QuickbooksServiceProvider::class ], /* diff --git a/config/services.php b/config/services.php index ae434aba65..6bb8ecaf95 100644 --- a/config/services.php +++ b/config/services.php @@ -120,5 +120,17 @@ return [ 'chorus' => [ 'client_id' => env('CHORUS_CLIENT_ID', false), 'secret' => env('CHORUS_SECRET', false), - ] + ], + 'quickbooks' => [ + // 'auth_mode' => 'oauth2', + 'client_id' => env('QUICKBOOKS_CLIENT_ID', false), + 'client_secret' => env('QUICKBOOKS_CLIENT_SECRET', false), + // 'ClientID' => env('QUICKBOOKS_CLIENT_ID', false), + // 'ClientSecret' => env('QUICKBOOKS_CLIENT_SECRET', false), + // TODO use env('QUICKBOOKS_REDIRECT_URI') or route()/ url() + // 'RedirectURI' => url("/quickbooks/authorized"), + // 'scope' => "com.intuit.quickbooks.accounting", + // 'baseUrl' => ucfirst(env('APP_URL')) + 'debug' => env('APP_DEBUG',false) + ], ]; diff --git a/database/migrations/2024_08_02_144614_alter_companies_quickbooks.php b/database/migrations/2024_08_02_144614_alter_companies_quickbooks.php index 0050e38da8..627239cc9d 100644 --- a/database/migrations/2024_08_02_144614_alter_companies_quickbooks.php +++ b/database/migrations/2024_08_02_144614_alter_companies_quickbooks.php @@ -12,11 +12,9 @@ return new class extends Migration * @return void */ public function up() - { + { Schema::table('companies', function (Blueprint $table) { - $table->string('quickbooks_realm_id')->nullable(); - $table->string('quickbooks_refresh_token')->nullable(); - $table->dateTime('quickbooks_refresh_expires')->nullable(); + $table->text('quickbooks')->nullable(); }); } @@ -27,8 +25,6 @@ return new class extends Migration */ public function down() { - Schema::table('companies', function (Blueprint $table) { - $table->dropColumn(['quickbooks_realm_id', 'quickbooks_refresh_token','quickbooks_refresh_expires']); - }); + } }; diff --git a/routes/api.php b/routes/api.php index ddcdc37b71..bbd67c59de 100644 --- a/routes/api.php +++ b/routes/api.php @@ -427,6 +427,9 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('yodlee/status/{account_number}', [YodleeController::class, 'accountStatus']); // @todo @turbo124 check route-path?! Route::get('nordigen/institutions', [NordigenController::class, 'institutions'])->name('nordigen.institutions'); + + Route::post('import/quickbooks', [ImportQuickbooksController::class, 'import'])->name('import.quickbooks'); + }); Route::post('api/v1/sms_reset', [TwilioController::class, 'generate2faResetCode'])->name('sms_reset.generate')->middleware('throttle:3,1'); @@ -460,4 +463,7 @@ Route::post('api/v1/yodlee/balance', [YodleeController::class, 'balanceWebhook'] Route::get('api/v1/protected_download/{hash}', [ProtectedDownloadController::class, 'index'])->name('protected_download')->middleware('throttle:300,1'); Route::post('api/v1/ppcp/webhook', [PayPalPPCPPaymentDriver::class, 'processWebhookRequest'])->middleware('throttle:1000,1'); +Route::get('quickbooks/authorize/{token}', [ImportQuickbooksController::class, 'authorizeQuickbooks'])->name('quickbooks.authorize'); +Route::get('quickbooks/authorized', [ImportQuickbooksController::class, 'onAuthorized'])->name('quickbooks.authorized'); + Route::fallback([BaseController::class, 'notFound'])->middleware('throttle:404'); diff --git a/routes/web.php b/routes/web.php index 78ac4dbfe5..4f22c11814 100644 --- a/routes/web.php +++ b/routes/web.php @@ -51,4 +51,5 @@ Route::get('mollie/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', [Mol Route::get('gocardless/ibp_redirect/{company_key}/{company_gateway_id}/{hash}', [GoCardlessController::class, 'ibpRedirect'])->middleware('domain_db')->name('gocardless.ibp_redirect'); Route::get('.well-known/apple-developer-merchantid-domain-association', [ApplePayDomainController::class, 'showAppleMerchantId']); -Broadcast::routes(['middleware' => ['token_auth']]); + +\Illuminate\Support\Facades\Broadcast::routes(['middleware' => ['token_auth']]); diff --git a/tests/Unit/Services/Import/Quickbooks/SdkWrapperTest.php b/tests/Unit/Services/Import/Quickbooks/SdkWrapperTest.php index a76da2720f..a7afa36e97 100644 --- a/tests/Unit/Services/Import/Quickbooks/SdkWrapperTest.php +++ b/tests/Unit/Services/Import/Quickbooks/SdkWrapperTest.php @@ -18,10 +18,11 @@ class SdkWrapperTest extends TestCase { parent::setUp(); - $this->sdkMock = Mockery::mock(sdtClass::class); + + $this->sdkMock = Mockery::mock(\stdClass::class); $this->sdk = new QuickbookSDK($this->sdkMock); - + $this->markTestSkipped('no resource'); } function testIsInstanceOf() {