From f0b2d21989048d2004f4d7e21dc9480afe3fdc2f Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 30 Jul 2026 11:04:27 +0000 Subject: [PATCH] =?UTF-8?q?Send=20a=20dedicated=20email=20for=20a=20switch?= =?UTF-8?q?ed=20=C3=9Cberweisung=20payment,=20not=20the=20full=20order-con?= =?UTF-8?q?firmation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New sendPaymentSwitchedEmail() — sent instead of sendOrderConfirmationEmail() when confirmPaymentEmail.ts sees order.paymentSwitchedAt set. Resending the full order-confirmation (with its invoice re-attached) after a payment-method switch read like a brand-new purchase; this is a short, dedicated "Zahlung erhalten" confirmation instead, matching the order-status-email shape. Also bumps @einfach-produktiv/invoicing to 0.2.8, fixing a font-path bug that broke every invoice PDF render inside the Payload backend specifically. --- README.md | 19 ++++++++++++ app/email-preview/[type]/page.tsx | 2 ++ app/lib/emailTemplates.ts | 1 + app/lib/orderEmail.ts | 41 ++++++++++++++++++++++++- app/lib/payload.ts | 3 +- app/lib/payments/confirmPaymentEmail.ts | 22 +++++++++++-- package-lock.json | 4 +-- 7 files changed, 85 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 59ddb4e..3ceffe6 100644 --- a/README.md +++ b/README.md @@ -2097,6 +2097,25 @@ realistic today, but not meaningful states to switch *from* either). straight from `company-settings` fields rather than through the CMS richText renderer (which already links emails/URLs correctly). +**New `sendPaymentSwitchedEmail()`** (`lib/orderEmail.ts`) — sent instead +of `sendOrderConfirmationEmail()` when `confirmPaymentEmail.ts` sees +`order.paymentSwitchedAt` set (a payment confirmation that came from +switching an existing Überweisung order to Stripe, not a fresh gated +checkout — see the Payload backend's own README on +`switchPaymentToStripeEndpoint`). Same shape as the order-status emails +(icon + admin-editable heading/bodyText + "Bestellung ansehen" button via +`renderOrderStatusHtml`), no item table or invoice re-attachment — the +invoice itself didn't change, only how it got paid, and the customer +already received the full order-confirmation email once already. New +`payment-method-switched` `EmailTemplateType`, added to +`/email-preview`'s valid types too. + +**Bumped `@einfach-produktiv/invoicing` to 0.2.8** — fixes a font-path +bug that broke every invoice/correction-invoice PDF render *inside the +Payload backend* specifically (this frontend's own renders were +unaffected). See that package's own README for the Turbopack +asset-hashing root cause. + ## Deployment - **Dockerfile**: 3-stage build (`deps` → `builder` → `runner`) with diff --git a/app/email-preview/[type]/page.tsx b/app/email-preview/[type]/page.tsx index 90e6fc1..568db10 100644 --- a/app/email-preview/[type]/page.tsx +++ b/app/email-preview/[type]/page.tsx @@ -19,6 +19,7 @@ const VALID_TYPES: EmailTemplateType[] = [ "order-tracking-added", "order-tracking-corrected", "order-delivered", + "payment-method-switched", ]; const STATUS_TYPE_FALLBACK_HEADING: Record = { @@ -29,6 +30,7 @@ const STATUS_TYPE_FALLBACK_HEADING: Record = { "order-tracking-added": "Hier ist deine Sendungsnummer", "order-tracking-corrected": "Korrigierte Sendungsnummer", "order-delivered": "Dein Paket ist angekommen", + "payment-method-switched": "Zahlung erhalten", }; // Entered exclusively via EmailTemplates.ts's admin.livePreview.url (a diff --git a/app/lib/emailTemplates.ts b/app/lib/emailTemplates.ts index c353ab3..165ea93 100644 --- a/app/lib/emailTemplates.ts +++ b/app/lib/emailTemplates.ts @@ -335,6 +335,7 @@ export const ORDER_STATUS_EMAIL_ICON: Record = { "order-tracking-added": "📦", "order-tracking-corrected": "📦", "order-delivered": "🎉", + "payment-method-switched": "💳", }; export function renderOrderStatusHtml( diff --git a/app/lib/orderEmail.ts b/app/lib/orderEmail.ts index 2be5f4b..ab3b66e 100644 --- a/app/lib/orderEmail.ts +++ b/app/lib/orderEmail.ts @@ -1,6 +1,6 @@ import { transport } from "./mailer"; import { getEmailTemplate } from "./payload"; -import { renderOrderConfirmationHtml, type OrderConfirmationData } from "./emailTemplates"; +import { renderOrderConfirmationHtml, renderOrderStatusHtml, type OrderConfirmationData } from "./emailTemplates"; import { generateInvoicePdf, getSellerForInvoice } from "./invoiceData"; import { sendCriticalAlert } from "./alertAdmin"; @@ -154,3 +154,42 @@ export async function sendOrderConfirmationEmail(order: OrderConfirmationEmailDa }); return true; } + +// Sent instead of sendOrderConfirmationEmail() when the confirmed payment +// came from switchPaymentToStripe.ts (an existing Überweisung order the +// customer moved to Kreditkarte/PayPal), not a fresh checkout — see +// confirmPaymentEmail.ts's own branch on order.paymentSwitchedAt. A second +// full "Vielen Dank für deine Bestellung!" email (with its own invoice PDF +// re-attached) would read as a brand-new purchase; this is a short, +// dedicated confirmation instead, same shape as the order-status emails +// (icon + admin-editable text + "Bestellung ansehen" button), no item +// table/invoice attachment — the invoice itself didn't change, only how +// it got paid. +export async function sendPaymentSwitchedEmail(orderNumber: string, customerEmail: string): Promise { + const fetchedTemplate = await getEmailTemplate("payment-method-switched"); + if (fetchedTemplate && !fetchedTemplate.active) return false; + const template = fetchedTemplate ?? { + subject: "Zahlung erhalten — danke!", + heading: "Zahlung erhalten", + bodyText: "Deine Zahlung per Kreditkarte/PayPal ist bei uns eingegangen. An deiner Bestellung selbst ändert sich nichts — sie wird wie gewohnt bearbeitet.", + footerText: null, + }; + + const seller = await getSellerForInvoice(); + const html = renderOrderStatusHtml( + template, + "💳", + orderNumber, + `https://einfach-produktiv.mk360.de/konto/bestellungen/${encodeURIComponent(orderNumber)}`, + seller, + ); + + await transport.sendMail({ + from: `"${seller?.emailFromName || seller?.sellerName || "Björn"}" <${seller?.emailFromAddress || seller?.sellerEmail || "hallo@einfach-produktiv.com"}>`, + replyTo: seller?.sellerEmail || undefined, + to: customerEmail, + subject: template.subject, + html, + }); + return true; +} diff --git a/app/lib/payload.ts b/app/lib/payload.ts index bca81af..cd9f757 100644 --- a/app/lib/payload.ts +++ b/app/lib/payload.ts @@ -838,7 +838,8 @@ export type EmailTemplateType = | "order-returned" | "order-tracking-added" | "order-tracking-corrected" - | "order-delivered"; + | "order-delivered" + | "payment-method-switched"; type PayloadEmailTemplate = { type: EmailTemplateType; diff --git a/app/lib/payments/confirmPaymentEmail.ts b/app/lib/payments/confirmPaymentEmail.ts index 4f99418..125f476 100644 --- a/app/lib/payments/confirmPaymentEmail.ts +++ b/app/lib/payments/confirmPaymentEmail.ts @@ -1,4 +1,4 @@ -import { sendOrderConfirmationEmail, type OrderConfirmationEmailData } from "../orderEmail"; +import { sendOrderConfirmationEmail, sendPaymentSwitchedEmail, type OrderConfirmationEmailData } from "../orderEmail"; import { sendCriticalAlert } from "../alertAdmin"; // The `order` snapshot returned by the backend's confirm-payment endpoint @@ -9,7 +9,13 @@ import { sendCriticalAlert } from "../alertAdmin"; // templates), so it returns everything needed here instead of the // frontend needing an authenticated order-read path it doesn't otherwise // have (ORDER_SERVICE_SECRET only ever authorizes *creating* an order). -export type ConfirmPaymentOrderSnapshot = OrderConfirmationEmailData & { customerEmail: string }; +export type ConfirmPaymentOrderSnapshot = OrderConfirmationEmailData & { + customerEmail: string; + // Present only when this payment came from switchPaymentToStripe.ts (an + // existing Überweisung order moved to Stripe) — see this function's own + // branch below. + paymentSwitchedAt?: string; +}; // Called from both the real Stripe webhook route and its PAYMENT_TEST_MODE // test-confirm sibling, right after confirm-payment reports success (and @@ -17,10 +23,20 @@ export type ConfirmPaymentOrderSnapshot = OrderConfirmationEmailData & { custome // this). Mirrors exactly what app/api/checkout/route.ts already does for // a manual/Überweisung order today, just triggered from the payment // webhook instead of the checkout request itself for gated methods. +// +// A payment-method switch (paymentSwitchedAt set) sends a short dedicated +// "Zahlung erhalten" confirmation instead — the customer already got the +// full order-confirmation email (with its invoice) when they originally +// placed the Überweisung order; resending that same email here would read +// as a second, brand-new purchase. export async function sendConfirmedPaymentEmail(order: ConfirmPaymentOrderSnapshot): Promise { const { customerEmail, ...emailData } = order; try { - await sendOrderConfirmationEmail(emailData, customerEmail); + if (order.paymentSwitchedAt) { + await sendPaymentSwitchedEmail(order.orderNumber, customerEmail); + } else { + await sendOrderConfirmationEmail(emailData, customerEmail); + } } catch (err) { sendCriticalAlert("Bestätigungs-Mail konnte nach Zahlungsbestätigung nicht gesendet werden", { orderNumber: order.orderNumber, diff --git a/package-lock.json b/package-lock.json index 17e31cc..b3def8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -455,8 +455,8 @@ } }, "node_modules/@einfach-produktiv/invoicing": { - "version": "0.2.7", - "resolved": "git+https://git.mk360.de/Marco/einfach-produktiv-invoicing.git#e66007cc4aacccf3b1f18892f3ae9a5e6adf685e", + "version": "0.2.8", + "resolved": "git+https://git.mk360.de/Marco/einfach-produktiv-invoicing.git#ed96d36b54ba529a9ce4bf4a8ee94fd4c2a50d90", "dependencies": { "@e-invoice-eu/core": "^3.1.1" },