Send the customer confirmation email from the payment webhook
The webhook route previously assumed the backend's confirm-payment endpoint sent the customer confirmation email; the backend assumed the opposite. Net effect: a successful Stripe payment never triggered any confirmation email. Consume the order snapshot confirm-payment now returns and send it from here, matching what the checkout route already does for a manual/Überweisung order. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import Stripe from "stripe";
|
||||
import { verifyStripeWebhookSignature } from "../../../lib/payments/stripeProvider";
|
||||
import { sendConfirmedPaymentEmail, type ConfirmPaymentOrderSnapshot } from "../../../lib/payments/confirmPaymentEmail";
|
||||
import { sendCriticalAlert } from "../../../lib/alertAdmin";
|
||||
|
||||
const PAYLOAD_URL = process.env.PAYLOAD_URL || "https://payload.mk360.de";
|
||||
@@ -67,5 +68,17 @@ export async function POST(request: Request) {
|
||||
return NextResponse.json({ ok: false }, { status: 502 });
|
||||
}
|
||||
|
||||
const data: { ok: boolean; alreadyProcessed?: boolean; order?: ConfirmPaymentOrderSnapshot } = await res.json();
|
||||
|
||||
// Fire-and-forget, same reasoning as the checkout route's own send: a
|
||||
// failed confirmation email must never turn an already-successful
|
||||
// payment confirmation into a non-2xx response (that would make Stripe
|
||||
// retry a webhook we've already fully processed). `alreadyProcessed`/
|
||||
// missing `order` means this is a repeat delivery — see confirmPayment.ts's
|
||||
// own comment on why the email must not be sent twice.
|
||||
if (data.order && !data.alreadyProcessed) {
|
||||
void sendConfirmedPaymentEmail(data.order);
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { isPaymentTestMode } from "../../../../lib/payments";
|
||||
import { sendConfirmedPaymentEmail, type ConfirmPaymentOrderSnapshot } from "../../../../lib/payments/confirmPaymentEmail";
|
||||
import { sendCriticalAlert } from "../../../../lib/alertAdmin";
|
||||
|
||||
const PAYLOAD_URL = process.env.PAYLOAD_URL || "https://payload.mk360.de";
|
||||
@@ -40,5 +41,15 @@ export async function POST(request: Request) {
|
||||
return NextResponse.json({ ok: false, reason: "Backend hat die Testzahlung nicht bestätigt." }, { status: 502 });
|
||||
}
|
||||
|
||||
const data: { ok: boolean; alreadyProcessed?: boolean; order?: ConfirmPaymentOrderSnapshot } = await res.json();
|
||||
|
||||
// Same email-send as the real webhook route — see its own comment and
|
||||
// confirmPaymentEmail.ts. Reproduces today's "immediate confirmation"
|
||||
// behavior on a test click, exercising the real send path rather than a
|
||||
// separate short-circuit.
|
||||
if (data.order && !data.alreadyProcessed) {
|
||||
void sendConfirmedPaymentEmail(data.order);
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { sendOrderConfirmationEmail, type OrderConfirmationEmailData } from "../orderEmail";
|
||||
import { sendCriticalAlert } from "../alertAdmin";
|
||||
|
||||
// The `order` snapshot returned by the backend's confirm-payment endpoint
|
||||
// (see docker/payload's src/lib/endpoints/confirmPayment.ts) — matches
|
||||
// OrderConfirmationEmailData minus `customerEmail`, which is passed
|
||||
// separately to sendOrderConfirmationEmail. Backend has no SMTP-based
|
||||
// order-confirmation sender of its own (only the 4 status-change
|
||||
// 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 };
|
||||
|
||||
// Called from both the real Stripe webhook route and its PAYMENT_TEST_MODE
|
||||
// test-confirm sibling, right after confirm-payment reports success (and
|
||||
// NOT `alreadyProcessed: true` — a repeat delivery must never resend
|
||||
// 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.
|
||||
export async function sendConfirmedPaymentEmail(order: ConfirmPaymentOrderSnapshot): Promise<void> {
|
||||
const { customerEmail, ...emailData } = order;
|
||||
try {
|
||||
await sendOrderConfirmationEmail(emailData, customerEmail);
|
||||
} catch (err) {
|
||||
sendCriticalAlert("Bestätigungs-Mail konnte nach Zahlungsbestätigung nicht gesendet werden", {
|
||||
orderNumber: order.orderNumber,
|
||||
customerEmail,
|
||||
error: String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user