/** * Backend Integration für Skrift Konfigurator * Erweitert handleOrderSubmit um Backend-API Calls */ import SkriftBackendAPI from './configurator-api.js'; import { preparePlaceholdersForIndex } from './configurator-utils.js'; /** * Bereitet Letter-Daten für Backend vor */ function prepareLettersForBackend(state) { const letters = []; const quantity = parseInt(state.answers?.quantity) || 1; // Haupttext const mainText = state.answers?.letterText || state.answers?.text || state.answers?.briefText || ''; const font = state.answers?.font || 'tilda'; const format = state.answers?.format || 'A4'; // Für jede Kopie einen Letter-Eintrag erstellen mit individuellen Platzhaltern for (let i = 0; i < quantity; i++) { const placeholders = preparePlaceholdersForIndex(state, i); letters.push({ index: i, text: mainText, font: mapFontToBackend(font), format: mapFormatToBackend(format), placeholders: placeholders, type: 'letter', }); } return letters; } /** * Bereitet Envelope-Daten für Backend vor */ function prepareEnvelopesForBackend(state) { const envelopes = []; // envelope ist ein boolean (true/false), nicht 'yes'/'no' const hasEnvelope = state.answers?.envelope === true; console.log('[Backend Integration] prepareEnvelopesForBackend:', { envelope: state.answers?.envelope, hasEnvelope, envelopeMode: state.answers?.envelopeMode, }); if (!hasEnvelope) { return envelopes; } const quantity = parseInt(state.answers?.quantity) || 1; const envelopeMode = state.answers?.envelopeMode || 'recipientData'; const format = state.answers?.format || 'A4'; const font = state.answers?.envelopeFont || state.answers?.font || 'tilda'; // Envelope Format bestimmen const envelopeFormat = format === 'a4' ? 'DIN_LANG' : 'C6'; if (envelopeMode === 'recipientData') { // Empfängeradresse-Modus: Ein Envelope pro Brief mit individuellen Empfängerdaten for (let i = 0; i < quantity; i++) { const placeholders = preparePlaceholdersForIndex(state, i); const recipient = state.recipientRows?.[i] || {}; // Umschlagtext aus Empfängerdaten zusammenbauen const lines = []; const fullName = `${recipient.firstName || ''} ${recipient.lastName || ''}`.trim(); if (fullName) lines.push(fullName); const streetLine = `${recipient.street || ''} ${recipient.houseNumber || ''}`.trim(); if (streetLine) lines.push(streetLine); const location = `${recipient.zip || ''} ${recipient.city || ''}`.trim(); if (location) lines.push(location); if (recipient.country && recipient.country !== 'Deutschland') { lines.push(recipient.country); } envelopes.push({ index: i, text: lines.join('\n'), font: mapFontToBackend(font), format: envelopeFormat, placeholders: placeholders, type: 'envelope', envelopeType: 'recipient', }); } } else if (envelopeMode === 'customText') { // Custom Text Modus mit Platzhaltern const customText = state.answers?.envelopeCustomText || ''; for (let i = 0; i < quantity; i++) { const placeholders = preparePlaceholdersForIndex(state, i); envelopes.push({ index: i, text: customText, font: mapFontToBackend(font), format: envelopeFormat, placeholders: placeholders, type: 'envelope', envelopeType: 'custom', }); } } return envelopes; } // Hinweis: preparePlaceholdersForIndex ist jetzt in configurator-utils.js /** * Mapped Frontend-Font zu Backend-Font */ function mapFontToBackend(frontendFont) { const fontMap = { 'tilda': 'tilda', 'alva': 'alva', 'ellie': 'ellie', // Füge weitere Mappings hinzu falls nötig }; return fontMap[frontendFont] || 'tilda'; } /** * Mapped Frontend-Format zu Backend-Format */ function mapFormatToBackend(frontendFormat) { const formatMap = { 'a4': 'A4', 'a6p': 'A6_PORTRAIT', 'a6l': 'A6_LANDSCAPE', 'A4': 'A4', 'A6_PORTRAIT': 'A6_PORTRAIT', 'A6_LANDSCAPE': 'A6_LANDSCAPE', }; return formatMap[frontendFormat] || 'A4'; } /** * Ermittelt das Umschlag-Format basierend auf Brief-Format */ function getEnvelopeFormat(letterFormat) { const format = String(letterFormat).toLowerCase(); if (format === 'a4') return 'DIN_LANG'; if (format === 'a6p' || format === 'a6l') return 'C6'; return 'DIN_LANG'; } /** * Formatiert Format für lesbare Ausgabe */ function formatFormatLabel(format) { const labels = { 'a4': 'A4 Hochformat', 'a6p': 'A6 Hochformat', 'a6l': 'A6 Querformat', }; return labels[format] || format; } /** * Formatiert Font für lesbare Ausgabe */ function formatFontLabel(font) { const labels = { 'tilda': 'Tilda', 'alva': 'Alva', 'ellie': 'Ellie', }; return labels[font] || font; } /** * Baut das komplette Webhook-Datenobjekt zusammen * Enthält ALLE relevanten Felder für Bestellbestätigung und n8n Workflow */ function buildWebhookData(state, backendResult) { const answers = state.answers || {}; const order = state.order || {}; const quote = state.quote || {}; const ctx = state.ctx || {}; // Gutschein-Informationen const voucherCode = order.voucherStatus?.valid ? order.voucherCode : null; const voucherDiscount = order.voucherStatus?.valid ? (order.voucherStatus.discount || 0) : 0; // Umschlag-Format ermitteln const envelopeFormat = answers.envelope ? getEnvelopeFormat(answers.format) : null; // Inland/Ausland zählen let domesticCount = 0; let internationalCount = 0; const addressMode = state.addressMode || 'classic'; const rows = addressMode === 'free' ? (state.freeAddressRows || []) : (state.recipientRows || []); for (const row of rows) { if (!row) continue; const country = addressMode === 'free' ? (row.line5 || '') : (row.country || ''); const countryLower = country.toLowerCase().trim(); const isDomestic = !countryLower || countryLower === 'deutschland' || countryLower === 'germany' || countryLower === 'de'; if (isDomestic) { domesticCount++; } else { internationalCount++; } } return { // === BESTELLNUMMER & ZEITSTEMPEL === orderNumber: backendResult?.orderNumber || null, timestamp: new Date().toISOString(), // === KUNDE === customerType: answers.customerType || 'private', customerTypeLabel: answers.customerType === 'business' ? 'Geschäftskunde' : 'Privatkunde', // === PRODUKT === product: ctx.product?.key || null, productLabel: ctx.product?.label || null, productCategory: ctx.product?.category || null, // === MENGE === quantity: parseInt(answers.quantity) || 0, domesticCount: domesticCount, internationalCount: internationalCount, // === FORMAT & SCHRIFT === format: answers.format || null, formatLabel: formatFormatLabel(answers.format), font: answers.font || 'tilda', fontLabel: formatFontLabel(answers.font || 'tilda'), // === VERSAND === shippingMode: answers.shippingMode || null, shippingModeLabel: answers.shippingMode === 'direct' ? 'Einzelversand durch Skrift' : 'Sammellieferung', // === UMSCHLAG === envelopeIncluded: answers.envelope === true, envelopeFormat: envelopeFormat, envelopeFormatLabel: envelopeFormat === 'DIN_LANG' ? 'DIN Lang' : (envelopeFormat === 'C6' ? 'C6' : null), envelopeMode: answers.envelopeMode || null, envelopeModeLabel: answers.envelopeMode === 'recipientData' ? 'Empfängeradresse' : (answers.envelopeMode === 'customText' ? 'Individueller Text' : null), envelopeFont: answers.envelopeFont || answers.font || 'tilda', envelopeFontLabel: formatFontLabel(answers.envelopeFont || answers.font || 'tilda'), envelopeCustomText: answers.envelopeCustomText || null, // === INHALT === contentCreateMode: answers.contentCreateMode || null, contentCreateModeLabel: answers.contentCreateMode === 'self' ? 'Selbst erstellt' : (answers.contentCreateMode === 'textservice' ? 'Textservice' : null), letterText: answers.letterText || null, // === MOTIV === motifNeeded: answers.motifNeed === true, motifSource: answers.motifSource || null, motifSourceLabel: answers.motifSource === 'upload' ? 'Eigenes Motiv hochgeladen' : (answers.motifSource === 'printed' ? 'Bedruckte Karten verwenden' : (answers.motifSource === 'design' ? 'Designservice' : null)), motifFileName: answers.motifFileName || null, motifFileMeta: answers.motifFileMeta || null, // === SERVICES === serviceText: answers.serviceText === true, serviceDesign: answers.serviceDesign === true, serviceApi: answers.serviceApi === true, // === FOLLOW-UP DETAILS (nur bei Follow-ups) === followupYearlyVolume: ctx.product?.isFollowUp ? (answers.followupYearlyVolume || null) : null, followupCreateMode: ctx.product?.isFollowUp ? (answers.followupCreateMode || null) : null, followupCreateModeLabel: ctx.product?.isFollowUp ? ( answers.followupCreateMode === 'auto' ? 'Automatisch (API)' : (answers.followupCreateMode === 'manual' ? 'Manuell' : null) ) : null, followupSourceSystem: ctx.product?.isFollowUp ? (answers.followupSourceSystem || null) : null, followupTriggerDescription: ctx.product?.isFollowUp ? (answers.followupTriggerDescription || null) : null, followupCheckCycle: ctx.product?.isFollowUp ? (answers.followupCheckCycle || null) : null, followupCheckCycleLabel: ctx.product?.isFollowUp ? ( answers.followupCheckCycle === 'weekly' ? 'Wöchentlich' : (answers.followupCheckCycle === 'monthly' ? 'Monatlich' : (answers.followupCheckCycle === 'quarterly' ? 'Quartalsweise' : null)) ) : null, // === GUTSCHEIN === voucherCode: voucherCode, voucherDiscount: voucherDiscount, // === PREISE === currency: quote.currency || 'EUR', subtotalNet: quote.subtotalNet || 0, vatRate: quote.vatRate || 0.19, vatAmount: quote.vatAmount || 0, totalGross: quote.totalGross || 0, priceLines: quote.lines || [], // === KUNDENDATEN (Rechnungsadresse) === billingFirstName: order.billing?.firstName || '', billingLastName: order.billing?.lastName || '', billingCompany: order.billing?.company || '', billingEmail: order.billing?.email || '', billingPhone: order.billing?.phone || '', billingStreet: order.billing?.street || '', billingHouseNumber: order.billing?.houseNumber || '', billingZip: order.billing?.zip || '', billingCity: order.billing?.city || '', billingCountry: order.billing?.country || 'Deutschland', // === LIEFERADRESSE (falls abweichend) === shippingDifferent: order.shippingDifferent || false, shippingFirstName: order.shippingDifferent ? (order.shipping?.firstName || '') : null, shippingLastName: order.shippingDifferent ? (order.shipping?.lastName || '') : null, shippingCompany: order.shippingDifferent ? (order.shipping?.company || '') : null, shippingStreet: order.shippingDifferent ? (order.shipping?.street || '') : null, shippingHouseNumber: order.shippingDifferent ? (order.shipping?.houseNumber || '') : null, shippingZip: order.shippingDifferent ? (order.shipping?.zip || '') : null, shippingCity: order.shippingDifferent ? (order.shipping?.city || '') : null, shippingCountry: order.shippingDifferent ? (order.shipping?.country || 'Deutschland') : null, // === EMPFÄNGERLISTE === addressMode: addressMode, addressModeLabel: addressMode === 'free' ? 'Freie Adresszeilen' : 'Klassische Adresse', recipients: addressMode === 'classic' ? (state.recipientRows || []).map((r, i) => ({ index: i, firstName: r?.firstName || '', lastName: r?.lastName || '', street: r?.street || '', houseNumber: r?.houseNumber || '', zip: r?.zip || '', city: r?.city || '', country: r?.country || 'Deutschland', })) : null, recipientsFree: addressMode === 'free' ? (state.freeAddressRows || []).map((r, i) => ({ index: i, line1: r?.line1 || '', line2: r?.line2 || '', line3: r?.line3 || '', line4: r?.line4 || '', line5: r?.line5 || '', })) : null, // === PLATZHALTER === placeholdersEnvelope: state.placeholders?.envelope || [], placeholdersLetter: state.placeholders?.letter || [], placeholderValues: state.placeholderValues || {}, // === BACKEND RESULT (falls vorhanden) === backendPath: backendResult?.path || null, backendFiles: backendResult?.files || [], backendSummary: backendResult?.summary || null, }; } /** * Bereitet Metadaten für Backend vor */ function prepareOrderMetadata(state) { return { customer: { type: state.answers?.customerType || 'private', firstName: state.order?.firstName || '', lastName: state.order?.lastName || '', company: state.order?.company || '', email: state.order?.email || '', phone: state.order?.phone || '', street: state.order?.street || '', zip: state.order?.zip || '', city: state.order?.city || '', }, orderDate: new Date().toISOString(), product: state.ctx?.product?.key || '', quantity: state.answers?.quantity || 1, format: state.answers?.format || 'A4', shippingMode: state.answers?.shippingMode || 'direct', quote: state.quote || {}, voucherCode: state.order?.voucherCode || null, }; } /** * Erweiterte Order-Submit Funktion mit Backend-Integration */ export async function handleOrderSubmitWithBackend(state) { const isB2B = state.answers?.customerType === "business"; const backend = window.SkriftConfigurator?.settings?.backend_connection || {}; const webhookUrl = backend.order_webhook_url; const redirectUrlBusiness = backend.redirect_url_business; const redirectUrlPrivate = backend.redirect_url_private; const api = window.SkriftBackendAPI; // Prüfe ob Backend konfiguriert ist if (!backend.api_url) { console.warn('[Backend Integration] Backend API URL nicht konfiguriert'); // Fallback zur alten Logik return handleOrderSubmitLegacy(state); } try { // 1. Backend Health Check const isHealthy = await api.healthCheck(); if (!isHealthy) { throw new Error('Backend ist nicht erreichbar'); } // 2. Bestellnummer generieren (fortlaufend vom WP-Backend) const orderNumber = await api.generateOrderNumber(); // 3. Daten vorbereiten const letters = prepareLettersForBackend(state); const envelopes = prepareEnvelopesForBackend(state); const metadata = prepareOrderMetadata(state); console.log('[Backend Integration] Generating order:', { orderNumber, letters, envelopes, metadata, }); // 4. Order im Backend generieren const result = await api.generateOrder( orderNumber, letters, envelopes, metadata ); if (!result.success) { throw new Error(result.error || 'Order generation failed'); } console.log('[Backend Integration] Order generated successfully:', result); // 5. Gutschein als verwendet markieren (falls vorhanden) const voucherCode = state.order?.voucherStatus?.valid ? state.order.voucherCode : null; if (voucherCode) { try { const restUrl = window.SkriftConfigurator?.restUrl || "/wp-json/"; await fetch(restUrl + "skrift/v1/voucher/use", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ code: voucherCode }), }); } catch (error) { console.warn('[Backend Integration] Fehler beim Markieren des Gutscheins:', error); } } // 6. Webhook aufrufen (wenn konfiguriert) if (webhookUrl) { try { const webhookData = buildWebhookData(state, result); const response = await fetch(webhookUrl, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(webhookData), }); if (!response.ok) { console.warn('[Backend Integration] Webhook call failed:', response.statusText); } } catch (error) { console.warn('[Backend Integration] Webhook error:', error); } } // 7. Weiterleitung if (isB2B) { if (redirectUrlBusiness) { // Bestellnummer als Query-Parameter anhängen const redirectUrl = new URL(redirectUrlBusiness); redirectUrl.searchParams.set('orderNumber', result.orderNumber); window.location.href = redirectUrl.toString(); } else { alert( `Vielen Dank für Ihre Bestellung!\n\nBestellnummer: ${result.orderNumber}\n\nSie erhalten in Kürze eine Bestätigungs-E-Mail mit allen Details.` ); } } else { // Privatkunde: Zu PayPal weiterleiten if (redirectUrlPrivate) { const redirectUrl = new URL(redirectUrlPrivate); redirectUrl.searchParams.set('orderNumber', result.orderNumber); window.location.href = redirectUrl.toString(); } else { alert( `Bestellung erfolgreich erstellt!\n\nBestellnummer: ${result.orderNumber}\n\nWeiterleitung zu PayPal folgt...` ); } } } catch (error) { console.error('[Backend Integration] Order submission failed:', error); alert( `Fehler bei der Bestellverarbeitung:\n\n${error.message}\n\nBitte versuchen Sie es erneut oder kontaktieren Sie uns.` ); } } /** * Legacy Order-Submit (Fallback ohne Backend) */ async function handleOrderSubmitLegacy(state) { const isB2B = state.answers?.customerType === "business"; const backend = window.SkriftConfigurator?.settings?.backend_connection || {}; const webhookUrl = backend.order_webhook_url; const redirectUrlBusiness = backend.redirect_url_business; const redirectUrlPrivate = backend.redirect_url_private; // Gutschein als verwendet markieren const voucherCode = state.order?.voucherStatus?.valid ? state.order.voucherCode : null; if (voucherCode) { try { const restUrl = window.SkriftConfigurator?.restUrl || "/wp-json/"; await fetch(restUrl + "skrift/v1/voucher/use", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ code: voucherCode }), }); } catch (error) { console.warn('Fehler beim Markieren des Gutscheins:', error); } } // Webhook aufrufen if (isB2B && webhookUrl) { try { const webhookData = buildWebhookData(state, null); await fetch(webhookUrl, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(webhookData), }); } catch (error) { console.warn('Webhook error:', error); } } // Weiterleitung if (isB2B && redirectUrlBusiness) { window.location.href = redirectUrlBusiness; } else if (!isB2B && redirectUrlPrivate) { window.location.href = redirectUrlPrivate; } else { alert("Vielen Dank für Ihre Bestellung!"); } } export default { handleOrderSubmitWithBackend, prepareLettersForBackend, prepareEnvelopesForBackend, mapFontToBackend, mapFormatToBackend, buildWebhookData, };