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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Generated
+2
-2
@@ -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"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user