v2.2: Verkaufsfertig-Fundament — Mollie/Payment-Abstraktion, MwSt/Grundpreis (PAngV), Versandzonen, Bestellmails (Listmonk/SMTP/Log), Feature-Flags

- payments.js: einheitliche createPayment/Webhook-Schnittstelle (Mollie Default, Stripe, Demo); Auto-Provider-Wahl; Mollie-REST + /api/payments/webhook (idempotent); Fake-Key => sauberer Demo-Fallback
- mailer.js: sendMail via Listmonk-Tx / SMTP (nodemailer) / Log-Fallback (email_log); gebrandete Bestellbestaetigung bei paid
- DACH: products.mwst + base_amount/base_unit/base_price_per (Grundpreis); Storefront/Warenkorb/Checkout/Erfolg/Admin mit MwSt-Ausweis + Versand-Transparenz; tax_cents/shipping_cents/country an Orders
- shipping_zones-Tabelle + CRUD + shippingFor(); Admin 'Versand'; serverseitige Versandberechnung in /api/checkout + /api/shipping-quote (Laenderwahl live)
- Feature-Flags (feature_*) + feature()-Helper; Admin Module-Toggles; Newsletter-Gating (Popup/Subscribe)
- Admin-API/Manifest/ai-admin.txt um shipping_zones erweitert; MCP list/upsert/delete_shipping; README/.env.example ergaenzt; Version 2.2.0
This commit is contained in:
2026-06-17 16:37:10 +00:00
parent 430fa718fa
commit e5514dd5da
31 changed files with 1077 additions and 129 deletions
+42
View File
@@ -0,0 +1,42 @@
import { getOrderByPaymentId, markOrderPaid } from '../../../lib/store.js';
import { molliePaymentStatus } from '../../../lib/payments.js';
import { sendOrderConfirmation } from '../../../lib/mailer.js';
export const prerender = false;
// Mollie sendet POST mit `id` (Payment-ID). Status via API prüfen → bei paid Order setzen + Mail.
export async function POST({ request }) {
let paymentId = '';
try {
const ct = request.headers.get('content-type') || '';
if (ct.includes('application/json')) {
const b = await request.json().catch(() => ({}));
paymentId = b.id || '';
} else {
const form = await request.formData().catch(() => null);
if (form) paymentId = String(form.get('id') || '');
}
} catch {}
if (!paymentId) return new Response('OK', { status: 200 });
try {
const st = await molliePaymentStatus(paymentId);
if (st.status === 'paid') {
let order = getOrderByPaymentId(paymentId);
if (!order && st.metadata && st.metadata.order_id) {
const { getOrderById } = await import('../../../lib/store.js');
order = getOrderById(st.metadata.order_id);
}
if (order) {
const res = markOrderPaid(order.id, { payment_id: paymentId, payment_provider: 'mollie' });
if (res.changed) { try { await sendOrderConfirmation(res.order); } catch {} }
}
}
} catch (e) {
// Webhook nie mit 500 quittieren (Mollie würde sonst dauerhaft retrien); 200 mit Log.
console.error('[payments:webhook]', e && e.message || e);
}
return new Response('OK', { status: 200 });
}
// GET zur einfachen Erreichbarkeitsprüfung.
export async function GET() { return new Response('payments webhook ready', { status: 200 }); }