intuit / QuickBooks-V3-PHP-SDK

Official PHP SDK for QuickBooks REST API v3.0: https://developer.intuit.com/
Apache License 2.0
246 stars 246 forks source link

refreshtoken 24 hour expiration,my code wrong? #446

Open yingyuhaonan opened 2 years ago

yingyuhaonan commented 2 years ago

$dataService = DataService::Configure( [ 'auth_mode' => 'oauth2', 'ClientID' => $list['clientID'], 'ClientSecret' => $list['clientSecret'], 'RedirectURI' => "XXXXXX", 'scope' => "com.intuit.quickbooks.payment com.intuit.quickbooks.accounting", 'state' => $list['stat'], 'baseUrl' => "Development", ] ); $OAuth2LoginHelper = $dataService->getOAuth2LoginHelper();

    $accessTokenObj    = $OAuth2LoginHelper->exchangeAuthorizationCodeForToken( $list['code'], $list['QBORealmID'] );
   // $result=$dataService->updateOAuth2Token($accessTokenObj);

    $id            = $offcustomerQB->saveDate( [
                                                   'id'            => $id,
                                                   'accessTokenKey'      => $accessTokenObj->getAccessToken(),
                                                   'refreshTokenKey'     => $accessTokenObj->getRefreshToken(),
                                                   'accessTokenTimeout'  => strtotime($accessTokenObj->getAccessTokenExpiresAt()),
                                                   'refreshTokenTimeout' => strtotime($accessTokenObj->getRefreshTokenExpiresAt()),

                                               ] );
asadmughal92 commented 1 year ago

did you solve the issue

npapratovic commented 1 year ago

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.

More info: https://help.developer.intuit.com/s/article/Handling-OAuth-token-expiration

https://help.developer.intuit.com/s/article/Validity-of-Refresh-Token

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