Files
Webklar-Kundenbereich/server/config.js
T
ClaudeandClaude Opus 5.5 65429aa3ac Wartungs-Abo (50 EUR/Monat) ueber Stripe mit Stundenkonto im Portal
- Bestellung auf der Portal-Startseite: Zusammenfassung, AGB- und
  Vorab-Leistungs-Zustimmung, Button 'Zahlungspflichtig bestellen',
  danach Stripe Checkout (Abo, Kleinunternehmer-Fusszeile)
- Stripe-Webhook /api/abo/webhook spiegelt Abos nach Appwrite
  (subscriptions) und schreibt je bezahlter Rechnung 60 min gut
  (hourCredits, idempotent ueber Invoice-ID, 12 Monate gueltig)
- Stundenkonto: FIFO-Verbrauch aus Worksheets mit countsToPlan,
  15-Minuten-Takt, Mehraufwand separat ausgewiesen
- Stripe-Kundenportal fuer Zahlungsart/Rechnungen/Kuendigung
- Oeffentlicher Kuendigungsbutton (§ 312k) und Widerrufsbutton
  (§ 356a) mit Eingangsbestaetigung per Mail
- Abo-Bedingungen inkl. Leistungsbeschreibung und Widerrufsbelehrung,
  als Anhang der Vertragsbestaetigung

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 12:04:45 +00:00

133 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
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.
import 'dotenv/config'
/** Einzige erlaubte Datenbank (Ticketsystem). Keine separate Portal-DB mehr. */
export const WOMS_DATABASE_ID = 'woms-database'
const DEPRECATED_DATABASE_IDS = new Set([
'6943bf0e0003291f8c35', // frühere Portal-/Legacy-DB
])
function resolveDatabaseId() {
const fromEnv = (process.env.APPWRITE_DATABASE_ID || '').trim()
if (!fromEnv || fromEnv === WOMS_DATABASE_ID) return WOMS_DATABASE_ID
if (DEPRECATED_DATABASE_IDS.has(fromEnv)) {
console.warn(
`[config] Veraltete APPWRITE_DATABASE_ID "${fromEnv}" – es wird nur ${WOMS_DATABASE_ID} verwendet.`
)
return WOMS_DATABASE_ID
}
console.warn(
`[config] APPWRITE_DATABASE_ID="${fromEnv}" ignoriert – Portal nutzt ausschließlich ${WOMS_DATABASE_ID}.`
)
return WOMS_DATABASE_ID
}
export const config = {
port: Number(process.env.PORT) || 3000,
sessionSecret: process.env.SESSION_SECRET || '',
cookieName: process.env.SESSION_COOKIE_NAME || 'webklar_portal_session',
allowedCustomerStatuses: (process.env.ALLOWED_CUSTOMER_STATUSES || 'active')
.split(',')
.map((s) => s.trim())
.filter(Boolean),
appwrite: {
endpoint: process.env.APPWRITE_ENDPOINT || 'https://ticket.webklar.com/v1',
projectId: process.env.APPWRITE_PROJECT_ID || '6a1058610003c5a13a05',
databaseId: resolveDatabaseId(),
apiKey: process.env.APPWRITE_API_KEY || '',
},
collections: {
customers: process.env.APPWRITE_COLLECTION_CUSTOMERS || 'customers',
customerPortalAccess: process.env.APPWRITE_COLLECTION_CUSTOMER_PORTAL_ACCESS || 'customerPortalAccess',
websiteProjects: process.env.APPWRITE_COLLECTION_WEBSITE_PROJECTS || 'websiteProjects',
portalFeatures: process.env.APPWRITE_COLLECTION_PORTAL_FEATURES || 'portalFeatures',
workorders: process.env.APPWRITE_COLLECTION_WORKORDERS || 'workorders',
worksheets: process.env.APPWRITE_COLLECTION_WORKSHEETS || 'worksheets',
employees: process.env.APPWRITE_COLLECTION_EMPLOYEES || 'employees',
portalMessages: process.env.APPWRITE_COLLECTION_PORTAL_MESSAGES || 'portalMessages',
subscriptions: process.env.APPWRITE_COLLECTION_SUBSCRIPTIONS || 'subscriptions',
hourCredits: process.env.APPWRITE_COLLECTION_HOUR_CREDITS || 'hourCredits',
},
publicBaseUrl: (process.env.PORTAL_PUBLIC_URL || 'https://project.webklar.com').replace(/\/$/, ''),
// Wartungs-Abo (Stripe Billing). Preis/Produkt legt der Server per lookup_key selbst an.
stripe: {
secretKey: process.env.STRIPE_SECRET_KEY || '',
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET || '',
priceLookupKey: process.env.STRIPE_ABO_PRICE_LOOKUP_KEY || 'webklar_wartung_monatlich_5000',
priceAmountCents: 5000,
productName: 'Webklar Wartungs-Abo',
// Kleinunternehmer: Pflichthinweis auf jeder Stripe-Rechnung
invoiceFooter:
process.env.STRIPE_INVOICE_FOOTER ||
'Gemäß § 19 UStG wird keine Umsatzsteuer berechnet (Kleinunternehmerregelung). WEBklar GbR, Am Schwimmbad 10, 67722 Winnweiler',
},
abo: {
termsVersion: '2026-09-25',
minutesPerMonth: 60,
creditValidityMonths: 12,
usageRoundingMinutes: 15,
},
smtp: {
host: process.env.SMTP_HOST || '',
port: Number(process.env.SMTP_PORT) || 587,
secure: (process.env.SMTP_SECURE || 'false') === 'true',
user: process.env.SMTP_USERNAME || '',
pass: process.env.SMTP_PASSWORD || '',
from: process.env.SMTP_FROM || process.env.SMTP_USERNAME || '',
},
chatNotify: {
emails: (process.env.CHAT_NOTIFY_EMAILS || 'kenso@webklar.com,andrej@webklar.com')
.split(',')
.map((s) => s.trim())
.filter(Boolean),
throttleMin: Number(process.env.CHAT_NOTIFY_THROTTLE_MIN) || 15,
},
gitea: {
webhookToken: process.env.GITEA_WEBHOOK_TOKEN || '',
baseUrl: process.env.GITEA_BASE_URL || 'https://git.webklar.com',
apiToken: process.env.GITEA_API_TOKEN || '',
templateOwner: process.env.GITEA_TEMPLATE_OWNER || 'knso',
templateRepo: process.env.GITEA_TEMPLATE_REPO || 'webklar-preview-template',
defaultOwner: process.env.GITEA_DEFAULT_OWNER || 'knso',
},
cors: {
allowedOrigins: (process.env.ADMIN_CORS_ORIGINS || 'https://ticket.webklar.com,http://localhost:5173')
.split(',')
.map((s) => s.trim())
.filter(Boolean),
},
preview: {
baseHost: process.env.PREVIEW_BASE_HOST || 'project.webklar.com',
deployRoot: process.env.PREVIEW_DEPLOY_ROOT || '',
traefikDynamicDir: process.env.TRAEFIK_DYNAMIC_DIR || '',
sessionSecret:
process.env.PREVIEW_SESSION_SECRET || process.env.SESSION_SECRET || '',
cookieDomain: process.env.PREVIEW_COOKIE_DOMAIN || '.project.webklar.com',
sessionMaxAge: Number(process.env.PREVIEW_SESSION_MAX_AGE || 60 * 60 * 24 * 7),
},
invoiceNinja: {
url: (process.env.INVOICE_NINJA_URL || 'https://invoice.webklar.com').replace(/\/$/, ''),
apiToken: process.env.INVOICE_NINJA_API_TOKEN || '',
},
}
export function assertServerConfig() {
if (!config.sessionSecret || config.sessionSecret.length < 32) {
console.warn('[config] SESSION_SECRET fehlt oder ist zu kurz (min. 32 Zeichen).')
}
if (!config.appwrite.apiKey) {
console.warn('[config] APPWRITE_API_KEY fehlt – DB-Zugriff und Webhook schlagen fehl.')
}
}