From 19540040a0ab4f2338a6a1a2e8f486021923fca1 Mon Sep 17 00:00:00 2001 From: hotrush Date: Tue, 30 Jul 2019 17:57:16 +0300 Subject: [PATCH 1/2] tokens refreshing changes --- ..._140501_create_quickbooks_tokens_table.php | 1 + readme.md | 10 +++- src/Commands/RefreshTokensCommand.php | 49 +++++++++++++++++++ src/QuickBooksConnection.php | 38 +++----------- src/QuickBooksManagerServiceProvider.php | 7 +++ src/QuickBooksToken.php | 22 +++++++-- 6 files changed, 90 insertions(+), 37 deletions(-) create mode 100644 src/Commands/RefreshTokensCommand.php diff --git a/database/migrations/2019_07_22_140501_create_quickbooks_tokens_table.php b/database/migrations/2019_07_22_140501_create_quickbooks_tokens_table.php index ab59502..601d035 100644 --- a/database/migrations/2019_07_22_140501_create_quickbooks_tokens_table.php +++ b/database/migrations/2019_07_22_140501_create_quickbooks_tokens_table.php @@ -21,6 +21,7 @@ public function up() $table->string('realm_id')->nullable(); $table->timestamp('issued_at')->nullable(); $table->timestamp('expire_at')->nullable(); + $table->timestamp('refresh_at')->nullable(); $table->timestamp('refresh_expire_at')->nullable(); }); } diff --git a/readme.md b/readme.md index 2712400..ace013c 100644 --- a/readme.md +++ b/readme.md @@ -75,4 +75,12 @@ Each event has next arguments: - `entityName` - the name of the entity type that changed (Customer, Invoice, etc.) - `entityId` - changed entity id - `lastUpdated` - carbon-parsed date object -- `deletedId` - the ID of the entity that was deleted and merged (only for Merge events) \ No newline at end of file +- `deletedId` - the ID of the entity that was deleted and merged (only for Merge events) + +## Tokens refresh schedule + +Schedule refreshing in `App\Console\Kernel`. [Schedule docs](https://laravel.com/docs/5.8/scheduling#defining-schedules). + +```php +$schedule->command(RefreshTokensCommand::class)->everyMinute(); +``` \ No newline at end of file diff --git a/src/Commands/RefreshTokensCommand.php b/src/Commands/RefreshTokensCommand.php new file mode 100644 index 0000000..9a8f92c --- /dev/null +++ b/src/Commands/RefreshTokensCommand.php @@ -0,0 +1,49 @@ +where('refresh_expire_at','>',now())->get(); + + if (!$tokens->count()) { + return; + } + + foreach ($tokens as $token) { + try { + $manager->connection($token->connection)->refreshToken(); + } catch (ServiceException $e) { + $this->error(sprintf('Error refreshing token for connection "%s": %s', $token->connection, $e->getMessage())); + } + } + } +} \ No newline at end of file diff --git a/src/QuickBooksConnection.php b/src/QuickBooksConnection.php index ad5f704..591fd04 100644 --- a/src/QuickBooksConnection.php +++ b/src/QuickBooksConnection.php @@ -54,14 +54,10 @@ private function initClient($forceRefresh = false) 'baseUrl' => $this->config['base_url'], 'QBORealmID' => $this->token ? $this->token->realm_id : null, 'accessTokenKey' => $this->token && !$this->token->isExpired() ? $this->token->access_token : null, - 'refreshTokenKey' => $this->token && ($this->token->isExpired() || $forceRefresh) && $this->token->isRefreshable() ? $this->token->refresh_token : null, + 'refreshTokenKey' => $this->token && $forceRefresh && $this->token->isRefreshable() ? $this->token->refresh_token : null, ]) ->setLogLocation(config('quickbooks_manager.logs_path')) ->throwExceptionOnError(true); - - if ($this->token && ($this->token->isExpired() || $forceRefresh) && $this->token->isRefreshable()) { - $this->refreshToken(); - } } /** @@ -102,8 +98,10 @@ private function loadTokenFromDatabase() /** * @throws \QuickBooksOnline\API\Exception\ServiceException */ - private function refreshToken() + public function refreshToken() { + $this->initClient(true); + $accessToken = $this->client->getOAuth2LoginHelper()->refreshToken(); $this->updateAccessToken($accessToken); @@ -117,6 +115,8 @@ private function updateAccessToken(OAuth2AccessToken $accessToken) $this->client->updateOAuth2Token($accessToken); $this->token = QuickBooksToken::createFromToken($this->name, $accessToken); + + QuickBooksToken::removeExpired($this->name, [$this->token->id]); } /** @@ -126,33 +126,7 @@ private function updateAccessToken(OAuth2AccessToken $accessToken) * @throws ServiceException */ public function __call($method, $parameters) - { - try { - return $this->executeSdkMethod($method, $parameters); - } catch (ServiceException $e) { - if ($this->detectTokenError($e) && $this->token && $this->token->isRefreshable()) { - $this->initClient(true); - return $this->executeSdkMethod($method, $parameters); - } - } - } - - /** - * @param $method - * @param $parameters - * @return mixed - */ - private function executeSdkMethod($method, $parameters) { return $this->client->$method(...$parameters); } - - /** - * @param ServiceException $e - * @return bool - */ - private function detectTokenError(ServiceException $e) - { - return $e->getCode() === 401 && strpos($e->getMessage(), 'Token expired') !== false; - } } \ No newline at end of file diff --git a/src/QuickBooksManagerServiceProvider.php b/src/QuickBooksManagerServiceProvider.php index 56d0858..bbc92b1 100644 --- a/src/QuickBooksManagerServiceProvider.php +++ b/src/QuickBooksManagerServiceProvider.php @@ -2,6 +2,7 @@ namespace Hotrush\QuickBooksManager; +use Hotrush\QuickBooksManager\Commands\RefreshTokensCommand; use Illuminate\Support\ServiceProvider; class QuickBooksManagerServiceProvider extends ServiceProvider @@ -11,6 +12,7 @@ public function boot() if ($this->app->runningInConsole()) { $this->definePublishingGroups(); $this->defineMigrations(); + $this->defineCommands(); } $this->loadRoutesFrom(__DIR__.'/../routes/route.php'); @@ -38,4 +40,9 @@ private function defineMigrations() { $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); } + + private function defineCommands() + { + $this->commands(RefreshTokensCommand::class); + } } diff --git a/src/QuickBooksToken.php b/src/QuickBooksToken.php index 130e07b..3bbad88 100644 --- a/src/QuickBooksToken.php +++ b/src/QuickBooksToken.php @@ -7,18 +7,26 @@ class QuickBooksToken extends Model { + const TOKEN_LIFETIME = 3600; + const TOKEN_REFRESH_PERIOD = 3000; + const REFRESH_TOKEN_LIFETIME = 8726400; + protected $table = 'quickbooks_tokens'; protected $fillable = [ - 'connection', 'access_token', 'refresh_token', 'realm_id', 'issued_at', 'expire_at', 'refresh_expire_at' + 'connection', 'access_token', 'refresh_token', 'realm_id', + 'issued_at', 'expire_at', 'refresh_at', 'refresh_expire_at', ]; public $timestamps = false; protected $dates = [ - 'issued_at', 'expire_at', 'refresh_expire_at', + 'issued_at', 'expire_at', 'refresh_at', 'refresh_expire_at', ]; + /** + * @return bool + */ public function isExpired() { return $this->expire_at < now(); @@ -40,8 +48,14 @@ public static function createFromToken($connection, OAuth2AccessToken $token) 'refresh_token' => $token->getRefreshToken(), 'realm_id' => $token->getRealmID(), 'issued_at' => now(), - 'expire_at' => now()->addSeconds(3600), - 'refresh_expire_at' => now()->addSeconds(8726400), + 'expire_at' => now()->addSeconds(self::TOKEN_LIFETIME), + 'refresh_at' => now()->addSeconds(self::TOKEN_REFRESH_PERIOD), + 'refresh_expire_at' => now()->addSeconds(self::REFRESH_TOKEN_LIFETIME), ]); } + + public static function removeExpired($connection, $except = []) + { + return self::where('connection', $connection)->whereNotIn('id', $except)->delete(); + } } \ No newline at end of file From 38e237f40bf3fa3060f3f29c5a1ffa1f9c410de2 Mon Sep 17 00:00:00 2001 From: hotrush Date: Tue, 3 Dec 2019 22:23:13 +0300 Subject: [PATCH 2/2] some refactoring and improvements --- composer.json | 2 +- config/quickbooks_manager.php | 2 ++ ..._140501_create_quickbooks_tokens_table.php | 9 +++++++-- readme.md | 6 +++++- src/QuickBooksToken.php | 20 ++++++++++--------- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index 54a723b..e0f0f53 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ "require": { "php": ">=7.1", "quickbooks/v3-php-sdk": "^5.0", - "illuminate/support": "~5" + "illuminate/support": "~5|~6" }, "require-dev": { "orchestra/testbench": "~3.0", diff --git a/config/quickbooks_manager.php b/config/quickbooks_manager.php index fc78d81..6a80dda 100644 --- a/config/quickbooks_manager.php +++ b/config/quickbooks_manager.php @@ -14,6 +14,8 @@ 'redirect_route' => 'app.home', + 'table_name' => 'quickbooks_tokens', + 'connections' => [ 'default' => [ diff --git a/database/migrations/2019_07_22_140501_create_quickbooks_tokens_table.php b/database/migrations/2019_07_22_140501_create_quickbooks_tokens_table.php index 601d035..01663cc 100644 --- a/database/migrations/2019_07_22_140501_create_quickbooks_tokens_table.php +++ b/database/migrations/2019_07_22_140501_create_quickbooks_tokens_table.php @@ -13,7 +13,7 @@ class CreateQuickbooksTokensTable extends Migration */ public function up() { - Schema::create('quickbooks_tokens', function(Blueprint $table) { + Schema::create($this->getTableName(), function(Blueprint $table) { $table->increments('id'); $table->string('connection'); $table->text('access_token')->nullable(); @@ -33,6 +33,11 @@ public function up() */ public function down() { - Schema::drop('quickbooks_tokens'); + Schema::drop($this->getTableName()); + } + + protected function getTableName() + { + return config('quickbooks_manager.table_name'); } } diff --git a/readme.md b/readme.md index ace013c..26eea1d 100644 --- a/readme.md +++ b/readme.md @@ -83,4 +83,8 @@ Schedule refreshing in `App\Console\Kernel`. [Schedule docs](https://laravel.com ```php $schedule->command(RefreshTokensCommand::class)->everyMinute(); -``` \ No newline at end of file +``` + +## Token's database table + +Now you can configure token's table name, just change `table_name` in config file. \ No newline at end of file diff --git a/src/QuickBooksToken.php b/src/QuickBooksToken.php index 3bbad88..568a532 100644 --- a/src/QuickBooksToken.php +++ b/src/QuickBooksToken.php @@ -4,14 +4,11 @@ use Illuminate\Database\Eloquent\Model; use QuickBooksOnline\API\Core\OAuth\OAuth2\OAuth2AccessToken; +use Carbon\Carbon; class QuickBooksToken extends Model { - const TOKEN_LIFETIME = 3600; - const TOKEN_REFRESH_PERIOD = 3000; - const REFRESH_TOKEN_LIFETIME = 8726400; - - protected $table = 'quickbooks_tokens'; + const TOKEN_REFRESH_WINDOW = 600; protected $fillable = [ 'connection', 'access_token', 'refresh_token', 'realm_id', @@ -24,6 +21,11 @@ class QuickBooksToken extends Model 'issued_at', 'expire_at', 'refresh_at', 'refresh_expire_at', ]; + public function getTable() + { + return config('quickbooks_manager.table_name'); + } + /** * @return bool */ @@ -47,10 +49,10 @@ public static function createFromToken($connection, OAuth2AccessToken $token) 'access_token' => $token->getAccessToken(), 'refresh_token' => $token->getRefreshToken(), 'realm_id' => $token->getRealmID(), - 'issued_at' => now(), - 'expire_at' => now()->addSeconds(self::TOKEN_LIFETIME), - 'refresh_at' => now()->addSeconds(self::TOKEN_REFRESH_PERIOD), - 'refresh_expire_at' => now()->addSeconds(self::REFRESH_TOKEN_LIFETIME), + 'issued_at' => Carbon::parse($token->getAccessTokenExpiresAt())->addSeconds(-$token->getAccessTokenValidationPeriodInSeconds()), + 'expire_at' => Carbon::parse($token->getAccessTokenExpiresAt()), + 'refresh_at' => Carbon::parse($token->getRefreshTokenExpiresAt())->addSeconds(-self::TOKEN_REFRESH_WINDOW), + 'refresh_expire_at' => Carbon::parse($token->getRefreshTokenExpiresAt()), ]); }