/** * IndiaMeters Purchase Panel - v4 (Production Quality) * * Improvements: * ✅ Promise-based Ecwid waiting (no polling) * ✅ Listener registered BEFORE Add to Cart, removed AFTER firing * ✅ Dynamic price detection (updates on variant changes) * ✅ Debug logging (toggle DEBUG flag) * ✅ Better DOM selectors (not just text) * ✅ Proper error handling */ (function() { 'use strict'; // ===== CONFIG ===== var DEBUG = false; // Set to true for console logging during development var config = { selectors: { price: '.details-product-price__value', priceAlt: '.ec-price-item', addBtn: 'button, a[role=button]' }, timeouts: { cartChangeListener: 3500, ecwidWait: 50 // attempts } }; // ===== LOGGING ===== function log(label, data) { if (DEBUG) { console.log('[IndiaMeters] ' + label, data || ''); } } // ===== STATE ===== var state = { productId: null, buyNowBtn: null, stickyBar: null, priceObserver: null, currentPrice: null }; // ===== PROMISES & WAITING ===== /** * Wait for Ecwid to be ready * Returns a Promise that resolves when Ecwid.OnPageLoaded exists */ function waitForEcwid() { return new Promise(function(resolve) { var attempts = 0; var maxAttempts = config.timeouts.ecwidWait; var check = function() { attempts++; if (typeof Ecwid !== 'undefined' && Ecwid.OnPageLoaded) { log('Ecwid ready after ' + attempts + ' attempts'); resolve(Ecwid); } else if (attempts < maxAttempts) { setTimeout(check, 200); } else { log('Ecwid not ready after ' + maxAttempts + ' attempts'); resolve(null); } }; check(); }); } // ===== HELPERS ===== function findPurchaseButton() { var buttons = document.querySelectorAll(config.selectors.addBtn); for (var i = 0; i < buttons.length; i++) { var btn = buttons[i]; var text = (btn.textContent || btn.value || '').toLowerCase(); // Match: "add", "buy" (but not "buy now", "continue", "checkout") var hasAddAction = text.indexOf('add') > -1 || (text.indexOf('buy') > -1 && text.indexOf('buy now') === -1); var isNotExcluded = text.indexOf('continue') === -1 && text.indexOf('checkout') === -1; if (hasAddAction && isNotExcluded) { log('Found purchase button: ' + text); return btn; } } log('No purchase button found'); return null; } function getPriceText() { try { // Try primary selector first var el = document.querySelector(config.selectors.price); if (el) { var match = el.textContent.match(/[₹$]\s?[\d,]+(?:\.\d{2})?/); if (match) return match[0].trim(); } // Try alternative var elAlt = document.querySelector(config.selectors.priceAlt); if (elAlt) { var m = elAlt.textContent.match(/[₹$]\s?[\d,]+(?:\.\d{2})?/); if (m) return m[0].trim(); } return '₹0'; } catch (e) { log('Price extraction error: ' + e.message); return '₹0'; } } function clickAddToCart() { var btn = findPurchaseButton(); if (btn) { log('Clicking Add to Cart'); btn.click(); } } function gotoCheckout() { if (typeof Ecwid !== 'undefined' && Ecwid.Cart && typeof Ecwid.Cart.gotoCheckout === 'function') { log('Going to checkout'); Ecwid.Cart.gotoCheckout(); } } // ===== LISTENER MANAGEMENT ===== /** * Trigger Add to Cart, then checkout after cart updates * Listener is created, added BEFORE clicking, and removed AFTER firing */ function handleBuyNow(uiElement) { if (!uiElement) return; uiElement.disabled = true; var originalText = uiElement.textContent; uiElement.textContent = 'Adding...'; log('Buy Now triggered'); // Create listener BEFORE clicking var onCartChanged = function() { log('Cart changed, removing listener and going to checkout'); // Remove listener immediately after firing if (typeof Ecwid !== 'undefined' && Ecwid.OnCartChanged && typeof Ecwid.OnCartChanged.remove === 'function') { Ecwid.OnCartChanged.remove(onCartChanged); } uiElement.textContent = 'Redirecting...'; setTimeout(gotoCheckout, 300); }; // Register listener BEFORE clicking if (typeof Ecwid !== 'undefined' && Ecwid.OnCartChanged && typeof Ecwid.OnCartChanged.add === 'function') { Ecwid.OnCartChanged.add(onCartChanged); log('OnCartChanged listener registered'); } // Click Add to Cart clickAddToCart(); // Safety timeout: remove listener if cart doesn't update setTimeout(function() { if (typeof Ecwid !== 'undefined' && Ecwid.OnCartChanged && typeof Ecwid.OnCartChanged.remove === 'function') { Ecwid.OnCartChanged.remove(onCartChanged); log('Listener cleanup timeout fired'); } uiElement.disabled = false; uiElement.textContent = originalText; }, config.timeouts.cartChangeListener); } // ===== UI: INLINE BUTTON ===== function createInlineButton() { if (document.getElementById('im-btn')) { log('Inline button already exists'); return; } var purchaseBtn = findPurchaseButton(); if (!purchaseBtn) { log('Cannot create inline button: purchase button not found'); return; } var buyNow = document.createElement('button'); buyNow.id = 'im-btn'; buyNow.className = 'im-btn'; buyNow.textContent = 'Buy Now'; buyNow.type = 'button'; buyNow.addEventListener('click', function(e) { e.preventDefault(); handleBuyNow(buyNow); }); purchaseBtn.parentNode.insertBefore(buyNow, purchaseBtn.nextSibling); state.buyNowBtn = buyNow; log('Inline Buy Now button created'); } // ===== UI: STICK