Files
webklar.com-v2-beta/webklar/server/index.js
2026-01-07 18:08:21 +01:00

147 lines
4.5 KiB
JavaScript

import express from 'express';
import cors from 'cors';
import Stripe from 'stripe';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
const port = process.env.PORT || 3001;
// Initialize Stripe
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
// Middleware
app.use(cors({
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
}));
app.use(express.json());
// Health check
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Create Stripe Checkout Session
app.post('/api/checkout', async (req, res) => {
try {
const { orderData } = req.body;
// Validate required fields
if (!orderData?.contact?.email || !orderData?.contact?.name) {
return res.status(400).json({ error: 'Name and email are required' });
}
// Create Stripe Checkout Session
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
mode: 'payment',
customer_email: orderData.contact.email,
line_items: [
{
price_data: {
currency: 'eur',
product_data: {
name: 'Website in 48h',
description: `${orderData.websiteType} | ${orderData.style} | ${orderData.theme === 'dark' ? 'Dunkel' : 'Hell'}`,
images: ['https://webklar.de/og-image.png'], // Optional
},
unit_amount: 19900, // 199€ in cents
},
quantity: 1,
},
],
metadata: {
customerName: orderData.contact.name,
customerCompany: orderData.contact.company || '',
customerPhone: orderData.contact.phone || '',
websiteType: orderData.websiteType,
style: orderData.style,
theme: orderData.theme,
colorPrimary: orderData.colors?.primary || '#0A400C',
colorSecondary: orderData.colors?.secondary || '#819067',
colorAccent: orderData.colors?.accent || '#B1AB86',
customReferenceUrl: orderData.customInput?.referenceUrl || '',
customDescription: orderData.customInput?.description || '',
},
success_url: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/konfigurator`,
});
res.json({
sessionId: session.id,
url: session.url
});
} catch (error) {
console.error('Stripe error:', error);
res.status(500).json({
error: 'Failed to create checkout session',
details: error.message
});
}
});
// Verify session (for success page)
app.get('/api/session/:sessionId', async (req, res) => {
try {
const session = await stripe.checkout.sessions.retrieve(req.params.sessionId);
res.json({
id: session.id,
status: session.payment_status,
customerEmail: session.customer_email,
amountTotal: session.amount_total,
metadata: session.metadata,
});
} catch (error) {
console.error('Session verification error:', error);
res.status(404).json({ error: 'Session not found' });
}
});
// Stripe Webhook (for production)
app.post('/api/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
const sig = req.headers['stripe-signature'];
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
if (!webhookSecret) {
console.warn('Webhook secret not configured');
return res.status(400).send('Webhook secret not configured');
}
try {
const event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret);
switch (event.type) {
case 'checkout.session.completed':
const session = event.data.object;
console.log('✅ Payment successful:', session.id);
// TODO: Save order to database, send confirmation email, etc.
break;
case 'payment_intent.payment_failed':
console.log('❌ Payment failed:', event.data.object.id);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.json({ received: true });
} catch (error) {
console.error('Webhook error:', error.message);
res.status(400).send(`Webhook Error: ${error.message}`);
}
});
app.listen(port, () => {
console.log(`\n🚀 Server running on http://localhost:${port}`);
console.log(`\n📋 Endpoints:`);
console.log(` GET /api/health`);
console.log(` POST /api/checkout`);
console.log(` GET /api/session/:sessionId`);
console.log(` POST /api/webhook\n`);
});