invoice-system/qbo_helper.js

117 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
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)) {
savedToken = JSON.parse(fs.readFileSync(tokenFile, 'utf8'));
}
} catch (e) {
console.error("❌ Fehler beim Laden des gespeicherten Tokens:", e);
}
if (savedToken) {
oauthClient.setToken(savedToken);
console.log("✅ Gespeicherter Token geladen.");
} else {
// Fallback auf .env (initiale Tokens)
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);
}
}
async function makeQboApiCall(requestOptions) {
const client = getOAuthClient();
// Funktion zum Aktualisieren des Tokens
const doRefresh = async () => {
console.log("🔄 QBO Token Refresh wird ausgeführt...");
try {
const authResponse = await client.refresh();
console.log("✅ Token erfolgreich erneuert.");
saveTokens(); // Neue Tokens persistent speichern
return authResponse;
} catch (e) {
console.error("❌ Refresh fehlgeschlagen:", e.originalMessage || e);
throw e;
}
};
// Vorab-Prüfung: Wenn Token ungültig (basierend auf expires_at), refreshen
if (!client.isAccessTokenValid()) {
console.log("⚠️ Access Token ist ungültig oder abgelaufen. Refresh wird durchgeführt.");
await doRefresh();
}
try {
// API-Aufruf durchführen
const response = await client.makeApiCall(requestOptions);
// Prüfen, ob QBO eine Fehlermeldung im Body sendet
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}`);
}
// Erfolgreichen Aufruf: Tokens speichern (falls geändert)
saveTokens();
return response;
} catch (e) {
// HTTP 401 Unauthorized fangen
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);
}
// Alle anderen Fehler weiterwerfen
throw e;
}
}
module.exports = {
getOAuthClient,
makeQboApiCall
};