diff --git a/backend/src/routes/cart.js b/backend/src/routes/cart.js index d79f084..a561c76 100644 --- a/backend/src/routes/cart.js +++ b/backend/src/routes/cart.js @@ -922,123 +922,33 @@ module.exports = (pool, query, authMiddleware) => { } }); - router.post('/checkout', async (req, res, next) => { + // Complete checkout after successful payment + router.post('/complete-checkout', async (req, res, next) => { try { - const { userId, shippingAddress, shippingMethod } = req.body; + const { userId, orderId, sessionId } = req.body; + console.log("Complete Checkout ", `${userId} ${orderId} ${sessionId}`) if (req.user.id !== userId) { return res.status(403).json({ error: true, - message: 'You can only checkout your own cart' + message: 'You can only complete your own checkout' }); } - // Get cart - const cartResult = await query( - 'SELECT * FROM carts WHERE user_id = $1', - [userId] + // Verify the order exists and belongs to the user + const orderResult = await query( + 'SELECT * FROM orders WHERE id = $1 AND user_id = $2', + [orderId, userId] ); - if (cartResult.rows.length === 0) { + if (orderResult.rows.length === 0) { return res.status(404).json({ error: true, - message: 'Cart not found' + message: 'Order not found' }); } - const cartId = cartResult.rows[0].id; - - // Get cart items - const cartItemsResult = await query( - `SELECT ci.*, p.price, p.name, p.description, p.weight_grams, p.length_cm, p.width_cm, p.height_cm, - ( - SELECT json_build_object( - 'path', pi.image_path, - 'isPrimary', pi.is_primary - ) - FROM product_images pi - WHERE pi.product_id = p.id AND pi.is_primary = true - LIMIT 1 - ) AS primary_image - FROM cart_items ci - JOIN products p ON ci.product_id = p.id - WHERE ci.cart_id = $1`, - [cartId] - ); - - if (cartItemsResult.rows.length === 0) { - return res.status(400).json({ - error: true, - message: 'Cart is empty' - }); - } - - // Calculate subtotal - const subtotal = cartItemsResult.rows.reduce((sum, item) => { - return sum + (parseFloat(item.price) * item.quantity); - }, 0); - - // Determine shipping cost and create shipment if needed - let shippingCost = 0; - let shipmentData = null; - - if (config.shipping.enabled) { - // If a specific shipping method was selected - if (shippingMethod && shippingMethod.id) { - shippingCost = parseFloat(shippingMethod.rate) || 0; - - // Check if this is a non-flat rate and we need to purchase a real shipment - if (config.shipping.easypostEnabled && - !shippingMethod.id.includes('flat-rate') && - !shippingMethod.id.includes('free-shipping')) { - - try { - // Parse shipping address to object if it's a string - const parsedAddress = typeof shippingAddress === 'string' - ? shippingService.parseAddressString(shippingAddress) - : shippingAddress; - - // Retrieve temporary shipment ID from cart metadata - const cartMetadataResult = await query( - 'SELECT metadata FROM carts WHERE id = $1', - [cartId] - ); - - let shipmentId = null; - if (cartMetadataResult.rows.length > 0 && - cartMetadataResult.rows[0].metadata && - cartMetadataResult.rows[0].metadata.temp_shipment_id) { - shipmentId = cartMetadataResult.rows[0].metadata.temp_shipment_id; - } - - if (shipmentId) { - // Purchase the shipment with the selected rate - shipmentData = await shippingService.purchaseShipment( - shipmentId, - shippingMethod.id - ); - - console.log('Shipment purchased successfully:', shipmentData.shipment_id); - - // Use the actual rate from the purchased shipment - shippingCost = shipmentData.selected_rate.rate; - } else { - console.log('No shipment ID found in cart metadata, using standard rate'); - } - } catch (error) { - console.error('Error purchasing shipment:', error); - // Continue with the rate provided - } - } - } else { - // Default to flat rate if no method selected - const shippingRates = shippingService.getFlatRateShipping(subtotal); - shippingCost = shippingRates[0].rate; - } - } - - // Calculate total with shipping - const total = subtotal + shippingCost; + const order = orderResult.rows[0]; // Begin transaction const client = await pool.connect(); @@ -1046,59 +956,133 @@ module.exports = (pool, query, authMiddleware) => { try { await client.query('BEGIN'); - // Create order - const orderId = uuidv4(); + // Update order status and payment info await client.query( - 'INSERT INTO orders (id, user_id, status, total_amount, shipping_address, payment_completed, shipping_cost) VALUES ($1, $2, $3, $4, $5, $6, $7)', - [orderId, userId, 'pending', total, shippingAddress, false, shippingCost] + 'UPDATE orders SET status = $1, payment_completed = true, payment_id = $2 WHERE id = $3', + ['processing', sessionId, orderId] ); - // Create order items - for (const item of cartItemsResult.rows) { - await client.query( - 'INSERT INTO order_items (id, order_id, product_id, quantity, price_at_purchase) VALUES ($1, $2, $3, $4, $5)', - [uuidv4(), orderId, item.product_id, item.quantity, item.price] - ); - } + // Get cart + const cartResult = await client.query( + 'SELECT * FROM carts WHERE user_id = $1', + [userId] + ); - // If we have shipping details, save them with the order - if (shippingMethod && shippingMethod.id) { - const shippingInfo = shipmentData || { - method_id: shippingMethod.id, - carrier: shippingMethod.carrier, - service: shippingMethod.service, - rate: shippingMethod.rate, - estimated_days: shippingMethod.delivery_days, - tracking_code: null - }; + if (cartResult.rows.length > 0) { + const cartId = cartResult.rows[0].id; + // Check if cart has a coupon applied + const cartMetadata = cartResult.rows[0].metadata; + if (cartMetadata && cartMetadata.coupon) { + const couponInfo = cartMetadata.coupon; + + // Increment the coupon's current_redemptions + await client.query( + 'UPDATE coupons SET current_redemptions = current_redemptions + 1 WHERE id = $1', + [couponInfo.id] + ); + + // Create a coupon redemption record + await client.query( + 'INSERT INTO coupon_redemptions (coupon_id, order_id, user_id, discount_amount) VALUES ($1, $2, $3, $4)', + [couponInfo.id, orderId, userId, couponInfo.discount_amount] + ); + + // Update the order with coupon information + await client.query( + 'UPDATE orders SET coupon_id = $1, discount_amount = $2 WHERE id = $3', + [couponInfo.id, couponInfo.discount_amount, orderId] + ); + } + + // Get cart items to update product stock + const cartItemsResult = await client.query( + 'SELECT * FROM cart_items WHERE cart_id = $1', + [cartId] + ); + + // Update product stock + for (const item of cartItemsResult.rows) { + await client.query( + 'UPDATE products SET stock_quantity = stock_quantity - $1 WHERE id = $2', + [item.quantity, item.product_id] + ); + + // Process stock notifications after updating stock + try { + const productWithNotification = await client.query( + `SELECT id, name, stock_quantity, stock_notification + FROM products + WHERE id = $1`, + [item.product_id] + ); + + if (productWithNotification.rows.length > 0) { + const product = productWithNotification.rows[0]; + let stockNotification; + + // Handle different ways the JSON could be stored + if (product.stock_notification) { + try { + // If it's a string, parse it + // why are we doing this its been an obj for months now + if (typeof product.stock_notification === 'string') { + stockNotification = JSON.parse(product.stock_notification); + } else { + // Otherwise use as is + stockNotification = product.stock_notification; + } + + console.log("Stock notification for product:", product.id, stockNotification); + + // Check if notification is enabled and stock is below threshold + if (stockNotification && + stockNotification.enabled === true && + stockNotification.email && + stockNotification.threshold && + product.stock_quantity <= parseInt(stockNotification.threshold)) { + + // Log the notification with the order ID + await client.query( + `INSERT INTO notification_logs + (order_id, notification_type, sent_at, status) + VALUES ($1, $2, NOW(), $3)`, + [orderId, 'low_stock_alert', 'pending'] + ); + + console.log(`Low stock notification queued for product ${product.id} - ${product.name}`); + } + } catch (parseError) { + console.error("Error parsing stock notification JSON:", parseError); + } + } + } + } catch (notificationError) { + console.error("Error processing stock notification:", notificationError); + // Continue with checkout even if notification processing fails + } + } + + // Clear cart await client.query( - 'UPDATE orders SET shipping_info = $1 WHERE id = $2', - [JSON.stringify(shippingInfo), orderId] + 'DELETE FROM cart_items WHERE cart_id = $1', + [cartId] + ); + + // Clear cart metadata (including coupon) + await client.query( + 'UPDATE carts SET metadata = $1 WHERE id = $2', + ['{}', cartId] ); } - // Clear the temporary shipment ID from cart metadata - await client.query( - `UPDATE carts SET metadata = metadata - 'temp_shipment_id' WHERE id = $1`, - [cartId] - ); - await client.query('COMMIT'); - // Send back cart items for Stripe checkout - res.status(201).json({ + res.status(200).json({ success: true, - message: 'Order created successfully, ready for payment', - orderId, - cartItems: cartItemsResult.rows, - subtotal, - shippingCost, - total, - shipmentData + message: 'Order completed successfully', + orderId }); - - // Note: We don't clear the cart here now - we'll do that after successful payment } catch (error) { await client.query('ROLLBACK'); throw error;