/* ===================================================================== lib/bestellung-erstellen.js — die eigentliche "Bestellung anlegen"-Logik. 1:1 portiert aus functions/_shared/bestellung-erstellen.js, D1-Aufrufe (async, .bind().run()) auf better- sqlite3 (synchron) umgestellt. Gleiche Validierung, gleiche "nur einmal pro Person"-Logik, gleicher E-Mail-Versand. ===================================================================== */ import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { db } from "../db.js"; import { sendeEmail, escapeHtmlFuerEmail } from "./email.js"; const __dirname = dirname(fileURLToPath(import.meta.url)); const gutscheineRaw = JSON.parse( readFileSync(join(__dirname, "..", "..", "src", "content", "gutscheine.json"), "utf-8") ); const ERLAUBTE_LAENDER = ["de", "at", "ch", "lu"]; const gutscheine = gutscheineRaw?.codes || []; const BENACHRICHTIGUNGS_EMAIL = "vansdiyundbastelbedarf@gmail.com"; const LAND_NAME = { de: "Deutschland", at: "Österreich", ch: "Schweiz", lu: "Luxemburg" }; function formatPreis(n) { return `${Number(n).toFixed(2).replace(".", ",")} €`; } function bestellBenachrichtigungHtml({ bestellnummer, name, email, land, artikel, summe, status }) { const artikelZeilen = artikel .map((a) => `
  • ${a.menge}× ${escapeHtmlFuerEmail(a.name)} — ${formatPreis(a.preis * a.menge)}
  • `) .join(""); const statusZeile = status === "zahlungOffen" ? `

    ⏳ Zahlung noch ausstehend — Überweisung, bitte Zahlungseingang abwarten, bevor die Ware verschickt wird.

    ` : `

    ✅ Zahlung bestätigt (PayPal)

    `; return `

    🩵 Neue Bestellung ${escapeHtmlFuerEmail(bestellnummer)}

    ${statusZeile}

    Von: ${escapeHtmlFuerEmail(name)} (${escapeHtmlFuerEmail(email)})
    Land: ${escapeHtmlFuerEmail(LAND_NAME[land] || land)}

    Artikel:

    Summe: ${formatPreis(summe)}

    → Zur Bestellverwaltung

    `; } function findeGutschein(code) { const normalisiert = String(code || "").trim().toUpperCase(); if (!normalisiert) return null; return gutscheine.find((g) => String(g.code || "").toUpperCase() === normalisiert) || null; } /** * Validiert die Bestelldaten, legt sie in SQLite an und verschickt die Benachrichtigungsmail. * @param {*} body Roh-Payload vom Checkout (gleiches Format wie bisher) * @param {{status: "zahlungOffen"|"bezahlt", paypalOrderId?: string|null}} optionen */ export async function erstelleBestellung(body, optionen) { const { status, paypalOrderId = null } = optionen; const land = String(body.land || "").toLowerCase(); if (!ERLAUBTE_LAENDER.includes(land)) { return { ok: false, httpStatus: 400, error: "Ungültiges Land." }; } const kunde = body.kunde && typeof body.kunde === "object" ? body.kunde : {}; const name = String(kunde.name || "").trim(); const email = String(kunde.email || "").trim(); if (!name || !email || !email.includes("@")) { return { ok: false, httpStatus: 400, error: "Ungültige Kundendaten." }; } const artikel = Array.isArray(body.artikel) ? body.artikel : []; if (artikel.length === 0) { return { ok: false, httpStatus: 400, error: "Die Bestellung enthält keine Artikel." }; } for (const a of artikel) { if (!a || typeof a.slug !== "string" || !a.slug.trim() || typeof a.name !== "string" || !a.name.trim()) { return { ok: false, httpStatus: 400, error: "Ungültige Artikeldaten." }; } if (!Number.isFinite(a.menge) || a.menge <= 0) { return { ok: false, httpStatus: 400, error: "Ungültige Artikelmenge." }; } if (!Number.isFinite(a.preis) || a.preis < 0) { return { ok: false, httpStatus: 400, error: "Ungültiger Artikelpreis." }; } } const summe = Number(body.summe); if (!Number.isFinite(summe) || summe < 0) { return { ok: false, httpStatus: 400, error: "Ungültige Bestellsumme." }; } const zwischensumme = Number.isFinite(Number(body.zwischensumme)) ? Number(body.zwischensumme) : 0; const versandkosten = Number.isFinite(Number(body.versandkosten)) ? Number(body.versandkosten) : 0; const rabattGesamt = Number.isFinite(Number(body.rabattGesamt)) ? Number(body.rabattGesamt) : 0; const gutscheinCodeRoh = typeof body.gutscheinCode === "string" ? body.gutscheinCode.trim() : ""; let gutscheinCodeNormalisiert = null; if (gutscheinCodeRoh) { const gutschein = findeGutschein(gutscheinCodeRoh); gutscheinCodeNormalisiert = gutscheinCodeRoh.toUpperCase(); if (gutschein && gutschein.einmaligProPerson) { const { anzahl } = db .prepare( `SELECT COUNT(*) as anzahl FROM orders WHERE gutschein_code = ? AND LOWER(kunde_email) = LOWER(?) AND status != 'storniert'` ) .get(gutscheinCodeNormalisiert, email); if (anzahl > 0) { return { ok: false, httpStatus: 400, error: "Dieser Gutscheincode wurde bereits mit dieser E-Mail-Adresse eingelöst und gilt nur einmal pro Person.", }; } } } const now = new Date().toISOString(); try { const insertOrder = db .prepare( `INSERT INTO orders (created_at, updated_at, status, kunde_name, kunde_email, land, strasse, plz, ort, zahlungsart, zwischensumme, versandkosten, rabatt_gesamt, summe, treuebonus_verwendet, abo_rabatt_verwendet, gutschein_code, paypal_order_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)` ) .run( now, now, status, name, email, land, String(kunde.strasse || ""), String(kunde.plz || ""), String(kunde.ort || ""), String(body.zahlungsart || ""), zwischensumme, versandkosten, rabattGesamt, summe, body.treuebonusVerwendet ? 1 : 0, body.aboRabattVerwendet ? 1 : 0, gutscheinCodeNormalisiert, paypalOrderId ); const orderId = insertOrder.lastInsertRowid; const jahr = new Date().getFullYear(); const bestellnummer = `VDB-${jahr}-${String(orderId).padStart(4, "0")}`; db.prepare(`UPDATE orders SET bestellnummer = ? WHERE id = ?`).run(bestellnummer, orderId); const insertItem = db.prepare( `INSERT INTO order_items (order_id, produkt_slug, name, kategorie_slug, menge, preis, gratis) VALUES (?,?,?,?,?,?,?)` ); const insertAlleItems = db.transaction((items) => { for (const a of items) { insertItem.run(orderId, a.slug, a.name, a.kategorie || null, Math.round(a.menge), a.preis, a.gratis ? 1 : 0); } }); insertAlleItems(artikel); await sendeEmail({ to: BENACHRICHTIGUNGS_EMAIL, subject: `${status === "zahlungOffen" ? "⏳" : "🩵"} Neue Bestellung ${bestellnummer}`, html: bestellBenachrichtigungHtml({ bestellnummer, name, email, land, artikel, summe, status }), text: `Neue Bestellung ${bestellnummer} von ${name} (${email}), ${artikel.length} Artikel, Summe ${formatPreis(summe)}. Status: ${status === "zahlungOffen" ? "Zahlung ausstehend (Überweisung)" : "Bezahlt (PayPal)"}.`, }); return { ok: true, id: orderId, bestellnummer }; } catch (err) { return { ok: false, httpStatus: 500, error: "Bestellung konnte nicht gespeichert werden." }; } }