// 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 };