import express from 'express'; import { stripe } from '../config/stripe.js'; import { Order } from '../models/Order.js'; const router = express.Router(); // Portfolio pricing configuration (price per ton in USD) const PORTFOLIO_PRICING = { 1: 15, // Balanced portfolio 2: 18, // High-impact portfolio 3: 20 // Premium portfolio }; // Processing fee percentage const PROCESSING_FEE_PERCENT = 0.03; // 3% /** * Calculate pricing with processing fee * @param {number} tons - Number of tons * @param {number} pricePerTon - Price per ton from Wren API * @returns {Object} Pricing breakdown */ function calculatePricing(tons, pricePerTon) { const baseAmount = Math.round(tons * pricePerTon * 100); // Convert to cents const processingFee = Math.round(baseAmount * PROCESSING_FEE_PERCENT); const totalAmount = baseAmount + processingFee; return { baseAmount, processingFee, totalAmount, pricePerTon }; } /** * POST /api/checkout/create-session * Create a Stripe Checkout Session */ router.post('/create-session', async (req, res) => { try { const { tons, portfolioId, pricePerTon, customerEmail } = req.body; // Validation if (!tons || tons <= 0) { return res.status(400).json({ error: 'Invalid tons value' }); } // Accept any valid positive integer portfolio ID (from Wren API) if (!portfolioId || !Number.isInteger(portfolioId) || portfolioId <= 0) { console.error('❌ Invalid portfolio ID received:', portfolioId); return res.status(400).json({ error: 'Invalid portfolio ID' }); } // Validate price per ton if (!pricePerTon || pricePerTon <= 0) { console.error('❌ Invalid pricePerTon received:', pricePerTon); return res.status(400).json({ error: 'Invalid price per ton' }); } console.log(`📦 Creating checkout for portfolio ID: ${portfolioId}, tons: ${tons}, price: $${pricePerTon}/ton`); // Calculate pricing using the price from frontend (Wren API) const { baseAmount, processingFee, totalAmount } = calculatePricing(tons, pricePerTon); // Create line items for Stripe const lineItems = [ { price_data: { currency: 'usd', product_data: { name: `Carbon Offset - ${tons.toFixed(2)} tons`, description: `Puffin Portfolio at $${Math.ceil(pricePerTon)}/ton`, // images: ['https://puffinoffset.com/images/carbon-offset.png'], // Optional: Add your logo when available }, unit_amount: baseAmount, // Base amount in cents }, quantity: 1, }, { price_data: { currency: 'usd', product_data: { name: 'Processing Fee (3%)', description: 'Transaction processing fee', }, unit_amount: processingFee, // Processing fee in cents }, quantity: 1, }, ]; // Create Stripe Checkout Session const session = await stripe.checkout.sessions.create({ payment_method_types: ['card', 'us_bank_account', 'link'], line_items: lineItems, mode: 'payment', success_url: `${process.env.FRONTEND_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${process.env.FRONTEND_URL}/checkout/cancel`, customer_email: customerEmail, customer_creation: 'always', // Always create a customer for payment tracking billing_address_collection: 'required', // Collect billing address for business payments phone_number_collection: { enabled: true, // Collect phone number }, tax_id_collection: { enabled: true, // Allow customers to enter business tax ID }, metadata: { tons: tons.toString(), portfolioId: portfolioId.toString(), baseAmount: baseAmount.toString(), processingFee: processingFee.toString(), }, }); // Store order in database const order = Order.create({ stripeSessionId: session.id, customerEmail: customerEmail || null, tons, portfolioId, baseAmount, processingFee, totalAmount, currency: 'USD', }); console.log(`✅ Created checkout session: ${session.id}`); console.log(` Order ID: ${order.id}`); console.log(` Amount: $${(totalAmount / 100).toFixed(2)} (${tons} tons @ $${pricePerTon}/ton + 3% fee)`); res.json({ sessionId: session.id, url: session.url, orderId: order.id, }); } catch (error) { console.error('❌ Checkout session creation error:', error); res.status(500).json({ error: 'Failed to create checkout session', message: error.message, }); } }); /** * GET /api/checkout/session/:sessionId * Retrieve checkout session details */ router.get('/session/:sessionId', async (req, res) => { try { const { sessionId } = req.params; // Get order from database const order = Order.findBySessionId(sessionId); if (!order) { return res.status(404).json({ error: 'Order not found' }); } // Get Stripe session (optional - for additional details) const session = await stripe.checkout.sessions.retrieve(sessionId); res.json({ order: { id: order.id, tons: order.tons, portfolioId: order.portfolio_id, baseAmount: order.base_amount, processingFee: order.processing_fee, totalAmount: order.total_amount, currency: order.currency, status: order.status, wrenOrderId: order.wren_order_id, createdAt: order.created_at, }, session: { paymentStatus: session.payment_status, customerEmail: session.customer_details?.email, }, }); } catch (error) { console.error('❌ Session retrieval error:', error); res.status(500).json({ error: 'Failed to retrieve session', message: error.message, }); } }); export default router;