Send a dedicated email for a switched Überweisung payment, not the full order-confirmation

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.
This commit is contained in:
Marco
2026-07-30 11:04:27 +00:00
parent 2e537bc78d
commit f0b2d21989
7 changed files with 85 additions and 7 deletions
+19
View File
@@ -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
+2
View File
@@ -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<string, string> = {
@@ -29,6 +30,7 @@ const STATUS_TYPE_FALLBACK_HEADING: Record<string, string> = {
"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
+1
View File
@@ -335,6 +335,7 @@ export const ORDER_STATUS_EMAIL_ICON: Record<string, string> = {
"order-tracking-added": "📦",
"order-tracking-corrected": "📦",
"order-delivered": "🎉",
"payment-method-switched": "💳",
};
export function renderOrderStatusHtml(
+40 -1
View File
@@ -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<boolean> {
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;
}
+2 -1
View File
@@ -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;
+19 -3
View File
@@ -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<void> {
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,
+2 -2
View File
@@ -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"
},