Allow QB reconnects

This commit is contained in:
David Bomba
2026-03-05 11:52:19 +11:00
parent 91e37d42ea
commit 7c5c953249
6 changed files with 27 additions and 65 deletions

View File

@@ -16,7 +16,6 @@ use App\Http\Requests\Quickbooks\AuthorizedQuickbooksRequest;
use App\Libraries\MultiDB;
use Illuminate\Support\Facades\Cache;
use App\Http\Requests\Quickbooks\AuthQuickbooksRequest;
use App\Models\TaxRate;
use App\Services\Quickbooks\QuickbooksService;
class ImportQuickbooksController extends BaseController

View File

@@ -101,60 +101,6 @@ class QuickbooksController extends BaseController
return response()->noContent();
}
/**
* status
*
* Returns the current QuickBooks connection status including whether reconnection is required.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function status(Request $request)
{
/** @var \App\Models\User $user */
$user = auth()->user();
$company = $user->company();
if (!$company->quickbooks || !$company->quickbooks->isConfigured()) {
return response()->json([
'connected' => false,
'requires_reconnect' => false,
]);
}
// Check if flag is already set
if ($company->quickbooks->requires_reconnect) {
return response()->json([
'connected' => true,
'requires_reconnect' => true,
'reason' => 'token_refresh_failed',
]);
}
// Check refresh token expiration
$refresh_expired = $company->quickbooks->refreshTokenExpiresAt > 0
&& $company->quickbooks->refreshTokenExpiresAt < time();
if ($refresh_expired) {
// Set the flag for future requests
$company->quickbooks->requires_reconnect = true;
$company->save();
return response()->json([
'connected' => true,
'requires_reconnect' => true,
'reason' => 'refresh_token_expired',
'expired_at' => $company->quickbooks->refreshTokenExpiresAt,
]);
}
return response()->json([
'connected' => true,
'requires_reconnect' => false,
'refresh_token_expires_at' => $company->quickbooks->refreshTokenExpiresAt,
]);
}
/**
* reconnectUrl
*
@@ -176,7 +122,8 @@ class QuickbooksController extends BaseController
// Generate a one-time token for the reconnect flow
$token = Str::random(64);
Cache::put('qb_reconnect_' . $token, [
Cache::put($token, [
'context' => 'quickbooks.reconnect',
'company_key' => $company->company_key,
'user_id' => $user->id,
], now()->addMinutes(30));

View File

@@ -50,7 +50,11 @@ class AuthorizedQuickbooksRequest extends FormRequest
*/
public function getTokenContent()
{
$token = Cache::get($this->state);
$state_data = Cache::get($this->state);
// Handle reconnect format: ['token' => $token, 'expected_realm_id' => ..., 'is_reconnect' => true]
// vs regular format: just the token string
$token = is_array($state_data) ? $state_data['token'] : $state_data;
$data = Cache::get($token);

View File

@@ -91,15 +91,23 @@ class QuickbooksService
'baseUrl' => $this->testMode ? CoreConstants::SANDBOX_DEVELOPMENT : CoreConstants::QBO_BASEURL,
];
$merged = array_merge($config, $this->ninjaAccessToken());
// Don't merge expired tokens when reconnection is required
// This allows getAuthorizationUrl() to work correctly
$requires_reconnect = $this->company->quickbooks && $this->company->quickbooks->requires_reconnect;
if (!$requires_reconnect) {
$config = array_merge($config, $this->ninjaAccessToken());
}
$this->sdk = DataService::Configure($merged);
$this->sdk = DataService::Configure($config);
$this->sdk->enableLog();
$this->sdk->setMinorVersion("75");
$this->sdk->throwExceptionOnError(true);
$this->checkToken();
if (!$requires_reconnect) {
$this->checkToken();
}
}
$this->invoice = new QbInvoice($this);

View File

@@ -24,7 +24,7 @@ class SdkWrapper
private $entities = ['Customer','Invoice', 'Item', 'SalesReceipt', 'Vendor', 'Purchase', 'Payment'];
private OAuth2AccessToken $token;
private ?OAuth2AccessToken $token = null;
public function __construct(public DataService $sdk, private Company $company)
{
@@ -33,8 +33,13 @@ class SdkWrapper
private function init(): self
{
$this->setNinjaAccessToken($this->company->quickbooks);
// Only set access token if quickbooks settings exist and have valid token data
// During reconnection flow, we may not have valid tokens yet
if ($this->company->quickbooks &&
$this->company->quickbooks->accessTokenKey &&
!$this->company->quickbooks->requires_reconnect) {
$this->setNinjaAccessToken($this->company->quickbooks);
}
return $this;

View File

@@ -340,8 +340,7 @@ Route::group(['middleware' => ['throttle:api', 'token_auth', 'valid_json','local
Route::post('quickbooks/sync', [QuickbooksController::class, 'sync'])->name('quickbooks.sync');
Route::post('quickbooks/settings', [QuickbooksController::class, 'settings'])->name('quickbooks.settings');
Route::post('quickbooks/disconnect', [QuickbooksController::class, 'disconnect'])->name('quickbooks.disconnect');
Route::get('quickbooks/status', [QuickbooksController::class, 'status'])->name('quickbooks.status');
Route::get('quickbooks/reconnect-url', [QuickbooksController::class, 'reconnectUrl'])->name('quickbooks.reconnect_url');
Route::post('quickbooks/reconnect_url', [QuickbooksController::class, 'reconnectUrl'])->name('quickbooks.reconnect_url');
Route::resource('recurring_expenses', RecurringExpenseController::class);
Route::post('recurring_expenses/bulk', [RecurringExpenseController::class, 'bulk'])->name('recurring_expenses.bulk');