Closed rimoi closed 5 years ago
I have the same problem if you had solved this, please let me know. @rimoi
hello @khyatibhojawala ,
apparently, the information on the site is not correct. It indicates that Refresh_Token is valid for 101 days! with the difference that in the documentation it is valid for 24 hours (https://intuit.github.io/QuickBooks-V3-PHP-SDK/authorization.html#oauth-2-0-vs-1-0a-in-quickbooks-online (Third point)) The Api generate a new token every 24 hours based on the old token.
I store the Refresh Token every time in the database
$OAuth2LoginHelper = new OAuth2LoginHelper($this->param->get('client_id'), $this->param->get('secret_key'));
// i get refresh Token from database
$refreshTokenValue = $this->em->getRepository(Param::class)->get('qbo_refresh_token');
$accessTokenObj = $OAuth2LoginHelper->refreshAccessTokenWithRefreshToken($refreshTokenValue);
$accessTokenValue = $accessTokenObj->getAccessToken();
// i update the refresh Token in database
$this->em->getRepository(Param::class)->set('qbo_refresh_token', $accessTokenObj->getRefreshToken());
return DataService::Configure([
'auth_mode' => 'oauth2',
'ClientID' => $this->param->get('client_id'),
'ClientSecret' => $this->param->get('secret_key'),
'accessTokenKey' => $accessTokenValue,
'refreshTokenKey' => $refreshTokenValue,
'QBORealmID' => $this->param->get('realm_id'),
'baseUrl' => 'Production',
]);
I'll tell you if it works :wink:
You are right @rimoi If you use the method refreshAccessTokenWithRefreshToken after 24h, the previous refresh token gets invalidated. For any consequent refreshAccessTokenWithRefreshToken requests, the new refresh token must be used, which is valid for 101 days or till the method is called again after 24h.
Cheers
Thanks @lemnisk8 for your reply,
Ok if I understand, I have to use the refreshAccessTokenWithRefreshToken
method once a day
like this :
$dataService = DataService::Configure([
// my configuration
// get the last Token from the database
]);
$OAuth2LoginHelper = $dataService->getOAuth2LoginHelper();
try {
$accessToken = $OAuth2LoginHelper->refreshToken();
$dataService->updateOAuth2Token($accessToken);
return $dataService->Query('SELECT * FROM JournalEntry', 0, 500);
} catch (ServiceException $e) {
// use refreshAccessTokenWithRefreshToken and store it in datatabase every 24h
}
@rimoi you can use both refreshToken OR refreshAccessTokenWithRefreshToken
You should save these values to the DB
$accessTokenValue = $accessTokenObj->getAccessToken();
$refreshTokenValue = $accessTokenObj->getRefreshToken();
$accessTokenExpiresAt = $accessTokenObj->getAccessTokenExpiresAt();
$refreshTokenExpiresAt = $accessTokenObj->getRefreshTokenExpiresAt();
Next time check if access token has expired...
if( strtotime( $accessTokenExpiresAt ) >= time() ) {
/*
Call refreshToken OR refreshAccessTokenWithRefreshToken again
and save/update new values to the DB
*/
}
Tremendous Thanks @lemnisk8 I try this and I inform you :wink:
Hi @lemnisk8,
I have a question
why are you comparing to AccessToken
not to RefreshToken
?
if( strtotime( $refreshTokenExpiresAt ) >= time() ) {
/*
Call refreshToken OR refreshAccessTokenWithRefreshToken again
and save/update new values to the DB
*/
}
Edit
I think I can see why ! because the value in variable $refreshTokenExpiresAt (corresponds timestamp to 101 days)
So I think in this case we should do rather
$afterOneDay = $accessTokenExpiresAt + 3600 * 23;
if( strtotime( $afterOneDay ) >= time() ) {
// use method refreshAccessTokenWithRefreshToken and store in database
} else {
// get refresh token from database and use method refreshToken
}
what do you think?
@rimoi refreshAccessTokenWithRefreshToken and refreshToken will give the same response... with oauth2 the refresh token sent back changes every 24h
Instead of keeping track of when the refreshToken has changed, its easier to just save it every time... :)
Okay good :ok_hand: thanks you so much for your time @lemnisk8 :wink:
Hi @lemnisk8 I am facing same problem.
In my case, the I can get the new AccessToken
from RefreshToken
in Sandbox mode.. but when I use the Production mode, this logic does not work and throws the invalid_grant
error.
here is exact error details.
I am also facing a same problem and getting an error {"error":"invalid_grant"}]
i am doing in laravel first time when I generate access token it is working fine but when we fetch refresh token it generates an error Refresh OAuth 2 Access token with Refresh Token failed. Body: [{"error_description":"Incorrect Token type or clientID","error":"invalid_grant"}]. my code is attached @lemnisk8 @imvishalchodvadiya @hriziya
public function test()
{
$clientID= 'clientid';
$clientSecret='clientsecret';
$accessTokenKey = 'eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..ZreAgv5F6yGly2ma-ZKtOg.dljIAppj5UPbmkHX1qKA1MAjA1nzDpd4qYzgtCmN_yROJs2luw7xug-ooGWeQ3rDs4KNK0rERJQlgvuJ-Qlv16Cfhtx9pDgKuGkJHEs7fGaNVEaQvoz8EIV3dOM4ASWhbvbaVuacb8B6GSWs-ocFDymZZq7LW-g6YpPku2RVc9KFvn2uwBCrIZR6LV2A0L1kmQw7YvbmNJfA-FsxfchMIPFjNIFxSlbEqo1U44ksHwpfhnkesdkzdnnFmN93-ZB4aApWhE0QpFmD2kGSnHoo8LbU4YEwy4UMRCeQ2FjKGsIdpnYO1JRL15oXD5n5XXXQ12Ohz1Q71eEonovGEj7ScWLY8OQCc5ThsK3RqZ9UYVs2lSSnAGtHkff1r_KsY9Ldzc8ldrblstQdnWo8XeKHIFEvnyzmzGVfwv1Ji2PZt54ul7XhPOUxKQRHFKft5o9sl4sYwxzcScw95oNSdttz44v01T0vpcY7jj9yQlCE3RrSgwJOf01Moly6-m5MhbH8ZEocR5hpDtzClD5CTQkf5bVCiiKKzYDwwSu_ICqdd_2RHUvllV2nTQl7arAX6vjLK11Auh2PL7CQhTzJ3_kvnpFr3FnPVE95wT15XvdAvvkUtNBuwUqJsJ9Lx_i_BHkFYf9Eeh1uwqSoPJv-9dYy1QzkQ1iI0GFQfkO3Ybcdai46bzaInTr0DQ4pL6IdQoCwoaAEMNwTpAhjPOHplKJDnw5LgbTv1GEa211YXe7OaVySl5SotC3tCInOIxWdxHvUr9TRQb3FVthn3k2MroAd8qMzH3Z9eEJDkndwSVznFONAKPG581R0bVvwnbFdwMr1k_Yd0gspGPYdL968BkZjkLzA21UnLqEXSAu5LlYX9S6MXjlNX0tlnVOPYcn2M3K5.8nOkmBMzg0_bKGKKpTqoMA';
$refresh_token='AB116791234451aEqdi28l8OL1CHORRhNP4HixTb6Z8sCoDAds';
$dataService = DataService::Configure(array(
'auth_mode' => 'oauth2',
'ClientID' => $clientID,
'ClientSecret' =>$clientSecret,
'RedirectURI' => 'http://localhost:8000/callback.php',
'scope' => 'com.intuit.quickbooks.accounting openid profile email phone address',
'baseUrl' => "development",
));
$Token = new OAuth2AccessToken($clientID,$clientSecret,$accessTokenKey,$refresh_token,'1670400645','1679123445');
$OAuth2LoginHelper = $dataService->getOAuth2LoginHelper();
try {
$dataService->updateOAuth2Token($Token);
$afterOneDay = strtotime($Token->getaccessTokenExpiresAt()) + 3600 * 23;
if( $afterOneDay >= time() ) {
// use method refreshAccessTokenWithRefreshToken and store in database
$newAccessTokenObj = $OAuth2LoginHelper->refreshAccessTokenWithRefreshToken($Token->getRefreshToken());
$newAccessTokenObj->setRealmID($Token->getRealmID());
$newAccessTokenObj->setBaseURL($Token->getBaseURL());
print_r($newAccessTokenObj);
} else {
// get refresh token from database and use method refreshToken
$CompanyInfo = $dataService->getCompanyInfo();
return json_encode($CompanyInfo);
}
} catch (ServiceException $e) {
}
}
In short, the recommended workflow for all apps is: Step 1. If the Access Token fails, use the current Refresh Token to request new Access and Refresh Tokens. Step 2. Store BOTH the Access and Refresh Tokens that are returned. Step 3. Use the new Access Token for making QuickBooks Online API calls. Step 4. When the Access Token fails in 1 hour, go to Step 1.
To solve this error: "Refresh OAuth 2 Access token with Refresh Token failed" in my Laravel application I had this approach:
1) store config data in .env files, store tokens in database; fetch them on every DataService object creation, and recreate tokens on the fly and save them again in database:
use QuickBooksOnline\API\Core\OAuth\OAuth2\OAuth2LoginHelper;
use QuickBooksOnline\API\DataService\DataService;
use QuickBooksOnline\API\Exception\SdkException;
use QuickBooksOnline\API\Exception\ServiceException;
public function getTokensFromDataBase()
{
$config = config('quickbooks');
$quickbook = \App\Models\QuickBook::where('realm_id', $config['realm_id'])->first();
return [
'access_token' => $quickbook->access_token,
'refresh_token' => $quickbook->refresh_token,
'x_refresh_token_expires_in' => $quickbook->x_refresh_token_expires_in,
];
}
public function getTokens()
{
// step 0 fetch latest tokens from db
$tokens_from_db = $this->getTokensFromDataBase();
// step 1: get config data, and get refresh token
$config = config('quickbooks');
$dataService = DataService::Configure([
'auth_mode' => 'oauth2',
'ClientID' => $config['client_id'],
'ClientSecret' => $config['client_secret'],
'RedirectURI' => $config['redirect_uri'],
'scope' => 'com.intuit.quickbooks.accounting',
'baseUrl' => $config['base_url'],
// Get the refresh token from session or database
'refreshTokenKey' => $tokens_from_db['refresh_token'],
'QBORealmId' => $config['realm_id'],
]);
// step 2: refresh access token using the refresh token
// The first parameter of OAuth2LoginHelper is the ClientID, the second parameter is the client Secret
$oauth2LoginHelper = new OAuth2LoginHelper($config['client_id'], $config['client_secret']);
$accessToken = $oauth2LoginHelper->refreshAccessTokenWithRefreshToken($tokens_from_db['refresh_token']);
$dataService->updateOAuth2Token($accessToken);
// step 3: save the new tokens in the database
$token_in_db = \App\Models\QuickBook::where('realm_id', $config['realm_id'])->first();
$token_in_db->access_token = $accessToken->getAccessToken();
$token_in_db->refresh_token = $accessToken->getRefreshToken();
$token_in_db->x_refresh_token_expires_in = $accessToken->getRefreshTokenExpiresAt();
$token_in_db->save();
return [
'access_token' => $accessToken->getAccessToken(),
'refresh_token' => $accessToken->getRefreshToken(),
'x_refresh_token_expires_in' => $accessToken->getRefreshTokenExpiresAt(),
'expires_in' => $accessToken->getAccessTokenExpiresAt(),
];
}
2) In your controller where you wwant to store invoice in QB, fetch tokens by calling method getTokens() and set DataService object again:
$tokens = $this->getTokens();
$config = config('quickbooks');
$dataService = DataService::Configure([
'auth_mode' => 'oauth2',
'ClientID' => $config['client_id'],
'ClientSecret' => $config['client_secret'],
'RedirectURI' => $config['redirect_uri'],
'accessTokenKey' => $tokens['access_token'],
'refreshTokenKey' => $tokens['refresh_token'],
'QBORealmID' => $config['realm_id'],
'baseUrl' => $config['base_url'],
]);
$dataService->throwExceptionOnError(true);
3) now, when DataSerice object is set up properly, you can follow instructions to save or ftech data to / from QB:
....
$invoice = Invoice::create([
"Line" => $lines,
"CustomerRef" => [
"value" => $customer->Id,
"name" => $customer->name,
],
"BillEmail" => [
"Address" => $customer->email,
],
]);
$response_from_qb = $dataService->Add($invoice);
$xmlResponseQB = XmlObjectSerializer::getPostXmlFromArbitraryEntity($response_from_qb, $urlResource);
// use this to debug response from QB
echo "Invoice Created Id={$response_from_qb->Id}. Reconstructed response body:\n\n";
echo $xmlResponseQB;
....
4) problem with tokens is that they expire - access token is valid for 60 minutes, and by using approach above, if your users do not perform query to the QB, access tokens in your db will expire
My approach was to create Laravel command, and set up cron job which will run every 10 minutes and update tokens in db by itself:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use QuickBooksOnline\API\Core\OAuth\OAuth2\OAuth2LoginHelper;
use QuickBooksOnline\API\DataService\DataService;
use QuickBooksOnline\API\Exception\SdkException;
use QuickBooksOnline\API\Exception\ServiceException;
class refreshQBToken extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:refresh-q-b-token';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This command refreshes the QuickBooks token.';
/**
* Execute the console command.
* @throws SdkException
* @throws ServiceException
*/
public function handle()
{
$config = config('quickbooks');
$token_in_db = \App\Models\QuickBook::where('realm_id', $config['realm_id'])->first();
$dataService = DataService::Configure([
'auth_mode' => 'oauth2',
'ClientID' => $config['client_id'],
'ClientSecret' => $config['client_secret'],
'RedirectURI' => $config['redirect_uri'],
'scope' => 'com.intuit.quickbooks.accounting',
'baseUrl' => $config['base_url'],
// Get the refresh token from session or database
'refreshTokenKey' => $token_in_db->refresh_token,
'QBORealmId' => $config['realm_id'],
]);
$oauth2LoginHelper = new OAuth2LoginHelper($config['client_id'], $config['client_secret']);
$accessToken = $oauth2LoginHelper->refreshAccessTokenWithRefreshToken($token_in_db->refresh_token);
$dataService->updateOAuth2Token($accessToken);
//save the new tokens in the database
$token_in_db->access_token = $accessToken->getAccessToken();
$token_in_db->refresh_token = $accessToken->getRefreshToken();
$token_in_db->x_refresh_token_expires_in = $accessToken->getRefreshTokenExpiresAt();
$token_in_db->save();
$this->info('Successfully refreshed QB tokens.');
}
}
Thats how I managed to solve problem with tokens expiry
Hello, I have a problem when updating the token, yet I use Refresh_token, but it is valid one hour! every time I have to go to https://developer.intuit.com/app/developer/playground to retrieve the
refresh_token
andaccess_token
Before I did like this
Now I discovered the method
refreshAccessTokenWithRefreshToken
It is work for 24 hours and i have this message error :
Refresh OAuth 2 Access token with Refresh Token failed. Body: [{"error":"invalid_grant"}]
Someone has an idea?
Thanks Sidi