Generate invoice PDFs attached to order confirmation, and send emails on order status changes

Invoice PDFs (§14 UStG line items, tenant-configurable VAT rate) are now
generated at checkout and attached to the confirmation email, plus
available on demand from the order-detail page. Payload-side, orders now
also email the customer on shipped/cancelled/return_requested/returned,
with Stornorechnung/Gutschrift correction PDFs attached for the latter two
so the original invoice's immutable number stays honest.
This commit is contained in:
Marco
2026-07-22 09:49:56 +00:00
parent fa02d95dff
commit 5232b14cdf
15 changed files with 1077 additions and 24 deletions
@@ -0,0 +1,50 @@
import { NextResponse } from "next/server";
import { getSessionCustomer, getCustomerOrderDetail } from "../../../../../lib/customerAuth";
import { generateInvoicePdf } from "../../../../../lib/invoiceData";
// On-demand download for "Rechnung herunterladen" on
// /konto/bestellungen/[orderNumber] — reuses the exact same render call as
// the checkout-time attachment (app/lib/orderEmail.ts), so a re-download
// always matches what was emailed; invoiceNumber itself never changes
// (assigned once, server-side, at order creation — see Orders.ts).
export async function GET(request: Request, { params }: { params: Promise<{ orderNumber: string }> }) {
const session = await getSessionCustomer();
if (!session) return NextResponse.json({ ok: false, reason: "Bitte zuerst einloggen." }, { status: 401 });
const { orderNumber } = await params;
const order = await getCustomerOrderDetail(session.token, session.customer.id, decodeURIComponent(orderNumber));
if (!order) return NextResponse.json({ ok: false, reason: "Bestellung nicht gefunden." }, { status: 404 });
if (!order.invoiceNumber || !order.invoiceIssuedAt) {
return NextResponse.json({ ok: false, reason: "Für diese Bestellung liegt noch keine Rechnung vor." }, { status: 404 });
}
const pdf = await generateInvoicePdf({
orderNumber: order.orderNumber,
invoiceNumber: order.invoiceNumber,
invoiceIssuedAt: order.invoiceIssuedAt,
customerFirstName: order.customerFirstName,
customerLastName: order.customerLastName,
deliveryMethod: order.deliveryMethod,
street: order.street,
packstationNumber: order.packstationNumber,
postNumber: order.postNumber,
zip: order.zip,
city: order.city,
country: order.country,
items: order.items,
subtotal: order.subtotal,
shippingCost: order.shippingCost,
discountAmount: order.discountAmount,
discountCode: order.discountCode,
total: order.total,
});
if (!pdf) return NextResponse.json({ ok: false, reason: "Rechnung konnte nicht erzeugt werden." }, { status: 500 });
return new NextResponse(new Uint8Array(pdf), {
status: 200,
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": `attachment; filename="Rechnung-${order.invoiceNumber}.pdf"`,
},
});
}