Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions config/quickbooks_manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

'redirect_route' => 'app.home',

'table_name' => 'quickbooks_tokens',

'connections' => [

'default' => [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ 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();
$table->string('refresh_token')->nullable();
$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();
});
}
Expand All @@ -32,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');
}
}
14 changes: 13 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,16 @@ 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)
- `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();
```

## Token's database table

Now you can configure token's table name, just change `table_name` in config file.
49 changes: 49 additions & 0 deletions src/Commands/RefreshTokensCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Hotrush\QuickBooksManager\Commands;

use Hotrush\QuickBooksManager\QuickBooksManager;
use Hotrush\QuickBooksManager\QuickBooksToken;
use Illuminate\Console\Command;
use QuickBooksOnline\API\Exception\ServiceException;

class RefreshTokensCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'qbm:refresh-tokens';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Refresh access tokens.';

/**
* Execute the console command.
*
* @param QuickBooksManager $manager
*
* @return mixed
*/
public function handle(QuickBooksManager $manager)
{
$tokens = QuickBooksToken::where('refresh_at','<=',now())->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()));
}
}
}
}
38 changes: 6 additions & 32 deletions src/QuickBooksConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

/**
Expand Down Expand Up @@ -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);
Expand All @@ -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]);
}

/**
Expand All @@ -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;
}
}
7 changes: 7 additions & 0 deletions src/QuickBooksManagerServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Hotrush\QuickBooksManager;

use Hotrush\QuickBooksManager\Commands\RefreshTokensCommand;
use Illuminate\Support\ServiceProvider;

class QuickBooksManagerServiceProvider extends ServiceProvider
Expand All @@ -11,6 +12,7 @@ public function boot()
if ($this->app->runningInConsole()) {
$this->definePublishingGroups();
$this->defineMigrations();
$this->defineCommands();
}

$this->loadRoutesFrom(__DIR__.'/../routes/route.php');
Expand Down Expand Up @@ -38,4 +40,9 @@ private function defineMigrations()
{
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}

private function defineCommands()
{
$this->commands(RefreshTokensCommand::class);
}
}
28 changes: 22 additions & 6 deletions src/QuickBooksToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,31 @@

use Illuminate\Database\Eloquent\Model;
use QuickBooksOnline\API\Core\OAuth\OAuth2\OAuth2AccessToken;
use Carbon\Carbon;

class QuickBooksToken extends Model
{
protected $table = 'quickbooks_tokens';
const TOKEN_REFRESH_WINDOW = 600;

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',
];

public function getTable()
{
return config('quickbooks_manager.table_name');
}

/**
* @return bool
*/
public function isExpired()
{
return $this->expire_at < now();
Expand All @@ -39,9 +49,15 @@ 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(3600),
'refresh_expire_at' => now()->addSeconds(8726400),
'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()),
]);
}

public static function removeExpired($connection, $except = [])
{
return self::where('connection', $connection)->whereNotIn('id', $except)->delete();
}
}