/*! MultiWhop Checkout Script v1.0 */ (function() { 'use strict'; var CONFIG = { storeId: '69265ddb9e82ad9d69d0412a', apiUrl: 'https://checkoutflow.app/api/functions', checkoutUrl: 'https://checkoutflow.app/Checkout', trackingUrl: 'https://checkoutflow.app/api/functions/trackCheckoutEvent', redirectPercentage: 100, checkoutEnabled: true, debug: true, sessionId: 'mw_' + Math.random().toString(36).substr(2, 9) }; function log() { if (CONFIG.debug) { console.log.apply(console, ['[MultiWhop]'].concat(Array.prototype.slice.call(arguments))); } } function shouldRedirect() { return Math.random() * 100 < CONFIG.redirectPercentage; } // Tracking function function trackEvent(eventType, extraData) { var payload = { event_type: eventType, store_id: CONFIG.storeId, session_id: CONFIG.sessionId }; if (extraData) { for (var key in extraData) { if (extraData.hasOwnProperty(key)) { payload[key] = extraData[key]; } } } log('Tracking event:', eventType, payload); fetch(CONFIG.trackingUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).catch(function(err) { log('Tracking error:', err); }); } // Extraire les infos du panier depuis la page Shopify function getCartData() { log('Fetching cart data from /cart.js...'); return fetch('/cart.js', { credentials: 'same-origin' }) .then(function(res) { log('Cart response status:', res.status); if (!res.ok) { throw new Error('Cart fetch failed: ' + res.status); } return res.json(); }) .then(function(cartData) { log('Cart data received:', cartData); return cartData; }) .catch(function(err) { console.error('[MultiWhop] Erreur récupération panier:', err); // Fallback: essayer avec l'API alternative return fetch('/cart?view=json', { credentials: 'same-origin' }) .then(function(res) { return res.json(); }) .catch(function() { log('Fallback cart fetch also failed'); return null; }); }); } // Extraire l'email si disponible function getCustomerEmail() { var emailInput = document.querySelector('input[type="email"], input[name="email"], input[name="checkout[email]"]'); if (emailInput && emailInput.value) return emailInput.value; if (window.ShopifyAnalytics && window.ShopifyAnalytics.meta && window.ShopifyAnalytics.meta.page) { return window.ShopifyAnalytics.meta.page.customerEmail || null; } return null; } // Créer une transaction et rediriger function initiateCheckout(cartData) { log('Initiating checkout with cart:', cartData); var cartItems = cartData.items.map(function(item) { // Shopify retourne l'image dans différents formats selon le thème var imageUrl = null; if (item.image) { imageUrl = typeof item.image === 'string' ? item.image : item.image.url || item.image.src; } else if (item.featured_image) { imageUrl = typeof item.featured_image === 'string' ? item.featured_image : item.featured_image.url || item.featured_image.src; } // Assurer que l'URL est complète if (imageUrl && !imageUrl.startsWith('http')) { imageUrl = 'https:' + imageUrl; } return { variant_id: String(item.variant_id), product_name: item.product_title, quantity: item.quantity, price: item.price / 100, image: imageUrl, variant_title: item.variant_title || null, product_id: item.product_id ? String(item.product_id) : null }; }); var payload = { store_id: CONFIG.storeId, cart_items: cartItems, total_amount: cartData.total_price / 100, currency: cartData.currency || 'EUR', customer_email: getCustomerEmail() }; log('Payload:', payload); fetch(CONFIG.apiUrl + '/initiateCheckout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) .then(function(res) { return res.json(); }) .then(function(data) { log('Response:', data); if (data.checkout_url) { window.location.href = data.checkout_url; } else if (data.transaction_id) { window.location.href = CONFIG.checkoutUrl + '?transaction_id=' + data.transaction_id; } else { console.error('[MultiWhop] Erreur:', data.error || 'Réponse invalide'); window.location.href = '/checkout'; } }) .catch(function(err) { console.error('[MultiWhop] Erreur réseau:', err); window.location.href = '/checkout'; }); } // Intercepter les clics vers le checkout - SEULEMENT checkout et buy now function interceptCheckout(e) { var target = e.target; // ÉTAPE 1: Vérifier si le clic est dans un formulaire /cart/add (Add to Cart) // Si oui, on IGNORE complètement sauf si c'est le bouton Buy Now var clickedInAddForm = target.closest && target.closest('form[action*="/cart/add"]'); var clickedOnBuyNow = target.closest && target.closest('.shopify-payment-button'); if (clickedInAddForm && !clickedOnBuyNow) { log('Clic dans formulaire add to cart, ignoré'); return; // Laisser Shopify gérer } // ÉTAPE 2: Chercher si c'est un checkout ou buy now while (target && target !== document.body) { var tagName = target.tagName ? target.tagName.toLowerCase() : ''; var href = target.getAttribute ? target.getAttribute('href') : ''; var name = target.getAttribute ? target.getAttribute('name') : ''; var formAction = target.getAttribute ? target.getAttribute('formaction') : ''; // Liens directs vers /checkout var isCheckoutLink = href && ( href === '/checkout' || href.endsWith('/checkout') || href.includes('/checkout?') || href.includes('/cart/checkout') ); // Bouton checkout (name="checkout" OU classes communes des cart drawers) var buttonText = (target.textContent || '').toLowerCase().trim(); var isCheckoutButton = ( (tagName === 'button' || tagName === 'input' || tagName === 'a') && ( name === 'checkout' || (target.classList && ( target.classList.contains('checkout-button') || target.classList.contains('cart__checkout') || target.classList.contains('cart-checkout') || target.classList.contains('btn-checkout') || target.classList.contains('checkout') || target.classList.contains('cart__checkout-button') || target.classList.contains('drawer__checkout') || target.classList.contains('ajax-cart__checkout') )) || (buttonText === 'checkout' || buttonText === 'check out' || buttonText === 'passer la commande' || buttonText === 'commander' || buttonText === 'valider') ) ); // Formulaire checkout (pas /cart/add) var isCheckoutForm = tagName === 'form' && target.action && ( target.action.endsWith('/checkout') || target.action.includes('/checkout?') ); // Bouton "Buy Now" de Shopify (paiement express) - classe très spécifique var isBuyNowButton = ( target.classList && target.classList.contains('shopify-payment-button__button') ) || ( target.closest && target.closest('.shopify-payment-button__button') ); if (isCheckoutLink || isCheckoutButton || isCheckoutForm || isBuyNowButton) { log('Checkout intercepté!', { isCheckoutLink, isCheckoutButton, isCheckoutForm, isBuyNowButton }); trackEvent('checkout_clicked'); if (!CONFIG.checkoutEnabled) { log('Checkout externe désactivé, passage au checkout Shopify natif'); return; } if (!shouldRedirect()) { log('Redirection ignorée (pourcentage)'); return; } e.preventDefault(); e.stopPropagation(); // Pour Buy Now, attendre que le produit soit ajouté au panier var delay = isBuyNowButton ? 1500 : 0; // Si c'est un Buy Now sur une page produit, d'abord ajouter au panier if (isBuyNowButton) { log('Buy Now détecté, tentative d\'ajout au panier...'); // Récupérer le variant_id depuis le formulaire produit var productForm = document.querySelector('form[action*="/cart/add"]'); var variantInput = productForm ? productForm.querySelector('input[name="id"], select[name="id"]') : null; var quantityInput = productForm ? productForm.querySelector('input[name="quantity"]') : null; if (variantInput) { var variantId = variantInput.value; var quantity = quantityInput ? (parseInt(quantityInput.value) || 1) : 1; log('Adding to cart: variant=' + variantId + ', qty=' + quantity); // Ajouter au panier via l'API Shopify fetch('/cart/add.js', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: variantId, quantity: quantity }), credentials: 'same-origin' }) .then(function(res) { return res.json(); }) .then(function(addedItem) { log('Product added to cart:', addedItem); // Maintenant récupérer le panier complet return getCartData(); }) .then(function(cartData) { log('Cart after add:', cartData); if (cartData && cartData.items && cartData.items.length > 0) { trackEvent('checkout_redirect', { cart_value: cartData.total_price / 100 }); initiateCheckout(cartData); } else { log('Cart still empty, fallback to Shopify'); window.location.href = '/checkout'; } }) .catch(function(err) { console.error('[MultiWhop] Add to cart error:', err); window.location.href = '/checkout'; }); return false; } } // Pour les autres cas (panier déjà rempli) setTimeout(function() { getCartData().then(function(cartData) { log('Cart data for checkout:', cartData); if (cartData && cartData.items && cartData.items.length > 0) { trackEvent('checkout_redirect', { cart_value: cartData.total_price / 100 }); initiateCheckout(cartData); } else { console.error('[MultiWhop] Panier vide ou introuvable. Cart data:', cartData); console.error('[MultiWhop] Fallback vers checkout Shopify natif'); window.location.href = '/checkout'; } }).catch(function(err) { console.error('[MultiWhop] Erreur getCartData:', err); window.location.href = '/checkout'; }); }, delay); return false; } target = target.parentNode; } } // Intercepter les soumissions de formulaire - UNIQUEMENT checkout, PAS add to cart function interceptFormSubmit(e) { var form = e.target; if (form.tagName && form.tagName.toLowerCase() === 'form') { var action = form.action || ''; // IGNORER les formulaires add to cart if (action.includes('/cart/add')) { log('Form add to cart, on laisse passer:', action); return; // Ne pas intercepter } // Intercepter UNIQUEMENT les formulaires checkout explicites var isCheckoutForm = ( action.endsWith('/checkout') || action.includes('/checkout?') || action.includes('/cart/checkout') ); if (isCheckoutForm) { log('Form checkout intercepté:', action); if (!CONFIG.checkoutEnabled) { log('Checkout externe désactivé'); return; } if (!shouldRedirect()) { log('Redirection ignorée (pourcentage)'); return; } e.preventDefault(); e.stopPropagation(); getCartData().then(function(cartData) { if (cartData && cartData.items && cartData.items.length > 0) { initiateCheckout(cartData); } else { form.submit(); } }); return false; } } } // Initialisation function init() { console.log('%c✅ MultiWhop Checkout Script chargé avec succès!', 'color: #22c55e; font-weight: bold; font-size: 14px;'); console.log('%cStore ID: ' + CONFIG.storeId + ' | Checkout actif: ' + CONFIG.checkoutEnabled + ' | Redirection: ' + CONFIG.redirectPercentage + '%', 'color: #6366f1;'); log('MultiWhop initialisé pour store:', CONFIG.storeId); trackEvent('script_loaded'); document.addEventListener('click', interceptCheckout, true); document.addEventListener('submit', interceptFormSubmit, true); if (window.MutationObserver) { var observer = new MutationObserver(function(mutations) {}); observer.observe(document.body, { childList: true, subtree: true }); } log('Listeners attachés'); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } window.MultiWhop = { config: CONFIG, getCartData: getCartData, initiateCheckout: initiateCheckout, trackEvent: trackEvent, enableDebug: function() { CONFIG.debug = true; log('Debug activé'); } }; })();