Search code examples
phpoauth-2.0quickbooks-online

Quickbooks PHP API: Refresh OAuth 2 Token Failed


I'm trying to access Quickbooks API using the PHP SDK but getting the following error:

Refresh OAuth 2 Access token with Refresh Token failed. Body: [{"error":"invalid_grant"}].

My Tokens seem to work for 24 hours but after that I receive the error above. Each time I call the API, I am saving my updated tokens to my database:

//Client ID & Secret
$qbClientId = $this->scopeConfig->getValue('quickbooks/api/qb_client_id', $storeScope);
$qbClientSecret = $this->scopeConfig->getValue('quickbooks/api/qb_client_secret', $storeScope);

//Retrieve currently saved Refresh_Token from DB
$qbRefreshToken = $this->scopeConfig->getValue('quickbooks/api/qb_refresh_token', $storeScope);

$OAuth2LoginHelper = new OAuth2LoginHelper($qbClientId, $qbClientSecret);
$accessTokenObj = $OAuth2LoginHelper->refreshAccessTokenWithRefreshToken($qbRefreshToken);

$error = $OAuth2LoginHelper->getLastError();

if($error) {
  throw new \Exception($error);
} else {
  // The refresh token and access token expiration
  $refreshTokenValue = $accessTokenObj->getRefreshToken();
  $refreshTokenExpiry = $accessTokenObj->getRefreshTokenExpiresAt();

  // Save new Refresh Token & Expiry to DB
  $this->configInterface->saveConfig('quickbooks/api/qb_refresh_token', $this->encryptor->encrypt($refreshTokenValue), 'default', 0);
  $this->configInterface->saveConfig('quickbooks/api/qb_refresh_token_expiry', $refreshTokenExpiry, 'default', 0);

  // The access token and access token expiration
  $accessTokenValue = $accessTokenObj->getAccessToken();
  $accessTokenExpiry = $accessTokenObj->getAccessTokenExpiresAt();

  // Save new Access Token & Expiry to DB
  $this->configInterface->saveConfig('quickbooks/api/qb_access_token', $this->encryptor->encrypt($accessTokenValue), 'default', 0);
  $this->configInterface->saveConfig('quickbooks/api/qb_access_token_expiry', $accessTokenExpiry, 'default', 0);

  return DataService::Configure(array(
    'auth_mode' => 'oauth2',
    'ClientID' => $qbClientId,
    'ClientSecret' => $qbClientSecret,
    'accessTokenKey' => $accessTokenValue,
    'refreshTokenKey' => $refreshTokenValue,
    'QBORealmID' => 'MyRealmID',
    'baseUrl' => 'Development'
  ));
}

So as you can see, on each API call, I'm using the refreshAccessTokenWithRefreshToken($qbRefreshToken) method to get new Refresh and Access Tokens and saving those to my DB for next use, however I still receive invalid_grant errors after 24hours.

Any ideas?


Solution

  • I struggled with the same problem. In my case, I had updated the tokens in the database, but not everywhere in memory (specifically in the $dataService object). So continuing to use the $dataService object to make API calls was using the old tokens. D'oh!

    Although I think it would still work, you do not need to refresh the tokens with every API call (as David pointed out). My solution was to make the API call and if it fails, then I refresh the tokens and make the API call again. Here is a simplified version of my code:

    $user        = ...; // get from database
    $dataService = getDataServiceObject();
    
    $response = $dataService->Add(...); // hit the QBO API
    $error    = $dataService->getLastError();
    
    if ($error) {
      refreshTokens();
      $response = $dataService->Add(...); // try the API call again
    }
    
    // ... "Add" complete, onto the next thing
    
    ////////////////////////////////
    
    function getDataServiceObject() {
      global $user;
    
      return DataService::Configure(array(
        'auth_mode'       => 'oauth2',
        'ClientID'        => '...',
        'ClientSecret'    => '...',
        'accessTokenKey'  => $user->getQbAccessToken(),
        'refreshTokenKey' => $user->getQbRefreshToken(),
        'QBORealmID'      => $user->getQbRealmId(),
        'baseUrl'         => '...',
      ));
    }
    
    function refreshTokens() {
      global $dataService;
      global $user;
    
      $OAuth2LoginHelper = $dataService->getOAuth2LoginHelper();
      $obj               = $OAuth2LoginHelper->refreshAccessTokenWithRefreshToken($user->getQbRefreshToken());
      $newAccessToken    = $obj->getAccessToken();
      $newRefreshToken   = $obj->getRefreshToken();
    
      // update $user and store in database
      $user->setQbAccessToken($newAccessToken);
      $user->setQbRefreshToken($newRefreshToken);
      $user->save();
    
      // update $dataService object
      $dataService = getDataServiceObject(); 
    }