From 38df7efb8464f9ed4322ba7d2a05ee3a7cf251c0 Mon Sep 17 00:00:00 2001 From: Dev Prashant Date: Sat, 27 Jun 2026 21:51:27 +0530 Subject: [PATCH] fix: product variants cannot be purchased (checkout and webhook) --- BACKEND/controllers/checkout.controller.js | 114 ++++++++++++++++----- scratch_checkout.txt | Bin 0 -> 21030 bytes scratch_lines.txt | Bin 0 -> 23504 bytes 3 files changed, 91 insertions(+), 23 deletions(-) create mode 100644 scratch_checkout.txt create mode 100644 scratch_lines.txt diff --git a/BACKEND/controllers/checkout.controller.js b/BACKEND/controllers/checkout.controller.js index ab7104c..a22731c 100644 --- a/BACKEND/controllers/checkout.controller.js +++ b/BACKEND/controllers/checkout.controller.js @@ -32,8 +32,15 @@ function productImage(product) { } async function restoreStock(deductions) { - for (const { productId, quantity } of deductions) { - await Product.findByIdAndUpdate(productId, { $inc: { baseStock: quantity } }); + for (const { productId, variantId, quantity } of deductions) { + if (variantId) { + await Product.findOneAndUpdate( + { _id: productId, "variants._id": variantId }, + { $inc: { "variants.$.stock": quantity } } + ); + } else { + await Product.findByIdAndUpdate(productId, { $inc: { baseStock: quantity } }); + } } } @@ -68,10 +75,31 @@ export const createCheckoutSession = async (req, res) => { return res.status(404).json({ success: false, message: `Product not found: ${item.name}` }); } - if (item.quantity > product.baseStock) { + let price = product.basePrice; + let stock = product.baseStock; + let name = product.name; + let img = productImage(product); + + if (product.hasVariants) { + if (!item.variantId) { + return res.status(400).json({ success: false, message: `Variant ID required for ${product.name}` }); + } + const variant = product.variants.id(item.variantId); + if (!variant) { + return res.status(404).json({ success: false, message: `Variant not found for ${product.name}` }); + } + price = variant.price; + stock = variant.stock; + name = `${product.name} - ${variant.size} ${variant.color}`; + if (variant.images && variant.images.length > 0) { + img = [variant.images[0]]; + } + } + + if (item.quantity > stock) { return res.status(400).json({ success: false, - message: `Insufficient stock for ${product.name}. Available: ${product.baseStock}, requested: ${item.quantity}` + message: `Insufficient stock for ${name}. Available: ${stock}, requested: ${item.quantity}` }); } @@ -79,10 +107,10 @@ export const createCheckoutSession = async (req, res) => { price_data: { currency: 'usd', product_data: { - name: product.name, - images: productImage(product), + name: name, + images: img, }, - unit_amount: Math.round(product.basePrice * 100), + unit_amount: Math.round(price * 100), }, quantity: item.quantity, }); @@ -127,7 +155,7 @@ export const createCheckoutSession = async (req, res) => { cancel_url: `${FRONTEND_URL}/cancel`, metadata: { items: JSON.stringify( - items.map((item) => ({ _id: item._id, quantity: item.quantity })) + items.map((item) => ({ _id: item._id, variantId: item.variantId || null, quantity: item.quantity })) ), userId: req.user?._id?.toString() || '', couponCode: couponDoc ? couponDoc.code : '', @@ -184,30 +212,61 @@ export const stripeWebhook = async (req, res) => { console.error(`Checkout webhook: product not found or deleted: ${item._id}`); return res.json({ received: true }); } - if (item.quantity > product.baseStock) { - console.error(`Checkout webhook: insufficient stock for ${product.name}`); + + let price = product.basePrice; + let stock = product.baseStock; + let name = product.name; + let img = productImage(product)[0] || ""; + + if (product.hasVariants && item.variantId) { + const variant = product.variants.id(item.variantId); + if (variant) { + price = variant.price; + stock = variant.stock; + name = `${product.name} - ${variant.size} ${variant.color}`; + if (variant.images && variant.images.length > 0) img = variant.images[0]; + } + } + + if (item.quantity > stock) { + console.error(`Checkout webhook: insufficient stock for ${name}`); return res.json({ received: true }); } orderItems.push({ product: product._id, - name: product.name, - price: product.basePrice, + name: name, + price: price, quantity: item.quantity, - image: productImage(product)[0] || "", + image: img, }); } const deductions = []; for (const item of cartItems) { - const updated = await Product.findOneAndUpdate( - { - _id: item._id, - isDeleted: { $ne: true }, - baseStock: { $gte: item.quantity }, - }, - { $inc: { baseStock: -item.quantity } } - ); + let updated; + if (item.variantId) { + updated = await Product.findOneAndUpdate( + { + _id: item._id, + isDeleted: { $ne: true }, + "variants._id": item.variantId, + "variants.stock": { $gte: item.quantity } + }, + { $inc: { "variants.$.stock": -item.quantity } }, + { new: true } + ); + } else { + updated = await Product.findOneAndUpdate( + { + _id: item._id, + isDeleted: { $ne: true }, + baseStock: { $gte: item.quantity }, + }, + { $inc: { baseStock: -item.quantity } }, + { new: true } + ); + } if (!updated) { console.error(`Checkout webhook: stock changed during fulfillment for ${item._id}`); @@ -215,13 +274,22 @@ export const stripeWebhook = async (req, res) => { return res.json({ received: true }); } + let newStock = 0; + if (item.variantId) { + const variant = updated.variants.id(item.variantId); + newStock = variant ? variant.stock : 0; + } else { + newStock = updated.baseStock; + } + // Emit real-time stock update io.emit("stockUpdate", { productId: item._id, - newStock: updated.stock - item.quantity + variantId: item.variantId || null, + newStock: newStock }); - deductions.push({ productId: item._id, quantity: item.quantity }); + deductions.push({ productId: item._id, variantId: item.variantId || null, quantity: item.quantity }); } const order = await Order.create({ diff --git a/scratch_checkout.txt b/scratch_checkout.txt new file mode 100644 index 0000000000000000000000000000000000000000..d76f7089ab9ade7567fca4769f5b5d69fc5afba3 GIT binary patch literal 21030 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 B1YrOG literal 0 HcmV?d00001 diff --git a/scratch_lines.txt b/scratch_lines.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b8af0531684c4ca010f96d505867c04d85bf9b0 GIT binary patch literal 23504 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 literal 0 HcmV?d00001