fixed coupon redemptions

This commit is contained in:
2ManyProjects 2025-05-03 01:03:16 -05:00
parent b209a77be9
commit 6e34a57bd6

View file

@ -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 { 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) { if (req.user.id !== userId) {
return res.status(403).json({ return res.status(403).json({
error: true, error: true,
message: 'You can only checkout your own cart' message: 'You can only complete your own checkout'
}); });
} }
// Get cart // Verify the order exists and belongs to the user
const cartResult = await query( const orderResult = await query(
'SELECT * FROM carts WHERE user_id = $1', 'SELECT * FROM orders WHERE id = $1 AND user_id = $2',
[userId] [orderId, userId]
); );
if (cartResult.rows.length === 0) { if (orderResult.rows.length === 0) {
return res.status(404).json({ return res.status(404).json({
error: true, error: true,
message: 'Cart not found' message: 'Order not found'
}); });
} }
const cartId = cartResult.rows[0].id; const order = orderResult.rows[0];
// 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;
// Begin transaction // Begin transaction
const client = await pool.connect(); const client = await pool.connect();
@ -1046,59 +956,133 @@ module.exports = (pool, query, authMiddleware) => {
try { try {
await client.query('BEGIN'); await client.query('BEGIN');
// Create order // Update order status and payment info
const orderId = uuidv4();
await client.query( 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)', 'UPDATE orders SET status = $1, payment_completed = true, payment_id = $2 WHERE id = $3',
[orderId, userId, 'pending', total, shippingAddress, false, shippingCost] ['processing', sessionId, orderId]
); );
// Create order items // Get cart
for (const item of cartItemsResult.rows) { const cartResult = await client.query(
'SELECT * FROM carts WHERE user_id = $1',
[userId]
);
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( await client.query(
'INSERT INTO order_items (id, order_id, product_id, quantity, price_at_purchase) VALUES ($1, $2, $3, $4, $5)', 'UPDATE coupons SET current_redemptions = current_redemptions + 1 WHERE id = $1',
[uuidv4(), orderId, item.product_id, item.quantity, item.price] [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]
); );
} }
// If we have shipping details, save them with the order // Get cart items to update product stock
if (shippingMethod && shippingMethod.id) { const cartItemsResult = await client.query(
const shippingInfo = shipmentData || { 'SELECT * FROM cart_items WHERE cart_id = $1',
method_id: shippingMethod.id,
carrier: shippingMethod.carrier,
service: shippingMethod.service,
rate: shippingMethod.rate,
estimated_days: shippingMethod.delivery_days,
tracking_code: null
};
await client.query(
'UPDATE orders SET shipping_info = $1 WHERE id = $2',
[JSON.stringify(shippingInfo), orderId]
);
}
// Clear the temporary shipment ID from cart metadata
await client.query(
`UPDATE carts SET metadata = metadata - 'temp_shipment_id' WHERE id = $1`,
[cartId] [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(
'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]
);
}
await client.query('COMMIT'); await client.query('COMMIT');
// Send back cart items for Stripe checkout res.status(200).json({
res.status(201).json({
success: true, success: true,
message: 'Order created successfully, ready for payment', message: 'Order completed successfully',
orderId, orderId
cartItems: cartItemsResult.rows,
subtotal,
shippingCost,
total,
shipmentData
}); });
// Note: We don't clear the cart here now - we'll do that after successful payment
} catch (error) { } catch (error) {
await client.query('ROLLBACK'); await client.query('ROLLBACK');
throw error; throw error;