mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2026-04-18 12:10:50 +00:00
Allow QB reconnects
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user