invoice-system/qbo_helper.js

116 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// qbo_helper.js - FINALE VERSION
require('dotenv').config();
const OAuthClient = require('intuit-oauth');
const fs = require('fs');
const path = require('path');
let oauthClient = null;
const tokenFile = path.join(__dirname, 'qbo_token.json');
const getOAuthClient = () => {
if (!oauthClient) {
oauthClient = new OAuthClient({
clientId: process.env.QBO_CLIENT_ID,
clientSecret: process.env.QBO_CLIENT_SECRET,
environment: process.env.QBO_ENVIRONMENT || 'sandbox',
redirectUri: process.env.QBO_REDIRECT_URI
});
let savedToken = null;
try {
if (fs.existsSync(tokenFile)) {
// Wir lesen nur, wenn es kein Ordner ist (für lokale Tests ohne Docker)
const stat = fs.statSync(tokenFile);
if (stat.isFile()) {
const content = fs.readFileSync(tokenFile, 'utf8');
if (content.trim() !== "{}") {
savedToken = JSON.parse(content);
}
}
}
} catch (e) {
console.error("❌ Fehler beim Laden des gespeicherten Tokens:", e.message);
}
if (savedToken) {
oauthClient.setToken(savedToken);
console.log("✅ Gespeicherter Token aus qbo_token.json geladen.");
} else {
const envToken = {
access_token: process.env.QBO_ACCESS_TOKEN || '',
refresh_token: process.env.QBO_REFRESH_TOKEN || '',
realmId: process.env.QBO_REALM_ID
};
oauthClient.setToken(envToken);
console.log(" Token aus .env geladen (Fallback).");
}
}
return oauthClient;
};
function saveTokens() {
try {
const token = getOAuthClient().getToken();
fs.writeFileSync(tokenFile, JSON.stringify(token, null, 2));
console.log("💾 Tokens erfolgreich in qbo_token.json gespeichert.");
} catch (e) {
console.error("❌ Fehler beim Speichern der Tokens:", e.message);
}
}
async function makeQboApiCall(requestOptions) {
const client = getOAuthClient();
const doRefresh = async () => {
console.log("🔄 QBO Token Refresh wird ausgeführt...");
try {
const authResponse = await client.refresh();
console.log("✅ Token erfolgreich erneuert.");
saveTokens();
return authResponse;
} catch (e) {
console.error("❌ Refresh fehlgeschlagen:", e.originalMessage || e);
throw e;
}
};
// --- WICHTIG: KEINE isAccessTokenValid() PRÜFUNG HIER! ---
// Wir vertrauen darauf, dass der Token (egal ob Datei oder .env) funktioniert.
// Wir refreshen nur, wenn QBO uns abweist.
try {
const response = await client.makeApiCall(requestOptions);
const data = response.getJson ? response.getJson() : response.json;
if (data.fault && data.fault.error) {
const errorCode = data.fault.error[0].code;
if (errorCode === '3202' || errorCode === '3100') {
console.log(`⚠️ QBO meldet Token-Fehler (${errorCode}). Versuche Refresh und Retry...`);
await doRefresh();
return await client.makeApiCall(requestOptions);
}
throw new Error(`QBO API Error ${errorCode}: ${data.fault.error[0].message}`);
}
// Bei Erfolg: Speichern (falls sich intern was geändert hat durch die Lib)
saveTokens();
return response;
} catch (e) {
const isAuthError = e.response?.status === 401 || (e.authResponse && e.authResponse.response && e.authResponse.response.status === 401);
if (isAuthError) {
console.log("⚠️ 401 Unauthorized erhalten. Versuche Refresh und Retry...");
await doRefresh();
return await client.makeApiCall(requestOptions);
}
throw e;
}
}
module.exports = {
getOAuthClient,
makeQboApiCall
};