fix(checkout): use full email template with PDF attachment for checkout API route
Some checks failed
Staging Build / build (push) Failing after 26s
Some checks failed
Staging Build / build (push) Failing after 26s
This commit is contained in:
@@ -7,6 +7,8 @@ import { createHash } from 'crypto';
|
|||||||
import { renderToBuffer } from '@react-pdf/renderer';
|
import { renderToBuffer } from '@react-pdf/renderer';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { InvoicePDF } from '@/components/invoice-pdf';
|
import { InvoicePDF } from '@/components/invoice-pdf';
|
||||||
|
import { buildEmailItemsSection } from '@/lib/actions/orders';
|
||||||
|
import { getOrderEmailTemplate } from '@/lib/actions/email-templates';
|
||||||
|
|
||||||
function generateOrderNumber(prefix: 'BE' | 'AE' = 'AE'): string {
|
function generateOrderNumber(prefix: 'BE' | 'AE' = 'AE'): string {
|
||||||
const year = new Date().getFullYear();
|
const year = new Date().getFullYear();
|
||||||
@@ -144,11 +146,128 @@ export async function POST(request: Request) {
|
|||||||
// Trigger E-Mail notifications
|
// Trigger E-Mail notifications
|
||||||
if (user.email) {
|
if (user.email) {
|
||||||
try {
|
try {
|
||||||
|
const formattedDate = new Date(order.created_at).toLocaleDateString('de-DE');
|
||||||
|
const taxRate = orderSnapshot.tax_rate ?? 19;
|
||||||
|
const items = orderSnapshot.items ?? [];
|
||||||
|
const oneTimeItems = items.filter((item: any) => item.billing_interval === 'one_time');
|
||||||
|
const monthlyItems = items.filter((item: any) => item.billing_interval === 'monthly');
|
||||||
|
|
||||||
|
const oneTimeNet = oneTimeItems.reduce((sum: number, item: any) => sum + (item.item_total || 0), 0);
|
||||||
|
const oneTimeTax = Math.round(oneTimeNet * (taxRate / 100) * 100) / 100;
|
||||||
|
const oneTimeGross = Math.round((oneTimeNet + oneTimeTax) * 100) / 100;
|
||||||
|
|
||||||
|
const monthlyNet = monthlyItems.reduce((sum: number, item: any) => sum + (item.item_total || 0), 0);
|
||||||
|
const monthlyTax = Math.round(monthlyNet * (taxRate / 100) * 100) / 100;
|
||||||
|
const monthlyGross = Math.round((monthlyNet + monthlyTax) * 100) / 100;
|
||||||
|
|
||||||
|
let totalDetailsText = '';
|
||||||
|
if (oneTimeNet > 0 && monthlyNet > 0) {
|
||||||
|
totalDetailsText = `\nEinmaliger Betrag:\n- Netto: ${oneTimeNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}\n- zzgl. ${taxRate}% MwSt: ${oneTimeTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}\n- Brutto Gesamtbetrag: ${oneTimeGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}\n\nMonatlicher Betrag:\n- Netto: ${monthlyNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat\n- zzgl. ${taxRate}% MwSt: ${monthlyTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat\n- Brutto Gesamtbetrag: ${monthlyGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat`;
|
||||||
|
} else if (oneTimeNet > 0) {
|
||||||
|
totalDetailsText = `\nGesamtsumme:\n- Netto: ${oneTimeNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}\n- zzgl. ${taxRate}% MwSt: ${oneTimeTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}\n- Brutto Gesamtbetrag: ${oneTimeGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}`;
|
||||||
|
} else {
|
||||||
|
totalDetailsText = `\nGesamtsumme:\n- Netto: ${monthlyNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat\n- zzgl. ${taxRate}% MwSt: ${monthlyTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat\n- Brutto Gesamtbetrag: ${monthlyGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalDetailsHtml = '';
|
||||||
|
if (oneTimeNet > 0 && monthlyNet > 0) {
|
||||||
|
totalDetailsHtml = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" style="padding: 8px 0; border-top: 1px solid #e2e8f0; font-weight: bold; color: #0f172a;">Einmaliger Betrag:</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; padding-left: 10px; color: #475569;">Netto:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${oneTimeNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; padding-left: 10px; color: #475569;">zzgl. ${taxRate}% MwSt:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${oneTimeTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; padding-left: 10px; font-weight: bold; color: #0f172a;">Brutto Gesamtbetrag:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; font-weight: bold; color: #0f172a;">${oneTimeGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" style="padding: 8px 0; border-top: 1px solid #e2e8f0; font-weight: bold; color: #0f172a;">Monatlicher Betrag:</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; padding-left: 10px; color: #475569;">Netto:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${monthlyNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; padding-left: 10px; color: #475569;">zzgl. ${taxRate}% MwSt:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${monthlyTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; padding-left: 10px; font-weight: bold; color: #0f172a;">Brutto Gesamtbetrag:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; font-weight: bold; color: #0f172a;">${monthlyGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
} else if (oneTimeNet > 0) {
|
||||||
|
totalDetailsHtml = `
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; color: #475569;">Netto-Gesamtbetrag:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${oneTimeNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; color: #475569;">zzgl. ${taxRate}% MwSt:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${oneTimeTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; font-weight: bold; color: #0f172a; border-top: 1px solid #e2e8f0; padding-top: 6px;">Gesamtbetrag (brutto):</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; font-weight: bold; color: #0f172a; border-top: 1px solid #e2e8f0; padding-top: 6px;">${oneTimeGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
totalDetailsHtml = `
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; color: #475569;">Netto-Gesamtbetrag:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${monthlyNet.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; color: #475569;">zzgl. ${taxRate}% MwSt:</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; color: #475569;">${monthlyTax.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 4px 0; font-weight: bold; color: #0f172a; border-top: 1px solid #e2e8f0; padding-top: 6px;">Gesamtbetrag (brutto):</td>
|
||||||
|
<td style="padding: 4px 0; text-align: right; font-weight: bold; color: #0f172a; border-top: 1px solid #e2e8f0; padding-top: 6px;">${monthlyGross.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} / Monat</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemsSection = buildEmailItemsSection(items);
|
||||||
|
|
||||||
|
const emailTemplate = getOrderEmailTemplate({
|
||||||
|
orderNumber: order.order_number,
|
||||||
|
formattedDate,
|
||||||
|
customerCompanyName: customerSnapshot.company_name,
|
||||||
|
totalDetailsText,
|
||||||
|
totalDetailsHtml,
|
||||||
|
itemsDetailsText: itemsSection.text,
|
||||||
|
itemsDetailsHtml: itemsSection.html
|
||||||
|
}, `${process.env.NEXT_PUBLIC_SITE_URL || 'https://staging.hephex.de'}`, false);
|
||||||
|
|
||||||
|
const { data: bufferData, error: downloadError } = await supabase
|
||||||
|
.storage
|
||||||
|
.from('invoices')
|
||||||
|
.download(fileName);
|
||||||
|
|
||||||
|
const attachments: any[] = [];
|
||||||
|
if (!downloadError && bufferData) {
|
||||||
|
const buffer = Buffer.from(await bufferData.arrayBuffer());
|
||||||
|
attachments.push({
|
||||||
|
filename: `Anfragebestaetigung_${order.order_number}.pdf`,
|
||||||
|
content: buffer,
|
||||||
|
contentType: 'application/pdf',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await sendMail({
|
await sendMail({
|
||||||
to: user.email,
|
to: user.email,
|
||||||
subject: `Bestätigung Ihrer CASPOS-Anfrage ${order.order_number}`,
|
subject: `Anfragebestätigung ${order.order_number}`,
|
||||||
text: `Vielen Dank für Ihre Anfrage ${order.order_number}.`,
|
text: emailTemplate.text,
|
||||||
html: `<p>Vielen Dank für Ihre Anfrage <strong>${order.order_number}</strong>.</p>`
|
html: emailTemplate.html,
|
||||||
|
attachments
|
||||||
});
|
});
|
||||||
} catch (mailErr) {
|
} catch (mailErr) {
|
||||||
console.error('Mail error for order ' + order.id, mailErr);
|
console.error('Mail error for order ' + order.id, mailErr);
|
||||||
|
|||||||
Reference in New Issue
Block a user