diff --git a/shop/app/api/orders/checkout/route.ts b/shop/app/api/orders/checkout/route.ts new file mode 100644 index 0000000..2b5bcdb --- /dev/null +++ b/shop/app/api/orders/checkout/route.ts @@ -0,0 +1,165 @@ +import { NextResponse } from 'next/server'; +import { createClient } from '@/lib/supabase/server'; +import { getProducts, getCategories } from '@/lib/actions/products'; +import { buildCustomerSnapshot, buildOrderSnapshot } from '@/lib/license-transform'; +import { sendMail } from '@/utils/mail'; +import { createHash } from 'crypto'; +import { renderToBuffer } from '@react-pdf/renderer'; +import React from 'react'; +import { InvoicePDF } from '@/components/invoice-pdf'; + +function generateOrderNumber(prefix: 'BE' | 'AE' = 'AE'): string { + const year = new Date().getFullYear(); + const rand = Math.floor(10000 + Math.random() * 90000); + return `${prefix}-${year}-${rand}`; +} + +function hashOrderSnapshot(snapshot: object): string { + return createHash('sha256').update(JSON.stringify(snapshot)).digest('hex'); +} + +export async function POST(request: Request) { + try { + const supabase = await createClient(); + const { data: { user } } = await supabase.auth.getUser(); + + if (!user) { + return NextResponse.json({ error: 'Not authenticated' }, { status: 401 }); + } + + const { data: dbUser } = await supabase + .from('users') + .select('role, company_id') + .eq('id', user.id) + .single(); + + const isAdminUser = dbUser?.role === 'admin'; + if (!isAdminUser && !dbUser?.company_id) { + return NextResponse.json({ error: 'Firma erforderlich' }, { status: 403 }); + } + + const body = await request.json(); + const { items, customerProfile, endCustomerId, endCustomer } = body; + + if (!items || !Array.isArray(items) || items.length === 0) { + return NextResponse.json({ error: 'Warenkorb leer' }, { status: 400 }); + } + + const products = await getProducts(); + const categories = await getCategories(); + + // Split items into purchase and subscription + const purchaseItems = items.filter( + (item: any) => item.billingInterval === 'one_time' || item.billingType === 'purchase' + ); + const subscriptionItems = items.filter( + (item: any) => item.billingInterval === 'monthly' || item.billingType === 'subscription' + ); + + const createdOrders: any[] = []; + + const processOrderGroup = async (groupItems: any[], type: 'purchase' | 'subscription', paymentMethod: string) => { + if (groupItems.length === 0) return; + + // Build consolidated snapshots + // For the multi-item support, we combine the order snapshots of each item in the group + const customerSnapshot = buildCustomerSnapshot(customerProfile, endCustomer); + + const orderItemsList: any[] = []; + let total = 0; + + for (const item of groupItems) { + const itemSnapshot = buildOrderSnapshot( + item.selections, + products, + categories, + type === 'purchase' ? 'one_time' : 'monthly', + item.moduleQuantities + ); + orderItemsList.push(...itemSnapshot.items); + total += itemSnapshot.total; + } + + const orderSnapshot = { + schema_version: 1, + billing_cycle: type === 'purchase' ? 'one_time' : 'monthly', + items: orderItemsList, + total: total, + tax_rate: 19, // default + tax_amount: Math.round(total * 0.19 * 100) / 100, + subtotal: Math.round((total / 1.19) * 100) / 100 + }; + + const orderHash = hashOrderSnapshot(orderSnapshot); + const orderNumber = generateOrderNumber(type === 'purchase' ? 'AE' : 'BE'); + + const { data: order, error: orderError } = await supabase + .from('orders') + .insert([{ + user_id: user.id, + company_id: dbUser?.company_id || null, + order_number: orderNumber, + order_hash: orderHash, + end_customer_id: endCustomerId || null, + total_price: total, + customer_data: customerSnapshot, + order_data: orderSnapshot, + type: type, + payment_method: paymentMethod, + status: 'pending' + }]) + .select() + .single(); + + if (orderError) throw orderError; + + // Trigger PDF generation & storage upload + try { + const buffer = await renderToBuffer( + React.createElement(InvoicePDF, { + order, + customer: customerSnapshot, + orderSnapshot, + }) + ); + + const fileName = `ab_${order.id}.pdf`; + const { error: uploadError } = await supabase + .storage + .from('invoices') + .upload(fileName, buffer, { contentType: 'application/pdf', upsert: true }); + + if (!uploadError) { + await supabase.from('orders').update({ pdf_url: fileName }).eq('id', order.id); + order.pdf_url = fileName; + } + } catch (pdfErr) { + console.error('PDF error for order ' + order.id, pdfErr); + } + + // Trigger E-Mail notifications + if (user.email) { + try { + await sendMail({ + to: user.email, + subject: `Bestellbestätigung ${order.order_number}`, + text: `Vielen Dank für Ihre Bestellung ${order.order_number}. Typ: ${type}.`, + html: `

Vielen Dank für Ihre Bestellung ${order.order_number}.

Typ: ${type}

` + }); + } catch (mailErr) { + console.error('Mail error for order ' + order.id, mailErr); + } + } + + createdOrders.push(order); + }; + + await processOrderGroup(purchaseItems, 'purchase', 'invoice'); + await processOrderGroup(subscriptionItems, 'subscription', 'sepa'); + + return NextResponse.json({ success: true, orders: createdOrders }); + } catch (error: any) { + console.error('Checkout error:', error); + return NextResponse.json({ error: error.message || 'Internal server error' }, { status: 500 }); + } +}