diff --git a/pdfGenerator.js b/pdfGenerator.js index 425d654..10a8e4e 100644 --- a/pdfGenerator.js +++ b/pdfGenerator.js @@ -222,9 +222,9 @@ const generateInvoicePDF = async (orderData) => { for (let i = 0; i < orderData.line_items.length; i++) { const item = orderData.line_items[i]; - // Background color for alternating rows + // Background color for alternating rows (slightly taller: 46 height) if (i % 2 === 1) { - doc.rect(40, tableY - 4, 515, 38).fill(rowAltColor); + doc.rect(40, tableY - 6, 515, 46).fill(rowAltColor); } doc.fontSize(9) @@ -232,41 +232,57 @@ const generateInvoicePDF = async (orderData) => { .fillColor(textColor); // S.No - doc.text((i + 1).toString(), 40, tableY); + doc.text((i + 1).toString(), 40, tableY + 10); + + // Handle product image + let textX = 85; + let textWidth = 270; + if (item.image_url) { + const imgBuffer = await fetchImageBuffer(item.image_url); + if (imgBuffer) { + try { + doc.image(imgBuffer, 85, tableY - 2, { fit: [35, 35] }); + textX = 130; + textWidth = 225; + } catch (e) { + console.error('Error rendering line item image:', e); + } + } + } // Product Name and Variant/Quantity details const productText = item.title || item.name; - doc.text(productText, 85, tableY, { width: 270, height: 15, ellipsis: true }); + doc.text(productText, textX, tableY, { width: textWidth, height: 15, ellipsis: true }); // Display quantity/variant info if available if (item.variant_title) { doc.fontSize(8) .fillColor('#64748b') - .text(item.variant_title, 85, tableY + 12, { width: 270 }); + .text(item.variant_title, textX, tableY + 14, { width: textWidth }); } // Reset font size doc.fontSize(9).fillColor(textColor); // Quantity - doc.text(item.quantity.toString(), 365, tableY, { width: 50, align: 'right' }); + doc.text(item.quantity.toString(), 365, tableY + 10, { width: 50, align: 'right' }); // Unit Price const unitPrice = parseFloat(item.price).toFixed(2); - doc.text(`${currencySymbol}${unitPrice}`, 425, tableY, { width: 60, align: 'right' }); + doc.text(`${currencySymbol}${unitPrice}`, 425, tableY + 10, { width: 60, align: 'right' }); // Total Price const totalPrice = (parseFloat(item.price) * item.quantity).toFixed(2); - doc.text(`${currencySymbol}${totalPrice}`, 495, tableY, { width: 60, align: 'right' }); + doc.text(`${currencySymbol}${totalPrice}`, 495, tableY + 10, { width: 60, align: 'right' }); // Divider Line - doc.moveTo(40, tableY + 28) - .lineTo(555, tableY + 28) + doc.moveTo(40, tableY + 38) + .lineTo(555, tableY + 38) .lineWidth(0.5) .strokeColor('#f1f5f9') .stroke(); - tableY += 34; + tableY += 44; } } diff --git a/server.js b/server.js index 6f3b358..373812b 100644 --- a/server.js +++ b/server.js @@ -52,6 +52,43 @@ app.post('/webhooks/orders/create', verifyShopifyWebhook, async (req, res) => { const processMessage = `[${new Date().toISOString()}] Processing Order ${orderData.order_number}\n`; fs.appendFileSync('webhook.log', processMessage); + // Fetch product images from Shopify Admin API (requires read_products scope) + if (orderData.line_items && orderData.line_items.length > 0) { + await Promise.all(orderData.line_items.map(async (item) => { + if (!item.product_id) return; + try { + const shopifyUrl = `https://${process.env.SHOPIFY_STORE_DOMAIN}/admin/api/2024-01/products/${item.product_id}.json`; + const response = await fetch(shopifyUrl, { + headers: { + 'X-Shopify-Access-Token': process.env.SHOPIFY_ADMIN_API_TOKEN + } + }); + if (response.ok) { + const data = await response.json(); + const product = data.product; + if (product) { + // Find variant-specific image + let imgUrl = null; + if (item.variant_id && product.variants) { + const variant = product.variants.find(v => v.id === item.variant_id); + if (variant && variant.image_id && product.images) { + const variantImg = product.images.find(img => img.id === variant.image_id); + if (variantImg) imgUrl = variantImg.src; + } + } + // Fallback to main product image + item.image_url = imgUrl || product.image?.src || (product.images && product.images[0]?.src) || null; + } + } else { + const errText = await response.text(); + console.error(`Shopify API error fetching product ${item.product_id}: Status ${response.status} - ${errText}`); + } + } catch (err) { + console.error(`Failed to fetch image for product ${item.product_id}:`, err); + } + })); + } + // 1. Generate the PDF invoice const pdfBuffer = await generateInvoicePDF(orderData);