neuer Ansatz ...
This commit is contained in:
parent
03e0516c08
commit
31f03b0d7c
|
|
@ -1,4 +1,13 @@
|
||||||
// qbo_helper.js - MIT OAUTH FLOW & VERBESSERTEM TOKEN HANDLING
|
// qbo_helper.js - DEFINITIVER FIX
|
||||||
|
//
|
||||||
|
// Kernproblem: client.refresh() ruft intern validateToken() auf,
|
||||||
|
// das das Token-Objekt prüft und "invalid" wirft wenn das Format
|
||||||
|
// nicht stimmt. Das passiert LOKAL, nicht bei Intuit.
|
||||||
|
//
|
||||||
|
// Lösung: refreshUsingToken(refreshTokenString) verwenden.
|
||||||
|
// Diese Methode akzeptiert den RT direkt als String und umgeht
|
||||||
|
// die validateToken()-Prüfung komplett.
|
||||||
|
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
const OAuthClient = require('intuit-oauth');
|
const OAuthClient = require('intuit-oauth');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
@ -35,10 +44,6 @@ const getOAuthClient = () => {
|
||||||
oauthClient.setToken(savedToken);
|
oauthClient.setToken(savedToken);
|
||||||
console.log("✅ Gespeicherter Token aus qbo_token.json geladen.");
|
console.log("✅ Gespeicherter Token aus qbo_token.json geladen.");
|
||||||
} else {
|
} else {
|
||||||
// WICHTIG: intuit-oauth braucht ein VOLLSTÄNDIGES Token-Objekt!
|
|
||||||
// Nur access_token + refresh_token reicht NICHT — die Library
|
|
||||||
// prüft intern auf token_type, expires_in, createdAt etc.
|
|
||||||
// und wirft "The Refresh token is invalid" wenn die fehlen.
|
|
||||||
const envToken = {
|
const envToken = {
|
||||||
token_type: 'bearer',
|
token_type: 'bearer',
|
||||||
access_token: process.env.QBO_ACCESS_TOKEN || '',
|
access_token: process.env.QBO_ACCESS_TOKEN || '',
|
||||||
|
|
@ -52,25 +57,39 @@ const getOAuthClient = () => {
|
||||||
oauthClient.setToken(envToken);
|
oauthClient.setToken(envToken);
|
||||||
console.log("ℹ️ Token aus .env geladen (Fallback).");
|
console.log("ℹ️ Token aus .env geladen (Fallback).");
|
||||||
} else {
|
} else {
|
||||||
console.warn("⚠️ Kein gültiger Token vorhanden. Bitte unter Settings → QBO autorisieren.");
|
console.warn("⚠️ Kein gültiger Token vorhanden.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return oauthClient;
|
return oauthClient;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt den oauthClient zurück, damit beim nächsten getOAuthClient()
|
|
||||||
* der Token frisch aus der Datei geladen wird.
|
|
||||||
*/
|
|
||||||
function resetOAuthClient() {
|
function resetOAuthClient() {
|
||||||
oauthClient = null;
|
oauthClient = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveTokens() {
|
function saveTokens() {
|
||||||
try {
|
try {
|
||||||
const token = getOAuthClient().getToken();
|
const client = getOAuthClient();
|
||||||
fs.writeFileSync(tokenFile, JSON.stringify(token, null, 2));
|
const token = client.getToken();
|
||||||
|
|
||||||
|
// Debug: Was genau bekommen wir vom Client?
|
||||||
|
console.log("💾 Speichere Token... refresh_token vorhanden:", !!token.refresh_token,
|
||||||
|
"| access_token Länge:", (token.access_token || '').length,
|
||||||
|
"| realmId:", token.realmId || 'FEHLT');
|
||||||
|
|
||||||
|
// Sicherstellen dass alle Pflichtfelder vorhanden sind
|
||||||
|
const tokenToSave = {
|
||||||
|
token_type: token.token_type || 'bearer',
|
||||||
|
access_token: token.access_token,
|
||||||
|
refresh_token: token.refresh_token,
|
||||||
|
expires_in: token.expires_in || 3600,
|
||||||
|
x_refresh_token_expires_in: token.x_refresh_token_expires_in || 8726400,
|
||||||
|
realmId: token.realmId || process.env.QBO_REALM_ID,
|
||||||
|
createdAt: token.createdAt || new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync(tokenFile, JSON.stringify(tokenToSave, null, 2));
|
||||||
console.log("💾 Tokens erfolgreich in qbo_token.json gespeichert.");
|
console.log("💾 Tokens erfolgreich in qbo_token.json gespeichert.");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("❌ Fehler beim Speichern der Tokens:", e.message);
|
console.error("❌ Fehler beim Speichern der Tokens:", e.message);
|
||||||
|
|
@ -80,27 +99,39 @@ function saveTokens() {
|
||||||
async function makeQboApiCall(requestOptions) {
|
async function makeQboApiCall(requestOptions) {
|
||||||
const client = getOAuthClient();
|
const client = getOAuthClient();
|
||||||
|
|
||||||
// Prüfen ob überhaupt ein Refresh Token vorhanden ist
|
|
||||||
const currentToken = client.getToken();
|
const currentToken = client.getToken();
|
||||||
if (!currentToken || !currentToken.refresh_token) {
|
if (!currentToken || !currentToken.refresh_token) {
|
||||||
throw new Error("Kein gültiger QBO Token vorhanden. Bitte unter Settings → 'Authorize QBO' klicken.");
|
throw new Error("Kein gültiger QBO Token vorhanden. Bitte Token erneuern.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const doRefresh = async () => {
|
const doRefresh = async () => {
|
||||||
console.log("🔄 QBO Token Refresh wird ausgeführt...");
|
console.log("🔄 QBO Token Refresh wird ausgeführt...");
|
||||||
|
|
||||||
|
// Den Refresh Token als String extrahieren
|
||||||
|
const refreshTokenStr = currentToken.refresh_token;
|
||||||
|
console.log("🔑 Refresh Token (erste 15 Zeichen):", refreshTokenStr.substring(0, 15) + "...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResponse = await client.refresh();
|
// KRITISCHER FIX: refreshUsingToken() statt refresh() verwenden!
|
||||||
console.log("✅ Token erfolgreich erneuert.");
|
//
|
||||||
|
// refresh() ruft intern validateToken() auf, das bei unvollständigem
|
||||||
|
// Token-Objekt "The Refresh token is invalid" wirft — OHNE jemals
|
||||||
|
// Intuit zu kontaktieren.
|
||||||
|
//
|
||||||
|
// refreshUsingToken() akzeptiert den RT als String und umgeht das.
|
||||||
|
const authResponse = await client.refreshUsingToken(refreshTokenStr);
|
||||||
|
console.log("✅ Token erfolgreich erneuert via refreshUsingToken().");
|
||||||
saveTokens();
|
saveTokens();
|
||||||
return authResponse;
|
return authResponse;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const errMsg = e.originalMessage || e.message || String(e);
|
const errMsg = e.originalMessage || e.message || String(e);
|
||||||
console.error("❌ Refresh fehlgeschlagen:", errMsg);
|
console.error("❌ Refresh fehlgeschlagen:", errMsg);
|
||||||
|
if (e.intuit_tid) console.error(" intuit_tid:", e.intuit_tid);
|
||||||
|
|
||||||
// Wenn der Refresh Token komplett ungültig ist → klare Meldung
|
if (errMsg.includes('invalid_grant')) {
|
||||||
if (errMsg.includes('invalid') || errMsg.includes('Authorize again')) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Der Refresh Token ist abgelaufen. Bitte unter Settings → 'Authorize QBO' neu autorisieren."
|
"Der Refresh Token ist bei Intuit ungültig (invalid_grant). " +
|
||||||
|
"Bitte im Playground einen neuen Token holen und set_qbo_token.js ausführen."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
|
|
@ -110,17 +141,14 @@ async function makeQboApiCall(requestOptions) {
|
||||||
try {
|
try {
|
||||||
const response = await client.makeApiCall(requestOptions);
|
const response = await client.makeApiCall(requestOptions);
|
||||||
|
|
||||||
// Prüfen, ob die Antwort JSON ist (manche Auth-Fehler sind HTML/Text)
|
|
||||||
const data = response.getJson ? response.getJson() : response.json;
|
const data = response.getJson ? response.getJson() : response.json;
|
||||||
|
|
||||||
if (data.fault && data.fault.error) {
|
if (data.fault && data.fault.error) {
|
||||||
const errorCode = data.fault.error[0].code;
|
const errorCode = data.fault.error[0].code;
|
||||||
|
|
||||||
// 3200 (Auth Failed), 3202, 3100 → Refresh versuchen
|
|
||||||
if (errorCode === '3200' || errorCode === '3202' || errorCode === '3100') {
|
if (errorCode === '3200' || errorCode === '3202' || errorCode === '3100') {
|
||||||
console.log(`⚠️ QBO meldet Token-Fehler (${errorCode}). Versuche Refresh und Retry...`);
|
console.log(`⚠️ QBO meldet Token-Fehler (${errorCode}). Versuche Refresh und Retry...`);
|
||||||
await doRefresh();
|
await doRefresh();
|
||||||
// Retry mit neuem Token
|
|
||||||
return await client.makeApiCall(requestOptions);
|
return await client.makeApiCall(requestOptions);
|
||||||
}
|
}
|
||||||
throw new Error(`QBO API Error ${errorCode}: ${data.fault.error[0].message}`);
|
throw new Error(`QBO API Error ${errorCode}: ${data.fault.error[0].message}`);
|
||||||
|
|
@ -130,7 +158,6 @@ async function makeQboApiCall(requestOptions) {
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// HTTP 401 Unauthorized fangen (falls die Lib wirft, statt data.fault zurückzugeben)
|
|
||||||
const isAuthError =
|
const isAuthError =
|
||||||
e.response?.status === 401 ||
|
e.response?.status === 401 ||
|
||||||
(e.authResponse && e.authResponse.response && e.authResponse.response.status === 401) ||
|
(e.authResponse && e.authResponse.response && e.authResponse.response.status === 401) ||
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue