(function() { const middleware = {}; middleware.config = { enabled: true, checkoutLoginRequired: false }; middleware.server_vars = {}; middleware.server_vars = {"domain":"https:\/\/gateway.aws.payl8r.com\/middleware"}; Object.assign(middleware.config, middleware.server_vars); // Who needs jquery..? - Helper function sections middleware.select = selector => [...document.querySelectorAll(selector)]; middleware.selectOne = selector => document.querySelector(selector); middleware.exists = selector => document.querySelector(selector) !== null; middleware.create = (html, tag = 'div') => { var frag = document.createDocumentFragment(), temp = document.createElement(tag); temp.innerHTML = html; while (temp.firstChild) { frag.appendChild(temp.firstChild); } return frag; }; middleware.prepend = (node, element, index = 0) => node.insertBefore(element, node.childNodes[index]); middleware.append = (node, element) => node.insertBefore(element, node.childNodes[node.childNodes.length - 1]); middleware.insert = (node, element, nth = null) => nth == null ? middleware.append(node, element) : middleware.prepend(node, element, nth); middleware.insertCss = (css) => middleware.append(middleware.selectOne('body'), middleware.create(``)); middleware.propsToAttributes = (props) => Object.keys(props).filter(prop => prop !== "content").map(key => `${key}="${props[key]}"`).join(' '); middleware.onClick = (selector, fn) => document.addEventListener('click', event => event.target.matches(selector) && fn(event)); middleware.show = (selector) => middleware.selectOne(selector).style.display = 'block'; middleware.hide = (selector) => middleware.selectOne(selector).style.display = 'none'; middleware.remove = (selector) => middleware.selectOne(selector) == null ? null : middleware.selectOne(selector).parentNode.removeChild(middleware.selectOne(selector)); middleware.waitUntil = (condition_closure, tick = 100) => new Promise((resolve, reject) => { if(condition_closure()) { return resolve(); } return setTimeout(() => middleware.waitUntil(condition_closure, tick).then(resolve), tick); }); // Ajax / XHR middleware.serializeData = data => Object.keys(data).reduce((form, key) => { form.append(key, typeof data[key] == 'object' ? JSON.stringify(data[key]) : data[key]); return form; }, new FormData()); middleware.post = (endpoint, data) => fetch(endpoint, { method: 'POST', mode: 'cors', // body: JSON.stringify(data) body: middleware.serializeData(data) }); middleware.getCustomerId = () => ((typeof ShopifyAnalytics !== 'undefined' && ShopifyAnalytics.meta && ShopifyAnalytics.meta.page && ShopifyAnalytics.meta.page.customerId) ? ShopifyAnalytics.meta.page.customerId : null); // Middleware tools middleware.getCart = () => new Promise((resolve, reject) => { fetch(`${window.location.origin}/cart.js`) .then(response => response.json()) .then(resolve); }); middleware.clearCart = () => new Promise((resolve, reject) => { fetch(`${window.location.origin}/clear.js`) .then(response => response.json()) .then(resolve => resolve); }); middleware.getShippingOptions = () => new Promise((resolve, reject) => { fetch(`${window.location.origin}/cart/shipping_rates.json?shipping_address%5Bzip%5D=m98dq&shipping_address%5Bcountry%5D=United+Kingdom`) .then(response => response.json()) .then(response => response.shipping_rates) .then(resolve); }); middleware.selectShippingOption = () => middleware.getShippingOptions().then(rates => new Promise((resolve, reject) => { // If there are no rates available - resolve with nothing if(rates == null || rates.length == 0) { resolve(null); return; } const options = rates.map(option => ({ name: option.name, code: option.code, price: option.price, price_display: `£${option.price}` })); middleware.modal('Select Shipping Option', options.map((_, i) => `${_.name} - ${_.price_display}`)).then(modalIndex => { resolve(rates[modalIndex]); }); })); // middleware.forceLogin = () => new Promise((resolve, reject) => { if(middleware.config.checkoutLoginRequired && middleware.getCustomerId() == null) { return middleware.modal('You need to be logged in in order to checkout with payl8r', ['Log in']).then(modalIndex => { window.location.href = '/account/login?return_url=%2Fcart'; reject(); }); } return resolve(); }); middleware.submitCartToMiddleware = () => new Promise((resolve, reject) => { middleware.initialiseModal(); middleware.getCart().then(cart => { middleware.forceLogin().then(() => { middleware.selectShippingOption().then(shippingOption => { middleware.post(`${middleware.config.domain}/v1/checkout`, { cart_json: JSON.stringify(cart), shipping_json: JSON.stringify(shippingOption), customerId: middleware.getCustomerId() }) .then(response => response.json()) .then(response => { resolve(response); }); }); }); }); }); middleware.modalStyle = ``; middleware.initialiseModal = () => { middleware.append(middleware.selectOne('body'), middleware.create(`
${middleware.modalStyle}
Please wait...
`)); }; middleware.updateModal = (label = null, content = null) => { if(label !== null) { middleware.remove('#payl8r_modal_label'); middleware.append(middleware.selectOne('#payl8r_modal_wrapper .payl8r_modal'), middleware.create(`
${label}
`)); } if(content !== null) { middleware.remove('#payl8r_modal_content'); middleware.append(middleware.selectOne('#payl8r_modal_wrapper .payl8r_modal'), middleware.create(`
${content}
`)); } } middleware.modalClose = () => middleware.remove('#payl8r_modal_wrapper'); middleware.modal = (label = '', options = []) => new Promise((resolve, reject) => { middleware.updateModal(label, options.map((option, index) => `
${option}
`).join('')); middleware.onClick('#payl8r_modal_wrapper', () => middleware.modalClose()); middleware.onClick('.payl8r_modal_option', (e) => { let result = e.target.dataset.index; middleware.updateModal('Hang on while we are getting your order ready', '
'); middleware.remove('#payl8r_modal_content'); resolve(result); }); }); // Checkout button logic middleware.checkout = { html: `
Pay with
`, style: ` @import url("https://payl8r.com/frontend/css/checkout-button-style.css"); #payl8r-middleware-checkout { float: right; clear: none; width: 225px; margin-left: 8px; font-size: 1em !important; }`, buttonNth: -1, parentSelector: '.cart__submit-controls', shouldDisplay: () => window.location.pathname == '/cart', initialise: () => { // Only add checkout button at the /cart if(!middleware.checkout.shouldDisplay()) { return; } // Add loading behaviour middleware.insertCss(middleware.checkout.style); // Insert the checkout button - once parent selector is available - as it might get added dynamically middleware .waitUntil(() => middleware.exists(middleware.checkout.parentSelector)) .then(() => { const submitArea = middleware.selectOne(middleware.checkout.parentSelector); const checkoutButton = middleware.create(middleware.checkout.html); // Insert button middleware.insert(submitArea, checkoutButton, middleware.checkout.buttonNth); // Add the event handler middleware.onClick(`#payl8r-middleware-checkout, #payl8r-middleware-checkout *`, (event) => middleware.checkout.checkoutClicked(event)); }); }, checkoutClicked: (event = null) => { if(event) { event.preventDefault(); } middleware.submitCartToMiddleware().then(response => { if(response.success) { // window.open(response.redirect_url, '_open'); window.location.href = response.redirect_url; middleware.modalClose(); return; } else { middleware.updateModal(response.result); } }) } }; // Calculator section middleware.calculator = { html : `
`, nth: -1, style: ``, scriptAttributes: { src: 'https://assets.payl8r.com/js/pl-calculator-light-app.js', id: 'pl-calculator-light-app-script', 'data-theme': 'spread', }, parentSelector: `.price__regular`, shouldDisplay: () => ( window.ShopifyAnalytics !== null && window.ShopifyAnalytics.meta !== null && window.ShopifyAnalytics.meta.page !== null && window.ShopifyAnalytics.meta.page.pageType == 'product'), priceGetter: () => parseFloat(middleware.selectOne(middleware.calculator.priceSelector).textContent.match(/[\d\.\,]+/g)[0].replace(',', '')), getUsername: () => new Promise ( ( resolve, reject) => { middleware.post(`${middleware.config.domain}/v1/getUsername`, { }) .then(response => response.json()) .then(response => { resolve(response); }); }), priceSelector: '.price-item.price-item--regular', priceChangeDomSelector: '.price-item.price-item--regular', minValueVisible: 50, initialise: () => { // Only display on product pages if(!middleware.calculator.shouldDisplay()) { return; } // Add style middleware.insertCss(middleware.calculator.style); // Calculator init document.plCalcPrice = {Price: middleware.calculator.priceGetter()}; // Username init middleware.calculator.getUsername().then(response => { document.plGetUsername = {Username: response.username}; }); middleware .waitUntil(() => middleware.exists(middleware.calculator.parentSelector)) .then(() => { // Initialize button middleware.insert( middleware.selectOne(middleware.calculator.parentSelector), middleware.create(middleware.calculator.html), middleware.calculator.nth ); // Initialise script let script = document.createElement( 'script' ); Object .keys(middleware.calculator.scriptAttributes) .forEach(key => script.setAttribute(key, middleware.calculator.scriptAttributes[key])); script.onload = () => setTimeout(middleware.calculator.refreshPrice(), 250); middleware.append(middleware.selectOne('body'), script); // Initialise price change detection middleware.calculator.priceUpdateDetectionLogic(); }); }, refreshPrice: () => { document.plCalcPrice.Price = middleware.calculator.priceGetter(); if(document.plCalcPrice.rerender) { document.plCalcPrice.rerender(document.plCalcPrice.Price); } // Show calculator on min value middleware.show('#pl-calculator-light-app'); if(middleware.calculator.priceGetter() < middleware.calculator.minValueVisible) { middleware.hide('#pl-calculator-light-app'); } }, priceUpdateDetectionLogic: () => { let MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; let priceDom = document.querySelector(middleware.calculator.priceChangeDomSelector); (new MutationObserver(() => middleware.calculator.refreshPrice())) .observe( priceDom, { attributes: true, childList: true, characterData: true, subtree: true } ); } } middleware.checkoutPage = { getCurrentPageElement: (selector) => fetch(window.location.href) .then(response => response.text()) .then(body => { let x = document.createElement('html'); x.innerHTML = body; return x; }) .then(_ => [..._.querySelectorAll(selector)]), script: ``, html: `
Pay with
`, style: `@import url("https://payl8r.com/frontend/css/checkout-button-style.css"); #payl8r-middleware-checkout { float: right; clear: none; width: 225px; margin-left: 8px; font-size: 1em !important; }`, buttonNth: 3, parentSelector: `.step__footer`, shouldDisplay: () => window.ShopifyAnalytics && window.ShopifyAnalytics.meta && window.ShopifyAnalytics.meta.page && window.ShopifyAnalytics.meta.page.path && ( window.ShopifyAnalytics.meta.page.path == "/checkout/contact_information" || window.ShopifyAnalytics.meta.page.path == "/checkout/shipping" || window.ShopifyAnalytics.meta.page.path == "/checkout/payment" ), initialise: () => { if(middleware.checkoutPage.shouldDisplay()) { const submitArea = middleware.selectOne(middleware.checkoutPage.parentSelector); const checkoutButton = middleware.create(middleware.checkoutPage.html); // Insert last of at nth position if specified middleware.insert(submitArea, checkoutButton, middleware.checkout.buttonNth); // Insert css middleware.insertCss(middleware.checkoutPage.style); // Add the event handler middleware.onClick(`#payl8r-middleware-checkout, #payl8r-middleware-checkout *`, middleware.checkoutPage.clicked); } }, clicked: (event = null) => { middleware.post(`${middleware.config.domain}/v1/checkout`, { checkout_token: Shopify.Checkout.token }) } } // Only available within checkout page middleware.checkoutPage.details = { selectorDiscount: '.reduction-code__text', selectorEmail: '.review-block__content bdo[dir=ltr]', selectorAddress: '.address.address--tight', getDiscount: () => new Promise((resolve, reject) => { middleware.checkoutPage.getCurrentPageElement(middleware.checkoutPage.discounts.selector) .then(nodes => { if(nodes.length > 0) { return resolve(Array.from(new Set(nodes.map(x => x.innerText)))); } return resolve([]); }); }), getEmail: () => new Promise((resolve, reject) => { middleware.checkoutPage.getCurrentPageElement(middleware.checkoutPage.contactInformation.selectorEmail) .then(nodes => { return (nodes.length > 0) ? resolve(nodes.map(x => x.innerText).join('')) : resolve(null); }); }), getAddress: () => new Promise((resolve, reject) => { middleware.checkoutPage.getCurrentPageElement(middleware.checkoutPage.contactInformation.selectorAddress) .then(nodes => { return (nodes.length > 0) ? resolve(nodes.map(x => x.innerText).join('').trim().split(', ')) : resolve(null); }); }) }; middleware.config.enabled = false; // No retailer found, domains searched: // if(middleware.config.enabled) { // If login is not set to required or customer id available middleware.checkout.initialise(); middleware.calculator.initialise(); // middleware.checkoutPage.initialise(); } // Expose middleware to frontend window.payl8r_middleware = middleware; document.payl8r_middleware = middleware; })();