// Kviq const SCRIPT_FILENAME = 'kviq-buy.js'; const BUY_BTN_CLASS = '_kviq_buy'; const KVIQ_ID = ( window.location.search.match(/[?&]_k=([^&]+)/) || [ , '' ] )[1]; const ROOT_DOMAIN = 'https://s2.kviq.no'; const POLL_INTERVAL = 5000; const POLL_TIMEOUT = 60000; //const STORES_DEBUGGING = [ 8093139025, 64014647361, 25135185971 ]; const STORES_DEBUGGING = [ 8093139025, 25135185971 ]; let KVIQ_TESTING = false; let KVIQ_LOADING = false; let paymentTimer = null; let storeId = 'unknown'; let kviq_buy_button, kviq_buy_button_live, kviq_buy_button_loading; if ( document.readyState === 'loading' ){ document.addEventListener( 'DOMContentLoaded', initKviq ); } else { initKviq(); } // Fix cleanup window.addEventListener('beforeunload', () => { if (paymentTimer) clearInterval(paymentTimer); }); // helper must come early async function jsonPost(url, body){ const res = await fetch(url, { method: 'POST', headers: { 'Content-Type':'application/json' }, body: JSON.stringify(body) }); const data = await res.json().catch(() => ({ error: 'Invalid JSON' })); if (!res.ok || !data.vippsRedirectUrl){ throw new Error(data.error || 'Request failed'); } return data; } (function(){ const params = new URLSearchParams( window.location.search ); const keys = [ 'utm_source','utm_medium','utm_campaign','utm_term','utm_content','utm_id','utm_channel','fbclid','gclid','gbraid','wbraid','ttclid','li_fat_id','msclkid','yclid','dclid','twclid','snclid','ref','referrer','affiliate_id','partner','campaign_id' ]; let data; try { data = JSON.parse( localStorage.getItem('kviq_tracking') ) || {}; } catch { data = { http_referrer: document.referrer || null }; } let updated = false; keys.forEach( k => { const v = params.get(k); if ( !v ){ return; } else { const existing = data[k]; if ( !existing || existing.value !== v ){ data[k] = { value: v, url: window.location.href, timestamp: new Date().toISOString() }; updated = true; } } }); if ( updated ){ data.last_update = new Date().toISOString(); try { localStorage.setItem('kviq_tracking', JSON.stringify(data)); } catch (e) { console.warn('Failed to save tracking data:', e); } } })(); function initKviq(){ const scriptTag = Array.from( document.querySelectorAll('script') ).find( s => s.src && s.src.includes(SCRIPT_FILENAME) ); storeId = (()=>{ try { const url = new URL( scriptTag.src ); const param = url.searchParams.keys().next().value; if ( param && /^\d+$/.test(param) ){ return param; } else { if ( scriptTag?.dataset?.storeId ){ return scriptTag.dataset.storeId; } else { return 'unknown'; } } } catch { return 'unknown'; } })(); // Debug flag logic const urlParams = new URLSearchParams(window.location.search); if (urlParams.has('kviq_testing')) { const debugValue = urlParams.get('kviq_testing'); if (debugValue === 'on') { localStorage.setItem('KVIQ_TESTING', '1'); } else if (debugValue === 'off') { localStorage.removeItem('KVIQ_TESTING'); } } KVIQ_TESTING = !!localStorage.getItem('KVIQ_TESTING'); const storeLanguage = scriptTag?.dataset?.lang || 'en'; kviq_buy_button_live = `Kjøp nå med `; kviq_buy_button_loading = ``; kviq_buy_button = kviq_buy_button_live; function observeCartDrawer(){ const obs = new MutationObserver(()=>{ const cartBtn = document.querySelector('._kviq_cart_button'); if ( cartBtn ){ if (!cartBtn.classList.contains('_kviq_ready')) { activateKviqButton('._kviq_cart_button', 'cart', async ()=>{ const res = await fetch('/cart.js',{credentials:'same-origin'}); const cart = await res.json(); return cart.items.map(i=>({variant_id:i.id,quantity:i.quantity})); }); } } }); obs.observe( document.body, { childList:true, subtree:true } ); } function checkPaymentStatus(){ if ( KVIQ_ID ){ fetch( ROOT_DOMAIN +'/api/vipps-status', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ ref: KVIQ_ID }) }) .then(r => r.json()) .then(d => { if ( d ){ // Restore buttons if error, timeout if ( d.status === 'timeout' || d.status === 'error' || d.status === 'aborted' ){ if ( paymentTimer ){ clearInterval( paymentTimer ); } document.querySelectorAll('._kviq_buynow_button, ._kviq_cart_button').forEach( btn => { btn.classList.remove('loading'); kviq_buy_button = kviq_buy_button_live; btn.innerHTML = kviq_buy_button; }); KVIQ_LOADING = false; } else { if ( ( d.status === 'redirect' || d.status === 'done' ) && d.data.shopify_order_url ){ fetch('/cart/clear.js', { method: 'POST', credentials: 'same-origin' }).then(() => console.log('Cart cleared')).catch(e => console.warn('Cart clear failed:', e)); window.location.href = ROOT_DOMAIN +'/r?'+ encodeURIComponent( d.data.shopify_order_url ); } } } }) .catch(e => { console.error('Vipps status error:', e); }); } } activateKviqButton('._kviq_buynow_button', 'product', async ()=>{ const form = document.querySelector('form[action*="/cart/add"]'); const id = form?.querySelector('[name="id"]')?.value || new URLSearchParams(location.search).get('variant'); const q = form?.querySelector('[name="quantity"]')?.value || 1; return [ { variant_id: id, quantity: parseInt(q,10) } ]; }); activateKviqButton('._kviq_cart_button', 'cart', async ()=>{ const res = await fetch('/cart.js',{credentials:'same-origin'}); const cart = await res.json(); return cart.items.map(i=>({variant_id:i.id,quantity:i.quantity})); }); observeCartDrawer(); if (KVIQ_ID) { const buttons = document.querySelectorAll('._kviq_buynow_button, ._kviq_cart_button'); let timeoutId = null; // Update button states to show loading buttons.forEach(btn => { if (!btn.classList.contains('loading')) { btn.dataset.originalHtml ??= btn.innerHTML; // Store original HTML btn.classList.add('loading'); btn.innerHTML = kviq_buy_button_loading; } }); KVIQ_LOADING = true; // Clear any existing timeout if (timeoutId) clearTimeout(timeoutId); async function pollPaymentStatus(attempt = 1, maxAttempts = POLL_TIMEOUT / POLL_INTERVAL) { try { await checkPaymentStatus(); // Assume this function handles status checks and redirects // Schedule next check if within attempt limit if (attempt < maxAttempts) { timeoutId = setTimeout(() => pollPaymentStatus(attempt + 1, maxAttempts), POLL_INTERVAL); } else { // Max attempts reached, reset buttons console.log('Payment check expired after 60 seconds'); buttons.forEach(btn => { btn.classList.remove('loading'); btn.innerHTML = btn.dataset.originalHtml || kviq_buy_button_live; }); KVIQ_LOADING = false; } } catch (error) { console.error('Payment status check failed:', error); // Optionally retry on error or reset immediately buttons.forEach(btn => { btn.classList.remove('loading'); btn.innerHTML = btn.dataset.originalHtml || kviq_buy_button_live; }); KVIQ_LOADING = false; } } // Start the first check pollPaymentStatus(); } else { // Improved click handler: Use event delegation on the product form instead of body for better scoping const productForm = document.querySelector('form[action*="/cart/add"]'); if (productForm) { productForm.addEventListener('click', e => { const btn = e.target.closest('._kviq_buynow_button'); if (!btn || btn.classList.contains('_kviq_ready')) return; const variantInput = productForm.querySelector('[name="id"]'); if (variantInput) { btn.dataset.variantid = variantInput.value; } }); // Optimized variant observer: Narrow to observe only the variant input's attributes/subtree const variantInput = productForm.querySelector('[name="id"]'); if (variantInput) { const variantObserver = new MutationObserver(() => { document.querySelectorAll('._kviq_buynow_button').forEach(btn => { btn.dataset.variantid = variantInput.value; }); }); variantObserver.observe(variantInput, { attributes: true, attributeFilter: ['value'] }); } } // Replace polling with a MutationObserver for cart button activation function observeCartForButton() { const cartObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === 'childList') { const cartBtn = document.querySelector('._kviq_cart_button:not(._kviq_ready)'); if (cartBtn) { activateKviqButton('._kviq_cart_button', 'cart', async () => { const res = await fetch('/cart.js', { credentials: 'same-origin' }); const cart = await res.json(); return cart.items.map(i => ({ variant_id: i.id, quantity: i.quantity })); }); } } }); }); // Observe a more specific cart container if possible; fallback to body const cartContainer = document.querySelector('#cart-drawer') || document.body; // Adjust #cart-drawer to your theme's selector cartObserver.observe(cartContainer, { childList: true, subtree: true }); } // Call the observer setup once observeCartForButton(); } } function applyKviqButtonStyle(btn) { const bgColor = btn?.dataset?.bgcolor || '#ff5b24'; const textColor = btn?.dataset?.textcolor || '#ffffff'; const borderRad = btn?.dataset?.borderradius || '0px'; const marginTop = btn?.dataset?.margintop || '0px'; const fontSize = btn?.dataset?.fontsize || '16px'; const fontWeight = btn?.dataset?.fontweight || '600'; const heightVal = btn?.dataset?.height || '50px'; const widthVal = btn?.dataset?.width || '100%'; const gapSize = btn?.dataset?.gap || '6px'; const shouldHide = !KVIQ_TESTING && STORES_DEBUGGING.includes(parseInt(storeId, 10)); Object.assign(btn.style, { backgroundColor: bgColor, color: textColor, borderRadius: borderRad, marginTop: marginTop, height: heightVal, width: widthVal, alignItems: 'center', justifyContent: 'center', gap: gapSize, cursor: 'pointer', fontWeight: fontWeight, fontSize: fontSize, display: shouldHide ? 'none' : 'flex' }); } function activateKviqButton(selector, origin, getOrder){ const btn = document.querySelector(selector); if (!btn || btn.classList.contains('_kviq_ready')) return; btn.classList.add('_kviq_ready','button'); applyKviqButtonStyle(btn); btn.setAttribute('role','button'); btn.setAttribute('tabindex','0'); btn.innerHTML = kviq_buy_button; btn.addEventListener('click', async (e)=>{ e.preventDefault(); e.stopPropagation(); if (KVIQ_LOADING) return; KVIQ_LOADING = true; btn.classList.add('loading'); kviq_buy_button = kviq_buy_button_loading; btn.innerHTML = kviq_buy_button; try { const order = await getOrder(); const tracking = JSON.parse( localStorage.getItem('kviq_tracking') || '{}' ); const data = await jsonPost( ROOT_DOMAIN +'/api/vipps-init', { store_id: storeId, origin: origin, order: order, tracking: tracking, returnUrl: window.location.href }); window.location.href = ROOT_DOMAIN +'/r?'+ encodeURIComponent( data.vippsRedirectUrl ); } catch (err){ console.error('Kviq '+ origin +' button error:', err); alert(err.message || 'Network error.'); restoreKviqButtons(); } }); } function restoreKviqButtons(){ document.querySelectorAll( '._kviq_buynow_button, ._kviq_cart_button' ).forEach( btn=>{ btn.classList.remove( 'loading' ); kviq_buy_button = kviq_buy_button_live; btn.innerHTML = kviq_buy_button; }); KVIQ_LOADING = false; }