Hypochlorous Acid Rice Peel Shot Duo For Face & Body | Gentle Exfoliating Serum for Dead Skin Cells & Pore Care, Blackheads & Excess Sebum Control | Rice Extract, Hypochlorous, Panthenol for Hydrating & Smoothing Care

1k+ sold
$24.90
$33.90
-27%
Count:  Hypochlorous Acid Peel Shot
const addToCartText = "Add to cart"; SPZCore.Dom.waitForChild( document.body, () => !!document.querySelector('[data-click="addToCart"], [role="addToCart"]'), () => { const addToCartEle = window.SPZCore.Dom.scopedQuerySelector( document.body, '[data-click="addToCart"], [role="addToCart"]' ); const getContentChild_ = function(el) { if(el) { const childs = el.childNodes; for (let i = childs.length - 1; i >= 0; i--) { const child = childs[i]; if (child.getAttribute && child.getAttribute('role') === 'content') { return child; } } } return; } const changeAddToCartText = function(text) { const contentEle = getContentChild_(addToCartEle); if(contentEle){ const addToCartChilds = addToCartEle.childNodes; for (let i = addToCartChilds.length - 1; i >= 0; i--) { const child = addToCartChilds[i]; if (typeof child.getAttribute !== 'function' || (child.getAttribute('role') !== 'content' && child.getAttribute('role') !== 'loading')) { addToCartEle.removeChild(child); } } }else{ addToCartEle.innerHTML = addToCartText; } } changeAddToCartText(addToCartText); } );
const SITE = (window.C_SETTINGS && window.C_SETTINGS.routes && window.C_SETTINGS.routes.root) || ''; const PRODUCT_PLAN_INFO_URL = `${SITE}/api/storefront/selling_plans/product_plan_info/:product_id`; const { i18nProductDetail, i18nProduct } = JSON.parse( document.querySelector('#i18n-json').textContent ); const currentI18nProduct = i18nProductDetail || i18nProduct; class SpzCustomSubscription extends SPZ.BaseElement { constructor(element) { super(element); this.productPlanInfo = null; this.cycle = null; this.optionid_ = null; this.form_ = null; this.variantId_ = null; this.variantChangeData = null; this.cycleList = [1,2]; this.productId = this.element.getAttribute('productId'); this.initialCycle = this.element.getAttribute('cycles'); this.noPlanShowTitle = JSON.parse(this.element.getAttribute('noPlanShowTitle')); } get initIsCycleSubscribe() { if(this.initialCycle == 2) { return true; } if(this.initialCycle == 1) { return false; } return null; } get productInfo() { let pJson = document.querySelector('#product-json'); if (pJson) { return JSON.parse(pJson.innerHTML); }else if(typeof $ === 'function' && typeof $(document).data === 'function') { return $(document).data('djproduct'); } return null; } get hasSKU() { if(this.productPlanInfo?.sku) { return true; } return false } get hasSPU() { if(this.productPlanInfo?.spu) { return true; } return false } get originalPrice() { if(!this.variantChangeData?.selected?.price){ return ''; }; return this.variantChangeData.selected.price; } get defaultPlanData() { if (!this.productPlanInfo) { return {}; } let planInfo = {}; if(this.hasSPU && this.productPlanInfo?.spu[this.productId]) { planInfo = this.productPlanInfo.spu[this.productId]; }else if(this.hasSKU && this.productPlanInfo?.sku[this.variantId_]) { planInfo = this.productPlanInfo.sku[this.variantId_]; } return planInfo; } get defaultCycle_() { if(!this.defaultPlanData) { return null; } return this.defaultPlanData.cycles; } get defaultOption_() { if(!this.defaultPlanData) { return null; } return this.defaultPlanData.selected_selling_plan_option_id; } get isCycleSubscribe() { if(this.cycle === 2) { return true; } if(this.cycle === 1) { return false; } return null; } get currentOption_() { if(!this.isCycleSubscribe){ return null; } if(!this.optionId_) { return this.defaultOption_; } return this.optionId_; } get currentPlanData() { if(!this.defaultPlanData) { return {}; } let result = Object.assign({},this.defaultPlanData); if(result.selected_selling_plan_option_id !== this.optionId_) { result.selected_selling_plan_option_id = this.optionId_; result.selling_plan_options = result.selling_plan_options.map(item => { return { ...item, selected_enabled: item.selling_plan_option_id === result.selected_selling_plan_option_id } }) } if (result.cycles !== this.cycle) { result.cycles = this.cycle; } return result; } fetchData_(product_id) { return SPZServices.xhrFor(this.win).fetchJson(PRODUCT_PLAN_INFO_URL.replace(/:product_id/gi, product_id)) .then((data) => { this.productPlanInfo = data; } ); } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } mountCallback() { this.getElement_(); Promise.all([this.productInfoReady_(), this.fetchData_(this.productId)]).then(() => { this.initVariantId(); this.init_(); this.initActions_(); if(!!this.variantId_) { this.track_('product_view', this.initIsCycleSubscribe); } this.showPlan_(true); if(!!this.variantId_){ this.changeBuyNowText(); this.dispatchSubScribeIdChange_(); } }); } productInfoReady_() { if(this.productInfo) { return Promise.resolve(); } SPZUtils.Event.listen(document,'load',()=> { if(this.productInfo) { return Promise.resolve(); }else{ return Promise.reject(); } }) } getElement_() { this.titleEle = SPZCore.Dom.scopedQuerySelector(document.body, '[role="plugin-subscription-title"]'); this.noPlanTipsEle = SPZCore.Dom.scopedQuerySelector(this.element, '[role="plugin-subscription-plan-tips"]'); this.noPaymentTipsEle = SPZCore.Dom.scopedQuerySelector(this.element, '[role="plugin-subscription-payment-tips"]'); this.renderEle = SPZCore.Dom.scopedQuerySelector(this.element, '[role="plugin-subscription-content-render"]'); this.productEle = SPZCore.Dom.scopedQuerySelector(document.body, '[data-section-type="product"], [data-section-type="product_detail"], [data-section-type="product_club"]') this.buyNowEle = SPZCore.Dom.scopedQuerySelector(this.productEle, '[data-click="submit"], [role="buyNow"]'); this.addToCartEle = SPZCore.Dom.scopedQuerySelector(this.productEle, '[data-click="addToCart"], [role="addToCart"]'); } init_() { this.cycle = this.defaultCycle_; this.optionId_ = this.defaultOption_; this.variantChangeData = this.productInfo; } initVariantId() { const formData = this.getFormData(); if (formData) { if(formData['variant_id'] && formData['variant_id'] !== 'undefined') { this.variantId_ = formData['variant_id']; }else{ this.variantId_ = '' } }else if(this.productInfo?.selected?.id){ this.variantId_ = this.productInfo.selected.id }else{ this.variantId_ = '' } } getFormData() { this.form_ = this.element.closest('form'); if(!this.form_) { return null; } const result = { quantity: 1, }; const formData = new FormData(this.form_); const formDataKey = formData.keys(); for (const key of formDataKey) { result[key] = formData.get(key); } return result; } showPlan_(rerender = false) { if (!this.variantId_) { return this.hide_(); } if (rerender) { this.renderPlan_(); } } renderPlan_() { if (Object.keys(this.currentPlanData).length === 0) { this.showNoPlanTips_(true); this.showNoPaymentTips_(false); this.showRender_(false); if(this.noPlanShowTitle) { this.showTitle_(true); }else{ this.showTitle_(false); } return; } this.showRender_(true); this.showNoPaymentTips_(true); this.showTitle_(true); this.showNoPlanTips_(false); const selectedPlanItem = this.currentPlanData.selling_plan_options.filter((item) => item.selected_enabled === true)[0]; SPZ.whenApiDefined(this.renderEle).then((apis) => { apis.render({ ...this.currentPlanData, originalPrice: this.originalPrice, selectedPlanItem }, false); }); } showTitle_(visible = true) { if(this.titleEle) { this.titleEle.toggleAttribute('hide', !visible); } } showNoPlanTips_(visible = true) { if(this.noPlanTipsEle) { this.noPlanTipsEle.toggleAttribute('hide', !visible); } } showNoPaymentTips_(visible = true) { if(this.noPaymentTipsEle){ this.noPaymentTipsEle.toggleAttribute('hide', !visible); } } showRender_(visible = true) { if(this.renderEle){ this.renderEle.toggleAttribute('hide', !visible); } } hide_() { this.showTitle_(false); this.element.toggleAttribute('hide', true); } show_() { this.showTitle_(true); this.element.toggleAttribute('hide', false); } dispatchSubScribeIdChange_() { this.dispatchEvent_('payment_ec_refresh', { sources: 'subscription' }); } dispatchEvent_(name, data) { const event = SPZUtils.Event.create(this.win, name, data, { bubbles: true }); this.element.dispatchEvent(event); } initActions_() { this.onVariantChange_(); this.element.addEventListener('change', (e) => { if(e.target?.role === 'plugin-subscription-option-selector') { this.onOptionChange_(e.target.value); } if(e.target?.role === 'plugin-subscription-option-card') { this.changeCardOption_(e.target.value); } }); this.registerAction( 'changeCycle', (invocation) => this.onCycleChange_(invocation.args?.selectedOptions[0]) ); this.registerAction('subscribeChange', (invocation) => { this.dispatchSubScribeIdChange_(invocation.args); }); this.addToCartEle.addEventListener('click', () => { this.track_('add_to_cart'); }); this.buyNowEle.addEventListener('click', () => { this.track_('add_to_cart'); }); this.submitInterrapt(); } onVariantChange_() { SPZUtils.Event.listen(document, 'dj.variantChange', (e) => { if(this.variantId_ === e.detail?.selected?.id) { return; } if(!this.variantId_ && e.detail?.selected?.id) { this.show_(); } this.variantId_ = e.detail.selected?.id; this.optionId_ = this.defaultOption_; this.cycle = this.defaultCycle_; this.variantChangeData = Object.assign(this.variantChangeData, e.detail); this.changeBuyNowText(); this.changeAddToCartText(e.detail); this.showPlan_(true); this.track_('product_view'); }); } onOptionChange_(op, render = true) { this.optionId_ = op; this.changeBuyNowText(); if(render) { this.showPlan_(true); } } onCycleChange_(val, render = true) { const cy = Number(val); if (this.cycleList.includes(cy)) { this.cycle = cy; this.changeBuyNowText(); if(render) { this.showPlan_(true); } } } submitInterrapt() { if(!this.form_) { window.djInterceptors.request.use((config) => { if(config?.url === '/api/cart') { return this.addToCartRequestData_(config); } if(config?.url === '/api/checkout/order') { return this.checkoutOrderRequestData_(config); } }) } } addToCartRequestData_(config) { const cartConfig = { ...config }; const hasProductId = cartConfig?.body?.product_id; const hasVariantId = cartConfig?.body?.variant_id; if(cartConfig?.method == 'POST' && hasProductId && hasVariantId) { if (!cartConfig.body.properties) { cartConfig.body.properties = {}; } if(this.currentOption_) { cartConfig.body.properties = { ...cartConfig.body.properties, _selling_plan_option_id: this.currentOption_ } } } return cartConfig; } checkoutOrderRequestData_(config) { const orderConfig = { ...config }; const hasVariantId = orderConfig?.body?.line_items[0]?.variant_id; if(orderConfig?.method == 'POST' && hasVariantId) { if (!orderConfig.body.line_items[0].properties) { orderConfig.body.line_items[0].properties = {}; } if(this.currentOption_) { orderConfig.body.line_items[0].properties = { ...orderConfig.body.line_items[0].properties, _selling_plan_option_id: this.currentOption_ } } } return orderConfig; } changeCardOption_(val) { const oneTimeInputEle = document.querySelector('.one-time [role=plugin-subscription-option-card]'); if(val == 0) { this.onCycleChange_(1, true); oneTimeInputEle.toggleAttribute('disabled',true); return; } oneTimeInputEle.toggleAttribute('disabled',false); this.onCycleChange_(2, false); this.onOptionChange_(val, true); } getContentChild_(el) { if(el) { const childs = el.childNodes; for (let i = childs.length - 1; i >= 0; i--) { const child = childs[i]; if (child.getAttribute && child.getAttribute('role') === 'content') { return child; } } } return; } changeBuyNowText() { let buyNowText = currentI18nProduct.buy_now; let subscribeText = "Subscribe"; const contentEle = this.getContentChild_(this.buyNowEle); // 1. 有role="content"的元素,替换role="content"的元素 if(contentEle) { contentEle.innerHTML = this.isCycleSubscribe ? subscribeText : buyNowText; }else{ // 2. 不存在role="content"的元素,直接替换文本 if(!this.buyNowEle) { return; } this.buyNowEle.innerHTML = this.isCycleSubscribe ? this.buyNowEle.innerHTML.replace(buyNowText, subscribeText) : this.buyNowEle.innerHTML.replace(subscribeText, buyNowText); } } changeAddToCartText(data) { const contentEle = this.getContentChild_(this.addToCartEle); // 1. 有role="content"的元素,移除role不等于content和loading的元素 if(contentEle) { const addToCartChilds = this.addToCartEle.childNodes; for (let i = addToCartChilds.length - 1; i >= 0; i--) { const child = addToCartChilds[i]; // 移除元素(role不等于content和loading) if (typeof child.getAttribute !== 'function' || (child.getAttribute('role') !== 'content' && child.getAttribute('role') !== 'loading')) { this.addToCartEle.removeChild(child); } } }else{ // 2. 不存在role="content"的元素,直接替换文本 const addToCartText = this.getAddToCartText_(data); this.addToCartEle.innerHTML = addToCartText; } } getAddToCartText_(data) { const selected = data?.selected; const productAvailable = data?.product?.available; const variantAvailable = data?.selected?.available; const variantId = data?.selected?.id; let text = currentI18nProduct.add_to_cart; if (!productAvailable) { text = currentI18nProduct.sold_out; } if (selected && !variantId) { text = currentI18nProduct.unavailable; } if (productAvailable && selected && variantId && !variantAvailable) { text = currentI18nProduct.sold_out; } return text; } track_(eventName, optionSelected = this.isCycleSubscribe) { if(window && window.sa && typeof window.sa.registerExtraInfo === 'function') { window.sa.registerExtraInfo(eventName, { function_name: "product_subscription", module: "apps", option_selected: optionSelected }, optionSelected === null); } } } SPZ.defineElement('spz-custom-subscription', SpzCustomSubscription);
Purchase options:
One-Time Purchase
$24.90
Subscribe & Save 20%
$24.90 $19.92
Delivery frequency
Skip, modify or cancel any time
Quantity
const getPluginI18nMessages = (message, replaceObj = {}) => { const lang = document.documentElement.lang || "en-US"; const [form, key] = message.split('.') let text = window.payment_plugin_message['en-US'][form][key]; if (window.payment_plugin_message[lang][form].hasOwnProperty(key)) { text = window.payment_plugin_message[lang][form][key]; } Object.keys(replaceObj).forEach(key => { text = text.replace(new RegExp(`\{${key}\}`, 'gi'), replaceObj[key]); }) return text; } const zhCN = { ec: { not_active_channel: "请到收款设置中{channelName}或在「快捷支付按钮」设置中选择其他的服务提供方,否则按钮将无法展示", not_support_theme: "当前主题不支持添加「快捷支付按钮」", more_button: "更多支付方式", skeleton_layer_tips_title: "快捷支付按钮", skeleton_layer_tips_content: "请点击左侧列表中的「快捷支付按钮」,在设置页面开启想要的展示的支付按钮", mock_tips: "快捷支付按钮是否展示还取决于买家使用的浏览器以及商品的货币、金额", not_find_form_tips: "快捷支付按钮组件仅支持配置到商品详情卡片内", legacy_discontinued_tips: "{channelName} 已下架,买家端将不再展示该按钮", } }; const zhTW = { ec: { not_active_channel: "请到收款设置中{channelName}或在「快捷支付按钮」设置中选择其他的服务提供方,否则按钮将无法展示", not_support_theme: "当前主题不支持添加「快捷支付按钮」", more_button: "更多付款方式", } }; const arSA = { ec: { not_active_channel: "Please activate {channelName} on B Admin or select another provider in the「Express checkout button」 settings, otherwise it will not display.", not_support_theme: "This Theme doesn't support adding「Express checkout button」", more_button: "المزيد من خيارات الدفع", } }; const deDE = { ec: { not_active_channel: "Please activate {channelName} on B Admin or select another provider in the「Express checkout button」 settings, otherwise it will not display.", not_support_theme: "This Theme doesn't support adding「Express checkout button」", more_button: "Weitere Bezahlmöglichkeiten", } }; const esES = { ec: { not_active_channel: "Please activate {channelName} on B Admin or select another provider in the「Express checkout button」 settings, otherwise it will not display.", not_support_theme: "This Theme doesn't support adding「Express checkout button」", more_button: "Más opciones de pago", } }; const frFR = { ec: { not_active_channel: "Please activate {channelName} on B Admin or select another provider in the「Express checkout button」 settings, otherwise it will not display.", not_support_theme: "This Theme doesn't support adding「Express checkout button」", more_button: "Plus d'options de paiement", } }; const idID = { ec: { not_active_channel: "Please activate {channelName} on B Admin or select another provider in the「Express checkout button」 settings, otherwise it will not display.", not_support_theme: "This Theme doesn't support adding「Express checkout button」", more_button: "Opsi pembayaran lainnya", } }; const itIT = { ec: { not_active_channel: "Please activate {channelName} on B Admin or select another provider in the「Express checkout button」 settings, otherwise it will not display.", not_support_theme: "This Theme doesn't support adding「Express checkout button」", more_button: "Altre opzioni di pagamento", } }; const jaJP = { ec: { not_active_channel: "Please activate {channelName} on B Admin or select another provider in the「Express checkout button」 settings, otherwise it will not display.", not_support_theme: "This Theme doesn't support adding「Express checkout button」", more_button: "その他の支払いオプション", } }; const koKR = { ec: { not_active_channel: "Please activate {channelName} on B Admin or select another provider in the「Express checkout button」 settings, otherwise it will not display.", not_support_theme: "This Theme doesn't support adding「Express checkout button」", more_button: "더 많은 결제 옵션", } }; const enUS = { ec: { not_active_channel: "Please activate {channelName} on B Admin or select another provider in the「Express checkout button」 settings, otherwise it will not display.", not_support_theme: "This Theme doesn't support adding「Express checkout button」", more_button: "More payment options", skeleton_layer_tips_title: "Express Checkout Button", skeleton_layer_tips_content: "Please click the「Express checkout button」on the block list,then you could enable the payment option you want to display in settings.", mock_tips: "Whether the Express checkout button is displayed also depends on the browser used by the buyer and the currency and amount of the product.", not_find_form_tips: "Express Checkout Button could only be added to Product details block.", legacy_discontinued_tips: "{channelName} has been deprecated and will no longer be displayed to buyers.", } }; const nlNL = { ec: { not_active_channel: "Please activate {channelName} on B Admin or select another provider in the「Express checkout button」 settings, otherwise it will not display.", not_support_theme: "This Theme doesn't support adding「Express checkout button」", more_button: "Meer betalingsmogelijkheden", } }; const plPL = { ec: { not_active_channel: "Please activate {channelName} on B Admin or select another provider in the「Express checkout button」 settings, otherwise it will not display.", not_support_theme: "This Theme doesn't support adding「Express checkout button」", more_button: "Więcej Opcji Płatności", } }; const ptPT = { ec: { not_active_channel: "Please activate {channelName} on B Admin or select another provider in the「Express checkout button」 settings, otherwise it will not display.", not_support_theme: "This Theme doesn't support adding「Express checkout button」", more_button: "Mais opções de pagamento", } }; const ruRU = { ec: { not_active_channel: "Please activate {channelName} on B Admin or select another provider in the「Express checkout button」 settings, otherwise it will not display.", not_support_theme: "This Theme doesn't support adding「Express checkout button」", more_button: "Другие варианты оплаты", } }; const thTH = { ec: { not_active_channel: "Please activate {channelName} on B Admin or select another provider in the「Express checkout button」 settings, otherwise it will not display.", not_support_theme: "This Theme doesn't support adding「Express checkout button」", more_button: "ตัวเลือกการชำระเงินเพิ่มเติม", } }; window.payment_plugin_message = { getPluginI18nMessages, "zh-CN": zhCN, "zh-TW": zhTW, "ar-SA": arSA, "de-DE": deDE, "es-ES": esES, "fr-FR": frFR, "id-ID": idID, "it-IT": itIT, "ja-JP": jaJP, "ko-KR": koKR, "en-US": enUS, "nl-NL": nlNL, "pl-PL": plPL, "pt-PT": ptPT, "ru-RU": ruRU, "th-TH": thTH, } document.dispatchEvent(new CustomEvent('payment_plugin_message_reader'));
try { const dom = document.getElementById('pm-payment-express-button-1539149753700-9'); dom.i18n = window?.payment_plugin_message?.getPluginI18nMessages; if (dom.i18n) { document.dispatchEvent(new CustomEvent('payment_ec_core_ready', { detail: { i18n: true } })) } else { document.addEventListener('payment_plugin_message_reader', () => { dom.i18n = window?.payment_plugin_message?.getPluginI18nMessages; document.dispatchEvent(new CustomEvent('payment_ec_core_ready', { detail: { i18n: true } })) }, {once: true}); } } catch (e) { } // 通用工具方法 try { const dom = document.getElementById('pm-payment-express-button-1539149753700-9') const ROOT_URL = (C_SETTINGS && C_SETTINGS.routes && C_SETTINGS.routes.root) || ''; const eventListeners = {}; const commonUtils = function () { return { getProduct() { const productJson = document.querySelector('#product-json'); if (productJson?.textContent) { return JSON.parse(productJson.textContent); } if (window.jQuery) { const $product = window.jQuery?.(document)?.data('djproduct'); const productData = JSON.parse(JSON.stringify($product || {})); return productData || {}; } return {}; }, isChrome() { return navigator?.userAgent?.indexOf('Chrome') > -1 || navigator?.userAgent?.indexOf('CriOS') > -1; }, isSafari() { let userAgentString = navigator.userAgent; let chromeAgent = userAgentString.indexOf('Chrome') > -1 || navigator?.userAgent?.indexOf('CriOS') > -1; let safariAgent = userAgentString.indexOf('Safari') > -1; if (chromeAgent && safariAgent) { safariAgent = false; } return safariAgent; }, isPreview() { return !!window?.C_EDITING_SETTINGS?.oseid; }, multiply(a, b) { const precision = 2; // 保留两位小数 return Number((a * b).toFixed(precision)); }, loadScript(fnReady, id, src, datasets, onError, attributeConfig = {}) { const sdkDomId = id + '-sdk'; const loadedEventName = `${id}-loaded`; if (fnReady()) { return Promise.resolve({id: true}); } const existingScript = document.getElementById(sdkDomId); if (existingScript) { return new Promise((resolve) => { const handleLoaded = () => { if (fnReady()) { resolve({id: true}); } else { resolve({id: false}); onError && onError(); } }; if (existingScript.dataset.loaded === 'true') { handleLoaded(); } else { window.addEventListener(loadedEventName, handleLoaded, {once: true}); } }); } return new Promise((resolve) => { const s = document.createElement('script'); s.id = sdkDomId; s.src = src; s.defer = true; if (datasets) { Object.keys(datasets).map((item) => { s.dataset[item] = datasets[item]; }); } s.onload = function () { s.dataset.loaded = 'true'; window.dispatchEvent(new CustomEvent(loadedEventName)); resolve({id: true}); }; s.onerror = function () { resolve({id: false}); onError && onError(); }; Object.keys(attributeConfig).forEach((key) => { s.setAttribute(key, attributeConfig[key]); }); document.head.appendChild(s); }); }, track(eventName, data) { window.sa && window?.sa?.track('pm_' + eventName, JSON.parse(JSON.stringify(data))); }, getExtUrl(name) { const url = document.cookie.match(new RegExp('\\b' + name.replace(/_/g, '-') + '-(v[s0-9]+)')); if (url && url[1]) { return `${name}.${url[1]}.js`; } else { return window?.exts?.[name]; } }, req: { post: async (url, data = {}) => { try { const response = await fetch(req.ROOT_URL + url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, ...data, body: JSON.stringify(data.body), }); return await response.json() } catch (error) { throw new Error('post request error' + error); } }, get: async (url, data = {}) => { try { const response = await fetch(ROOT_URL + url); return await response.json() } catch (error) { throw new Error('get request error' + error); } } }, debounce(fn, wait) { let timeout = null; return function () { if (timeout !== null) { clearTimeout(timeout); } timeout = setTimeout(function () { fn.apply(this, arguments); }, wait); } }, delayCallback(cb) { window.requestIdleCallback ? requestIdleCallback(cb, {timeout: 50}) : setTimeout(cb, 50); }, loadFilly(tag, cb, trackEcPerfFn) { if (!tag) { return } const fireLoaded = function() { if (trackEcPerfFn && !performance.getEntriesByName('ec_filly_load_end').length) { performance.mark('ec_filly_load_end'); const fillyStart = performance.getEntriesByName('ec_filly_load_start')[0]; const channels = window?.C_SETTINGS?.payment_settings?.dynamic_config?.express_checkout_config?.express_channels || []; trackEcPerfFn({ eventName: 'ec_perf_filly_load_end', extraData: { filly_load_time: fillyStart ? performance.now() - fillyStart.startTime : null, payment_channel: channels.join(',') }, eventType: 'card_expose' }); } cb && cb(); }; if (window.FillyEC) { fireLoaded(); return; } const existing = document.querySelector('script[src*="' + tag + '"]'); if (existing) { existing.addEventListener('load', fireLoaded, {once: true}); return; } const script = document.createElement('script'); script.type = 'text/javaScript'; script.src = `//static.staticdj.com/${tag}`; script.onload = fireLoaded; document.getElementsByTagName('head')[0].appendChild(script); }, ecEvent: { on: (eventName, listener, useCapture) => { eventListeners[eventName] = listener; window.addEventListener(eventName, listener, useCapture); }, emit: (eventName, data) => window.dispatchEvent(new CustomEvent(eventName, {detail: data})), }, trackEcPerf({ eventName, extraData, eventType }) { const skeletonStart = performance.getEntriesByName('ec_skeleton_start')[0]; const sinceSkeletonStart = skeletonStart ? performance.now() - skeletonStart.startTime : null; window.sa && window.sa.track('function_expose', { function_name: 'payment_platform', plugin_name: 'payment_platform', business_type: 'product_payment', module: 'payment', module_type: 'payment_platform', event_type: eventType || 'expose', event_developer: 'cartoon', event_info: JSON.stringify( Object.assign( { process_name: eventName, process_type: 'perf', since_skeleton: sinceSkeletonStart }, extraData || {} ) ) }); } } } dom.commonUtilsFn = commonUtils; document.dispatchEvent(new CustomEvent('payment_ec_core_ready', { detail: { commonUtils: true } })) } catch (e) { } // 核心数据 try { const dom = document.getElementById('pm-payment-express-button-1539149753700-9'); const coreData = function () { const {getProduct} = dom.commonUtils; let productDetail = getProduct(); let productPrice = productDetail?.selected?.price || 24.9; const shopCurrencyCode = "USD"; const expressCheckoutList = { sdkErrorList: [], paymentChannelList: [], disabledChannelList: [], showChannelList: [], blockChannelList: [], extraChannelList: [], }; const channelType = { googlepay: ['shoplazzagoogle'], applepay: ['shoplazzaapple'], credit: ['paypal'] }; const ecGlobalVarEnums = { paypal: 'pluginPaypalEC' }; const providerEnums = { SHOPLAZZA: 'shoplazza', STRIPE: 'stripe', PAYPAL: 'paypal' }; const channelEnums = { SHOPLAZZA_GOOGLE: 'shoplazzagoogle', SHOPLAZZA_APPLE: 'shoplazzaapple', STRIPE_GOOGLE: 'stripegoogle', STRIPE_APPLE: 'stripeapple', PAYPAL: 'paypal' }; const channelThemeConfig = { [channelEnums.PAYPAL]: { default: { url: 'oss/operation/f557c83808e1cd456411170286a1ea95.svg', classList: ['paypal-card'], }, }, [channelEnums.SHOPLAZZA_GOOGLE]: { light: { url: 'oss/operation/778afb93da43adf75bdc80b078e5d4fd.svg', classList: ['googlepay-light'], }, dark: { url: 'oss/operation/e53180c224f0b0af44b44663775aa930.svg', classList: ['googlepay-dark'], }, }, [channelEnums.SHOPLAZZA_APPLE]: { light: { url: 'oss/operation/dadceb884044e0a9bbfe26c15192f542.svg', classList: ['applepay-light'], }, dark: { url: 'oss/operation/6597f66eac8b0681ebfb75941e8f6f52.svg', classList: ['applepay-dark'], }, }, }; function getContainerDomId() { const domIDSuffix = '-express-button-container'; const prefix = 'pm-'; const formatId = (channel) => `${prefix}${channel}${domIDSuffix}-1539149753700-9`; return { [providerEnums.PAYPAL]: formatId(providerEnums.PAYPAL), [channelEnums.SHOPLAZZA_GOOGLE]: formatId(channelEnums.SHOPLAZZA_GOOGLE), [channelEnums.SHOPLAZZA_APPLE]: formatId(channelEnums.SHOPLAZZA_APPLE), }; } return { ecGlobalVarEnums, providerEnums, channelEnums, productPrice, shopCurrencyCode, getChannelThemeConfig(ecName) { const themeType = window.PaymentEC?.settings?.express_theme_configs?.[ecName]?.theme_type?.toLowerCase() || 'default'; return channelThemeConfig[ecName][themeType] || channelThemeConfig[ecName]['dark']; }, getProductPrice() { return productDetail?.selected?.price; }, getProductDetail() { return productDetail; }, setProductDetail(data) { productDetail = data; }, isRequiresShipping() { return productDetail?.product?.requires_shipping }, getOpenChannelType() { const {paymentChannelList, blockChannelList} = expressCheckoutList const openList = paymentChannelList.filter(item => blockChannelList.includes(item)) || []; return { hasApplepay: openList.filter(item => channelType.applepay.includes(item))?.length > 0, hasGooglepay: openList.filter(item => channelType.googlepay.includes(item))?.length > 0, hasCredit: openList.filter(item => channelType.credit.includes(item))?.length > 0 } }, containerDomId: getContainerDomId(), channel2ProviderEnums: { [channelEnums.PAYPAL]: channelEnums.PAYPAL, [channelEnums.SHOPLAZZA_GOOGLE]: channelEnums.SHOPLAZZA_GOOGLE, [channelEnums.SHOPLAZZA_APPLE]: channelEnums.SHOPLAZZA_APPLE, [channelEnums.STRIPE_GOOGLE]: providerEnums.STRIPE, [channelEnums.STRIPE_APPLE]: providerEnums.STRIPE, }, getExpressCheckoutList() { return expressCheckoutList; }, setShowChannel(showChannelList = []) { expressCheckoutList.showChannelList = showChannelList; return expressCheckoutList; }, setBlockChannel(blockChannelList = []) { expressCheckoutList.blockChannelList = blockChannelList; return expressCheckoutList; }, setPaymentChannelList(paymentChannelList = []) { expressCheckoutList.paymentChannelList = paymentChannelList; return expressCheckoutList; }, setSdkErrorList(paymentChannelList = []) { expressCheckoutList.sdkErrorList = paymentChannelList; return expressCheckoutList; }, setExtraChannelList(extraChannelList = []) { expressCheckoutList.extraChannelList = extraChannelList; return expressCheckoutList; }, setDisabledChannelList(disabledChannelList = []) { expressCheckoutList.disabledChannelList = disabledChannelList; return expressCheckoutList; } } } dom.coreDataFn = coreData; document.dispatchEvent(new CustomEvent('payment_ec_core_ready', { detail: { coreData: true } })) } catch (e) { console.log(e); } // 通用业务数据处理方法 try { const dom = document.getElementById('pm-payment-express-button-1539149753700-9') const businessUtils = function () { const {track, isChrome, isSafari, req, isPreview, multiply, loadScript, trackEcPerf} = dom.commonUtils; const {getProductPrice, containerDomId, ecGlobalVarEnums, channelType, getOpenChannelType} = dom.coreData; const { channelEnums, shopCurrencyCode, isRequiresShipping, getProductDetail, setShowChannel, setBlockChannel, setSdkErrorList, setExtraChannelList, setDisabledChannelList, setPaymentChannelList, getExpressCheckoutList } = dom.coreData; const _businessUtils = { getECConfig: async () => { if (window.PaymentEC?.settings) { return window.PaymentEC?.settings; } // 优先从 C_SETTINGS 获取 express_checkout_config let ecConfig = window?.C_SETTINGS?.payment_settings?.dynamic_config?.express_checkout_config; // 如果 C_SETTINGS 中没有,使用接口请求作为兜底 if (!ecConfig) { const result = await req.get('/api/payment/settings'); ecConfig = result?.settings?.express_checkout_config || {}; } const {blockChannelList} = getExpressCheckoutList(); const filteredExpressChannels = blockChannelList.filter(ecName => ecConfig?.express_channels?.includes(ecName)) || []; setPaymentChannelList(filteredExpressChannels); window.PaymentEC.settings = { ...ecConfig, express_channels: filteredExpressChannels, currencyCode: shopCurrencyCode }; return window.PaymentEC.settings; }, getAttributeConfig(channelInfo) { const {ecGlobalVar, ecName} = channelInfo; const config = { paypal: { 'data-namespace': ecGlobalVar } }; return config[ecName] || {}; }, getThemeFormData() { let themeFormData = {}; const formDOM = dom.closest("form"); if (formDOM) { themeFormData = { note: '', product_id: '', variant_id: '', quantity: 1, properties: {}, }; const formData = new FormData(formDOM); const formDataKey = formData.keys(); for (const key of formDataKey) { const value = formData.get(key); const propertiesKey = key.match(/^properties(?:\.(\w+)$|\[(\w+)\]$)/); if (!propertiesKey) { themeFormData[key] = value; continue; } const objKey = propertiesKey[1] || propertiesKey[2]; themeFormData['properties'] = {...themeFormData['properties'], [objKey]: value}; } } if (isPreview() && (!themeFormData.product_id || !themeFormData.variant_id)) { const previewProduct = (dom.commonUtils.getProduct() || {}).product; if (previewProduct) { themeFormData.product_id = themeFormData.product_id || previewProduct.id; const previewVariant = previewProduct.selected_or_first_available_variant || (previewProduct.variants || [])[0]; themeFormData.variant_id = themeFormData.variant_id || (previewVariant && previewVariant.id); } } return themeFormData; }, getProductFormData() { const themeFormData = _businessUtils.getThemeFormData() return [{ ...themeFormData, note: themeFormData?.note || "", product_id: themeFormData?.product_id || "", variant_id: themeFormData?.variant_id || "", quantity: themeFormData?.quantity || 1, // 与主题确认,只以一个为准,防止form不存在的数据仍被传递 properties: themeFormData?.properties || {}, }] }, getOrderFetchParams(data) { if (!data) { return {}; } return { line_items: data.map((item) => ({ ...item, note: item?.note || "", quantity: item?.quantity || 1, product_id: item?.product_id, variant_id: item?.variant_id, properties: item?.properties, })), refer_info: { source: 'buy_now', }, customer_note: '', }; }, isAllowTheme() { const allowThemeList = ['Nova 2023', 'Dropshiping', 'Geek', 'Hero', 'Eva']; const currentTheme = window?.C_SETTINGS?.theme?.merchant_theme_name; return allowThemeList.includes(currentTheme); }, getSubscriptionIdInit() { let defaultID; const selectSubscriptionEnum = { CLOSE: 1, ACTIVE: 2, } const productDetail = getProductDetail(); const sellingPlan = {"block_style":"1","store_ini_payment_subscription":true,"sku":[],"spu":{"17dffa9c-9dd9-4de4-8cdd-dd0323e319a6":{"variant_selling_plan_enabled":true,"selling_plan_id":"644495718916050167","discount_enabled":true,"selected_selling_plan_option_id":"644817048114826487","cycles":2,"discount_value":"20","discount_type":"percentage","selling_plan_options":[{"selling_plan_option_id":"644817048114826487","interval_count":"1","interval":"month","selected_enabled":true,"discount_value":"20","discount_type":"percentage"}]}}}; if (!sellingPlan || typeof sellingPlan !== "object") { return null; } let sellingItems; if (sellingPlan?.spu?.[productDetail?.product?.id]) { sellingItems = sellingPlan.spu[productDetail?.product?.id] } if (sellingPlan?.sku?.[productDetail?.selected?.id]) { sellingItems = sellingPlan.sku[productDetail?.product?.id] } if (sellingItems?.cycles === selectSubscriptionEnum.ACTIVE && sellingItems?.selected_selling_plan_option_id) { defaultID = sellingItems?.selected_selling_plan_option_id } return defaultID ?? null }, getSubscriptionId() { const formData = _businessUtils.getThemeFormData(); const defaultID = _businessUtils.getSubscriptionIdInit(); console.log(`[paymentEC]订阅信息:form-${formData?.properties?._selling_plan_option_id},默认-${defaultID}`); if (formData?.properties) { return formData?.properties?._selling_plan_option_id } return defaultID ?? null; }, isSubscription() { return !!_businessUtils.getSubscriptionId(); }, isAllowSubscriptionPay(channel) { if (!_businessUtils.isSubscription()) { return true; } return [channelEnums.PAYPAL].includes(channel); }, checkApplePayAvailability(channel) { let available = false; let reason = 'no_session'; const isApplePay = channelType?.applepay?.includes?.(channel); if(!isApplePay){ return true; } if (window.ApplePaySession) { if (window.ApplePaySession.canMakePayments()) { available = true; reason = 'available'; } else { reason = 'cannot_make_payments'; } } trackEcPerf({ eventName: 'ec_applepay_availability_check', extraData: { available: available, reason: reason, layer: 'pike_init', payment_channel: channel }, eventType: 'expose' }); return available; }, getEcLegacyLists() { const dc = window?.C_SETTINGS?.payment_settings?.dynamic_config || {}; const asArray = (v) => (Array.isArray(v) ? v : []); return { ec_legacy_apple_pay_block_store_ids: asArray(dc.ec_legacy_apple_pay_block_store_ids), ec_legacy_google_pay_block_store_ids: asArray(dc.ec_legacy_google_pay_block_store_ids) }; }, isLegacyShopKept(storeIds) { const legacyShopId = String(window?.C_SETTINGS?.shop?.shop_id || ''); return Array.isArray(storeIds) && storeIds.some((id) => !!legacyShopId && String(id).trim() === legacyShopId); }, getLegacyDiscontinuedChannels() { const legacyCfg = _businessUtils.getEcLegacyLists(); const discontinued = []; if (false && !_businessUtils.isLegacyShopKept(legacyCfg?.ec_legacy_google_pay_block_store_ids)) { discontinued.push('Google Pay'); } if (false && !_businessUtils.isLegacyShopKept(legacyCfg?.ec_legacy_apple_pay_block_store_ids)) { discontinued.push('Apple Pay'); } return discontinued; }, blockChannelHandler() { const legacyCfg = _businessUtils.getEcLegacyLists(); const keepApple = _businessUtils.isLegacyShopKept(legacyCfg?.ec_legacy_apple_pay_block_store_ids); const keepGoogle = _businessUtils.isLegacyShopKept(legacyCfg?.ec_legacy_google_pay_block_store_ids); const block_googlePay = keepGoogle && false && "shoplazzagoogle"; const block_applePay = keepApple && false && "shoplazzaapple"; const block_credit = true && "paypal"; const blockChannel = { googlepay: block_googlePay, applepay: block_applePay, credit: block_credit }; if (!isPreview()) { const expressChannels = window?.C_SETTINGS?.payment_settings?.dynamic_config?.express_checkout_config?.express_channels || []; const hasStripe = (keepApple || keepGoogle) && (expressChannels.includes('shoplazzagoogle') || expressChannels.includes('shoplazzaapple')); const {hasApplepay} = getOpenChannelType(); if (hasStripe) { loadScript(() => window.Stripe, 'Stripe', 'https://js.stripe.com/v3/'); } if (hasApplepay) { loadScript(() => { const hasAppURL = document.getElementById('applepay-sdk'); return !!hasAppURL; }, 'applepay', 'https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js'); } } const sortList = ['credit', 'googlepay', 'applepay']; const methodSort = Object.keys(blockChannel).filter(key => blockChannel[key] && key).sort((a, b) => { const indexA = sortList.indexOf(a); const indexB = sortList.indexOf(b); return indexA - indexB; }).map(key => blockChannel[key]); const result = setBlockChannel(methodSort); track('setBlockChannel', result); return result; }, showECButtonHandler() { const { paymentChannelList, sdkErrorList, disabledChannelList, extraChannelList, } = getExpressCheckoutList(); const showChannelList = paymentChannelList.filter((ecName) => !sdkErrorList.includes(ecName) && !disabledChannelList.includes(ecName) && !extraChannelList.includes(ecName)) || []; const result = setShowChannel(showChannelList); track('showECButton', result); return result; }, filterECButtonHandler({type}, cb) { const { paymentChannelList, sdkErrorList, disabledChannelList, extraChannelList, } = getExpressCheckoutList(); const showChannelList = paymentChannelList.filter((ecName) => !sdkErrorList.includes(ecName) && !disabledChannelList.includes(ecName) && !extraChannelList.includes(ecName)) || []; const result = setShowChannel(showChannelList.filter((ecName) => ecName !== type) || []); cb && cb(); track('filterECButton', result); return result; }, loadSDKErrorHandler(type) { const {sdkErrorList} = getExpressCheckoutList(); const result = setSdkErrorList([...sdkErrorList, type]); track('loadSDKError', result); return result; }, extraFilterShowHandler(channel) { const {extraChannelList} = getExpressCheckoutList(); const result = setExtraChannelList(extraChannelList.filter(ecName => ecName !== channel)); track('extraFilterEvent_show', result); return result; }, extraFilterHideHandler(channel) { const {extraChannelList} = getExpressCheckoutList(); const result = setExtraChannelList([...extraChannelList, channel]); track('extraFilterEvent_hide', result); return result; }, disabledChannelListHandler(checkoutData = {}, cb) { const {paymentChannelList} = getExpressCheckoutList(); const productDetail = getProductDetail(); const disabledChannelList = paymentChannelList.filter(ecName => { let mustDisable = false; if(!_businessUtils.checkApplePayAvailability(ecName)){ mustDisable = true; } if (!isRequiresShipping() && ecName !== channelEnums.PAYPAL) { mustDisable = true; } if (!_businessUtils.isAllowSubscriptionPay(ecName)) { mustDisable = true; } if (!productDetail?.selected?.available) { mustDisable = true; } const {payment_due} = checkoutData?.prices; const paymentDueNum = Number(payment_due || 0) * 100; const showFlag = paymentDueNum > 0; return mustDisable || !showFlag; }) const result = setDisabledChannelList(disabledChannelList) result?.disabledChannelList?.forEach(ecName => { cb && cb(ecName); }) track('disabledChannelListEvent', result); }, async getCheckoutData() { const formData = _businessUtils.getProductFormData(); const totalPrice = multiply(getProductPrice(), formData?.[0]?.quantity || 0); return { prices: {payment_due: totalPrice, subtotal_price: totalPrice}, orderParams: _businessUtils.getOrderFetchParams(_businessUtils.getProductFormData()), containerDOMIdEnums: containerDomId, ecGlobalVarEnums } }, } return _businessUtils } dom.businessUtilsFn = businessUtils; document.dispatchEvent(new CustomEvent('payment_ec_core_ready', { detail: { businessUtils: true } })) } catch (e) { } // 通用渲染方法 try { const dom = document.getElementById('pm-payment-express-button-1539149753700-9'); const containerDOM = 'pm-payment-express-button-container-1539149753700-9'; const commonRenderUtils = function () { return { addChildrenDOM(id, allowShow, options = {}) { if (!id) { return; } const paymentEl = document.getElementById(containerDOM); const childrenEL = document.getElementById(id); if (paymentEl && childrenEL) { childrenEL.style.display = allowShow ? 'block' : 'none'; return; } if (paymentEl && !childrenEL) { const dom = document.createElement('div'); dom.id = id; dom.style.display = allowShow ? 'block' : 'none'; if (options?.style) { Object.keys(options?.style).forEach(key => { dom.style[key] = options.style[key]; }) } if (Array.isArray(options?.classList)) { dom.classList.add(...options.classList) } paymentEl.appendChild(dom); } }, removeChildrenDOM(id) { if (!id) { return; } const paymentEl = document.getElementById(containerDOM); const childrenEL = document.getElementById(id); if (paymentEl && childrenEL) { // childrenEL.remove(); childrenEL.style.display = 'none'; } }, mockAddChildrenDOM(id, allowShow, options = {}) { if (!id) { return; } const paymentEl = document.getElementById(containerDOM); const childrenEL = document.getElementById(id); if (paymentEl && childrenEL) { childrenEL.style.display = allowShow ? 'flex' : 'none'; return; } if (paymentEl && !childrenEL) { const dom = document.createElement('div'); dom.id = id; dom.style.display = allowShow ? 'flex' : 'none'; if (options?.style) { Object.keys(options?.style).forEach(key => { dom.style[key] = options.style[key]; }) } if (Array.isArray(options?.classList)) { dom.classList.add(...options.classList) } dom.classList.add('mock-img'); const img = document.createElement('img'); img.src = `//static.staticdj.com/${options?.url}`; dom.appendChild(img); paymentEl.appendChild(dom); } }, resetRenderDOM() { const resetStyleList = [ "pm-payment-express-error-tips-1539149753700-9", "pm-payment-express-more-button-1539149753700-9", "pm-payment-express-mock-tips-1539149753700-9", "pm-payment-express-skeletonLayer-1539149753700-9", ] const resetHtmlList = [ "pm-payment-express-skeletonLayer-title-content-1539149753700-9", "pm-payment-express-skeletonLayer-content-1539149753700-9", "pm-payment-express-mock-tips-1539149753700-9", "pm-payment-express-error-tips-1539149753700-9", "pm-payment-express-button-container-1539149753700-9", "pm-payment-express-more-button-1539149753700-9", ] resetStyleList.forEach(domID => { const content = document.getElementById(domID); if (content) { content.style.display = 'none'; } }) resetHtmlList.forEach(domID => { const content = document.getElementById(domID); if (content) { content.innerHTML = ''; } }) } } } dom.commonRenderUtilsFn = commonRenderUtils; document.dispatchEvent(new CustomEvent('payment_ec_core_ready', { detail: { commonRenderUtils: true } })) } catch (e) { } // 错误提示渲染 try { const dom = document.getElementById('pm-payment-express-button-1539149753700-9'); const renderTipsUtils = function () { const {i18n} = dom; const {isPreview} = dom.commonUtils; const {channelEnums} = dom.coreData; return { showChannelNotOpenTips(channelList) { const tipsDom = document.getElementById('pm-payment-express-error-tips-1539149753700-9'); if (!isPreview()) { return; } if (!tipsDom) { return; } tipsDom.style.display = channelList.length > 0 ? 'block' : 'none'; const channelName = { [channelEnums.SHOPLAZZA_GOOGLE]: "ShoplazzaPayments - GooglePay", [channelEnums.SHOPLAZZA_APPLE]: "ShoplazzaPayments - ApplePay", [channelEnums.PAYPAL]: "PayPal", } channelList.forEach(ecName => { const id = `pm-payment-express-error-tips-1539149753700-9-${ecName}`; const hasDom = document.getElementById(id) if (!hasDom) { const dom = document.createElement('div'); dom.id = id; dom.innerHTML = i18n('ec.not_active_channel', {channelName: channelName[ecName]}); tipsDom.appendChild(dom); } }) }, disabledThemTips() { const tipsDom = document.getElementById('pm-payment-express-error-tips-1539149753700-9'); if (!isPreview()) { return; } if (!tipsDom) { return; } tipsDom.style.display = 'block'; const id = 'pm-payment-express-error-tips-1539149753700-9-theme'; const hasDom = document.getElementById(id); if (!hasDom) { const dom = document.createElement('div'); dom.id = id; dom.innerHTML = i18n('ec.not_support_theme'); tipsDom.appendChild(dom); } }, notFindFormTips() { const tipsDom = document.getElementById('pm-payment-express-error-tips-1539149753700-9'); if (!isPreview()) { return; } if (!tipsDom) { return; } tipsDom.style.display = 'block'; const id = 'pm-payment-express-error-tips-1539149753700-9-theme'; const hasDom = document.getElementById(id); if (!hasDom) { const dom = document.createElement('div'); dom.id = id; dom.innerHTML = i18n('ec.not_find_form_tips'); tipsDom.appendChild(dom); } }, showSkeletonLayerTips() { const skeletonLayerDOMId = 'pm-payment-express-skeletonLayer-1539149753700-9'; const skeletonLayerDOM = document.getElementById(skeletonLayerDOMId); const titleDOM = document.getElementById('pm-payment-express-skeletonLayer-title-content-1539149753700-9'); const contentDOM = document.getElementById('pm-payment-express-skeletonLayer-content-1539149753700-9'); if (!skeletonLayerDOM || !titleDOM || !contentDOM) { return; } skeletonLayerDOM.style.display = 'block'; titleDOM.innerHTML = i18n('ec.skeleton_layer_tips_title'); contentDOM.innerHTML = i18n('ec.skeleton_layer_tips_content'); }, showMockTips() { const tipsDOM = document.getElementById('pm-payment-express-mock-tips-1539149753700-9'); if (!tipsDOM) { return; } tipsDOM.style.display = 'block'; tipsDOM.innerHTML = i18n('ec.mock_tips'); }, showLegacyDiscontinuedTips(channelNames) { const tipsDom = document.getElementById('pm-payment-express-error-tips-1539149753700-9'); if (!isPreview()) { return; } if (!tipsDom || !channelNames?.length) { return; } tipsDom.style.display = 'block'; channelNames.forEach((channelName) => { const id = `pm-payment-express-error-tips-1539149753700-9-discontinued-${channelName.replace(/\s/g, '')}`; const hasDom = document.getElementById(id); if (!hasDom) { const dom = document.createElement('div'); dom.id = id; dom.innerHTML = i18n('ec.legacy_discontinued_tips', {channelName}); tipsDom.appendChild(dom); } }); } } } dom.renderTipsUtilsFn = renderTipsUtils; document.dispatchEvent(new CustomEvent('payment_ec_core_ready', { detail: { renderTipsUtils: true } })) } catch (e) { } // 更多信息渲染 try { const dom = document.getElementById('pm-payment-express-button-1539149753700-9'); const moreDOM = document.getElementById('pm-payment-express-more-button-1539149753700-9'); const moreButtonConfig = { firstClick: true, maxSize: isNaN(1) ? 1 : 1 }; const renderMoreUtils = function () { const {i18n} = dom; const {getExpressCheckoutList, channelEnums} = dom.coreData; function moreButtonEvent(cb) { if (!moreDOM) { return; } moreDOM.style.display = 'none'; moreButtonConfig.firstClick = false; cb && cb(); } return { getMoreButtonConfig() { return moreButtonConfig }, showMoreButton(cb) { if (!moreDOM) { return; } let {showChannelList} = getExpressCheckoutList(); const showLength = showChannelList.length; const {firstClick, maxSize} = moreButtonConfig; moreDOM.style.display = (firstClick && showLength > 0 && showLength > maxSize) ? 'block' : 'none'; moreDOM.innerHTML = i18n('ec.more_button'); moreDOM.onclick = () => moreButtonEvent(cb); }, } } dom.renderMoreUtilsFn = renderMoreUtils; document.dispatchEvent(new CustomEvent('payment_ec_core_ready', { detail: { renderMoreUtils: true } })) } catch (e) { } try { const dom = document.getElementById('pm-payment-express-button-1539149753700-9'); function start() { const { getExtUrl, loadFilly, delayCallback, ecEvent, track, loadScript, debounce, trackEcPerf } = dom.commonUtils; const { blockChannelHandler, getAttributeConfig, showECButtonHandler, filterECButtonHandler, loadSDKErrorHandler, extraFilterShowHandler, extraFilterHideHandler, disabledChannelListHandler, getECConfig, isAllowTheme, getCheckoutData, getThemeFormData } = dom.businessUtils; const {addChildrenDOM, removeChildrenDOM} = dom.commonRenderUtils; const {getMoreButtonConfig, showMoreButton} = dom.renderMoreUtils; const { ecGlobalVarEnums, providerEnums, channelEnums, getExpressCheckoutList, getProductPrice, getProductDetail, setProductDetail, containerDomId, channel2ProviderEnums, getChannelThemeConfig } = dom.coreData; const expressChannels = window?.C_SETTINGS?.payment_settings?.dynamic_config?.express_checkout_config?.express_channels || []; if (!performance.getEntriesByName('ec_skeleton_start').length) { performance.mark('ec_skeleton_start'); trackEcPerf({ eventName: 'ec_perf_skeleton_start', extraData: { page_start: performance.now(), payment_channel: expressChannels.join(',') }, eventType: 'card_expose' }); } function getFilly() { if (!performance.getEntriesByName('ec_filly_load_start').length) { performance.mark('ec_filly_load_start'); trackEcPerf({ eventName: 'ec_perf_filly_load_start', extraData: { payment_channel: expressChannels.join(',') }, eventType: 'card_expose' }); } const fillyTag = getExtUrl('filly'); if (fillyTag) { loadFilly(fillyTag, init, trackEcPerf); } } function extraFilterEvent(e) { const {channel, domId, allowShow} = e?.detail || {}; if (channel && domId) { if (allowShow) { extraFilterShowHandler(channel); const container = document.getElementById(domId); if (container) { container.style.border = 'none'; container.style.backgroundColor = 'transparent'; } } else { extraFilterHideHandler(channel); filterECButtonHandler({type: channel}, () => removeChildrenDOM(domId) ); } renderEC(); } } const renderEC = () => { showECButtonHandler(); const expressCheckoutList = getExpressCheckoutList(); var {showChannelList} = expressCheckoutList; const {firstClick, maxSize} = getMoreButtonConfig(); if (showChannelList.length === 0) { showMoreButton(renderEC); } showChannelList.forEach((ecName, index) => { const disableShow = firstClick && index >= maxSize; const containerId = containerDomId[channel2ProviderEnums[ecName]]; addChildrenDOM(containerId, !disableShow, getChannelThemeConfig(ecName)); showMoreButton(renderEC); }); } const loadErrorEvent = (type) => { const domID = containerDomId[type]; if (!domID) { return; } loadSDKErrorHandler(type); filterECButtonHandler({type}, () => removeChildrenDOM(domID) ); showMoreButton(renderEC); }; async function loadEC() { const themeFormData = getThemeFormData?.() || {}; if (!themeFormData?.product_id || !themeFormData?.variant_id) { return; } const ecConfig = await getECConfig(); const expressCheckoutList = getExpressCheckoutList(); track('loadEC', expressCheckoutList); if (ecConfig) { const checkoutData = await getCheckoutData(); disabledChannelListHandler(checkoutData, (ecName) => { filterECButtonHandler({type: ecName}, () => removeChildrenDOM(containerDomId[channel2ProviderEnums[ecName]]) ); }); renderEC(); window.PaymentEC.handleEcPluginsLoad = ({ channelInfos = [], loadedCbFn = () => { } }) => { const expressCheckoutLoadList = []; const { showChannelList } = getExpressCheckoutList(); const hasStripeChannels = channelInfos.some(info => info.ecGlobalVar === 'Stripe'); const stripeExpressChannels = (window?.C_SETTINGS?.payment_settings?.dynamic_config?.express_checkout_config?.express_channels || []) .filter(ch => ['shoplazzagoogle'].includes(ch)); if (hasStripeChannels && !performance.getEntriesByName('ec_stripe_load_start').length) { performance.mark('ec_stripe_load_start'); trackEcPerf({ eventName: 'ec_perf_stripe_load_start', extraData: { payment_channel: stripeExpressChannels.join(',') }, eventType: 'card_expose' }); } channelInfos.map((channelInfo) => { const {ecGlobalVar, ecName = '', sdkPath = '', datasets, fnReady, sdkReadyCb, sdkName} = channelInfo; let hasContainer = document.getElementById(containerDomId[ecName]); if (!hasContainer) { return; } const scriptName = sdkName || ecName === 'paypal' ? ecGlobalVar : ecName; performance.mark('ec_third_party_sdk_load_start'); trackEcPerf({ eventName: 'ec_third_party_sdk_load_start', extraData: { payment_channel: ecName, sdk_path: sdkPath }, eventType: 'expose' }); const attributeConfig = getAttributeConfig(channelInfo) || {}; const loadPromise = loadScript(fnReady ? fnReady : () => window[ecGlobalVar], scriptName, sdkPath, datasets, () => { loadErrorEvent(ecName); }, attributeConfig).then(() => { if (ecGlobalVar === 'Stripe' && !performance.getEntriesByName('ec_stripe_load_end').length) { performance.mark('ec_stripe_load_end'); const stripeStart = performance.getEntriesByName('ec_stripe_load_start')[0]; trackEcPerf({ eventName: 'ec_perf_stripe_load_end', extraData: { stripe_load_time: stripeStart ? performance.now() - stripeStart.startTime : null, payment_channel: stripeExpressChannels.join(',') }, eventType: 'card_expose' }); } performance.mark('ec_third_party_sdk_load_end'); const sdkStartLoadTime = performance.getEntriesByName('ec_third_party_sdk_load_start')[0]; trackEcPerf({ eventName: 'ec_third_party_sdk_load_end', extraData: { sdk_load_time: sdkStartLoadTime ? performance.now() - sdkStartLoadTime.startTime : null, payment_channel: ecName, sdk_path: sdkPath }, eventType: 'expose' }); sdkReadyCb?.({ ...checkoutData, loadSpent: 0, paymentExpressChannels: showChannelList }); }); expressCheckoutLoadList.push(loadPromise); }); const startLoadSDKTime = Date.now(); Promise.all(expressCheckoutLoadList).then(() => { const finishLoadSDKTime = Date.now(); const loadSpent = finishLoadSDKTime - startLoadSDKTime; loadedCbFn({...checkoutData, loadSpent, paymentExpressChannels: showChannelList}); }); }; // 通知外部数据变更 ecEvent.emit('tc_payment_ec_data_change', { ecGlobalVarEnums, containerDOMIdEnums: containerDomId }); } } const loadECDebounce = debounce(loadEC, 300) async function refreshEC(data = {}, sources) { if (!sources) { console.warn('[paymentEC]hide: sources is null'); return; } if (data?.detail?.selected?.price) { setProductDetail(data?.detail) } loadECDebounce(); } function init() { ecEvent.on('shoplazza_express_channels_change', extraFilterEvent, false); ecEvent.on('shoplazza_express_channels_change_ready', extraFilterEvent, false); if (typeof window.PaymentEC === 'object') { window.PaymentEC.getCheckoutData = getCheckoutData; } document.addEventListener('dj.variantChange', (data) => refreshEC(data, 'variantChange')); document.addEventListener('payment_ec_refresh', (data) => refreshEC(data, data?.detail?.sources)); refreshEC({}, 'init'); } if (isAllowTheme()) { blockChannelHandler(); // 提前加载 filly:如果 DOM 已经准备好,立即加载;否则在 DOMContentLoaded 时加载 // 使用 DOMContentLoaded 替代 load 事件,提前加载时机(比 window.load 更早) if (document.readyState === 'complete' || document.readyState === 'interactive') { getFilly(); } else if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', getFilly, {once: true}); } else { // 兜底:如果 readyState 未知,立即加载 getFilly(); } } } dom.startFn = start; document.dispatchEvent(new CustomEvent('payment_ec_core_ready', { detail: { start: true } })) } catch (e) { console.log(e); } // 预览模式 try { const dom = document.getElementById('pm-payment-express-button-1539149753700-9'); function start() { const {track} = dom.commonUtils; const {showMoreButton, getMoreButtonConfig} = dom.renderMoreUtils; const { showECButtonHandler, getECConfig, blockChannelHandler, getLegacyDiscontinuedChannels, isAllowTheme, getThemeFormData } = dom.businessUtils; const { disabledThemTips, showChannelNotOpenTips, showSkeletonLayerTips, showMockTips, notFindFormTips, showLegacyDiscontinuedTips } = dom.renderTipsUtils; const {mockAddChildrenDOM, resetRenderDOM} = dom.commonRenderUtils; const { channelEnums, getChannelThemeConfig, getExpressCheckoutList, getOpenChannelType } = dom.coreData; const mockDomId = { [channelEnums.PAYPAL]: channelEnums.PAYPAL, [channelEnums.SHOPLAZZA_GOOGLE]: channelEnums.SHOPLAZZA_GOOGLE, [channelEnums.SHOPLAZZA_APPLE]: channelEnums.SHOPLAZZA_APPLE, [channelEnums.STRIPE_GOOGLE]: channelEnums.STRIPE_GOOGLE, [channelEnums.STRIPE_APPLE]: channelEnums.STRIPE_APPLE, } const renderNotOpenTips = () => { const {blockChannelList, paymentChannelList} = getExpressCheckoutList(); const notOpenChannel = blockChannelList.filter(ecName => !paymentChannelList.includes(ecName)); showChannelNotOpenTips(notOpenChannel); } const renderMockTips = () => { const {hasApplepay, hasGooglepay} = getOpenChannelType(); if (hasApplepay || hasGooglepay) { showMockTips(); } } const renderEC = () => { showECButtonHandler(); const {showChannelList} = getExpressCheckoutList(); const {firstClick, maxSize} = getMoreButtonConfig(); if (showChannelList.length === 0) { showMoreButton(renderEC); } showChannelList.forEach((ecName, index) => { const disableShow = firstClick && index >= maxSize; mockAddChildrenDOM(mockDomId[ecName], !disableShow, getChannelThemeConfig(ecName)); showMoreButton(renderEC); }); } async function loadEC() { const date = new Date().getTime(); dom.loadEC_timestamp = date const ecConfig = await getECConfig(); if (date !== dom.loadEC_timestamp) { return; } const expressCheckoutList = getExpressCheckoutList(); track('preview-loadEC', expressCheckoutList); resetRenderDOM(); // 初始化时没有事件推送 if (ecConfig) { renderNotOpenTips(); showLegacyDiscontinuedTips(getLegacyDiscontinuedChannels()); renderEC(); renderMockTips(); } } const init = () => { blockChannelHandler(); const {blockChannelList} = getExpressCheckoutList(); if (!isAllowTheme()) { disabledThemTips() return; } const themeFormData = getThemeFormData?.() || {}; if (!themeFormData?.product_id || !themeFormData?.variant_id) { notFindFormTips(); return; } if (blockChannelList.length > 0) { loadEC(); } else { showLegacyDiscontinuedTips(getLegacyDiscontinuedChannels()); showSkeletonLayerTips() } } init(); } dom.mockStartFn = start; document.dispatchEvent(new CustomEvent('payment_ec_core_ready', { detail: { start: true } })) } catch (e) { } try { const dom = document.getElementById('pm-payment-express-button-1539149753700-9'); window.PaymentEC = {} const delayCallback = (cb) => { window.requestIdleCallback ? requestIdleCallback(cb, {timeout: 50}) : setTimeout(cb, 50); } const checkReady = function (data) { const { i18n, commonUtilsFn, coreDataFn, businessUtilsFn, commonRenderUtilsFn, renderTipsUtilsFn, renderMoreUtilsFn, startFn, mockStartFn } = dom let readyData = { commonUtils: !!(commonUtilsFn) || false, coreData: !!(coreDataFn) || false, businessUtils: !!(businessUtilsFn) || false, commonRenderUtils: !!(commonRenderUtilsFn) || false, renderTipsUtils: !!(renderTipsUtilsFn) || false, renderMoreUtils: !!(renderMoreUtilsFn) || false, start: !!(startFn) || false, mockStart: !!(mockStartFn) || false, i18n: !!(i18n) || false } if (data?.detail) { Object.keys(data.detail).forEach(key => { readyData[key] = data.detail[key] }) } let isReady = true; Object.keys(readyData).forEach(key => { if (!readyData[key]) { isReady = false } }) return isReady } const readyFn = () => { if (!checkReady()) { return; } document.removeEventListener('payment_ec_core_ready', readyFn); dom.commonUtils = dom.commonUtilsFn(); dom.coreData = dom.coreDataFn(); dom.businessUtils = dom.businessUtilsFn(); dom.commonRenderUtils = dom.commonRenderUtilsFn(); dom.renderTipsUtils = dom.renderTipsUtilsFn(); dom.renderMoreUtils = dom.renderMoreUtilsFn(); const productData = dom?.commonUtils?.getProduct?.() || {}; if (JSON.stringify(productData) === '{}') { return; } if (dom?.commonUtils?.isPreview()) { dom.mockStartFn() } else { dom.startFn(); } } const init = () => { if (checkReady()) { readyFn(); } else { document.addEventListener('payment_ec_core_ready', readyFn) } } // 使用 DOMContentLoaded 替代 window.load,提前初始化时机(与 filly 加载逻辑一致) if (document.readyState === 'complete' || document.readyState === 'interactive') { delayCallback(init); } else if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => delayCallback(init), {once: true}); } else { // 兜底:如果 readyState 未知,立即执行 delayCallback(init); } } catch (e) { } /** @private {string} */ class SpzCustomAnchorScroll extends SPZ.BaseElement { static deferredMount() { return false; } constructor(element) { super(element); /** @private {Element} */ this.scrollableContainer_ = null; } isLayoutSupported(layout) { return layout == SPZCore.Layout.LOGIC; } buildCallback() { this.viewport_ = this.getViewport(); this.initActions_(); } setTarget(containerId, targetId) { this.containerId = '#' + containerId; this.targetId = '#' + targetId; } scrollToTarget() { const container = document.querySelector(this.containerId); const target = container.querySelector(this.targetId); const {scrollTop} = container; const eleOffsetTop = this.getOffsetTop_(target, container); this.viewport_ .interpolateScrollIntoView_( container, scrollTop, scrollTop + eleOffsetTop ); } initActions_() { this.registerAction( 'scrollToTarget', (invocation) => this.scrollToTarget(invocation?.caller) ); this.registerAction( 'setTarget', (invocation) => this.setTarget(invocation?.args?.containerId, invocation?.args?.targetId) ); } /** * @param {Element} element * @param {Element} container * @return {number} * @private */ getOffsetTop_(element, container) { if (!element./*OK*/ getClientRects().length) { return 0; } const rect = element./*OK*/ getBoundingClientRect(); if (rect.width || rect.height) { return rect.top - container./*OK*/ getBoundingClientRect().top; } return rect.top; } } SPZ.defineElement('spz-custom-anchor-scroll', SpzCustomAnchorScroll); const STRENGTHEN_TRUST_URL = "/api/strengthen_trust/settings"; class SpzCustomStrengthenTrust extends SPZ.BaseElement { constructor(element) { super(element); this.renderElement_ = null; } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback() { this.xhr_ = SPZServices.xhrFor(this.win); const renderId = this.element.getAttribute('render-id'); SPZCore.Dom.waitForChild( document.body, () => !!document.getElementById(renderId), () => { this.renderElement_ = SPZCore.Dom.scopedQuerySelector( document.body, `#${renderId}` ); if (this.renderElement_) { this.render_(); } this.registerAction('track', (invocation) => { this.track_(invocation.args); }); } ); } render_() { this.fetchData_().then((data) => { if (!data) { return; } SPZ.whenApiDefined(this.renderElement_).then((apis) => { apis?.render(data); document.querySelector('#strengthen-trust-render-1539149753700').addEventListener('click',(event)=>{ if(event.target.nodeName == 'A'){ this.track_({type: 'trust_content_click'}); } }) }); }); } track_(data = {}) { const track = window.sa && window.sa.track; if (!track) { return; } track('trust_enhancement_event', data); } parseJSON_(string) { let result = {}; try { result = JSON.parse(string); } catch (e) {} return result; } fetchData_() { return this.xhr_ .fetchJson(STRENGTHEN_TRUST_URL) .then((responseData) => { if (!responseData || !responseData.data) { return null; } const data = responseData.data; const moduleSettings = (data.module_settings || []).reduce((result, moduleSetting) => { return result.concat(Object.assign(moduleSetting, { logos: (moduleSetting.logos || []).map((item) => { return moduleSetting.logos_type == 'custom' ? this.parseJSON_(item) : item; }) })); }, []); return Object.assign(data, { module_settings: moduleSettings, isEditor: window.self !== window.top, }); }); } } SPZ.defineElement('spz-custom-strengthen-trust', SpzCustomStrengthenTrust);

Description

Double the Power — Peel Shot Glow for Face & Body Powered by Hypochlorous Acid and Rice Extract, this unique formula helps exfoliate, purify, and smooth the skin by lifting away rough buildup, excess sebum, and dead skin cells that can contribute to trouble skin and visible skin concerns.  

[Gentle Exfoliation, Visible Results] Easy exfoliation at home as visible impurities roll away with every massage. How it works : -Gentle, hydrating formula powered by Rice Extract & Panthenol softens excess sebum and dead skin cells so that they're easier to remove and prepping skin for exfoliation. -As you roll, the formula attaches itself to dead skin cells like a magnet and gathers them into a visible gommage*, leaving skin feeling clean, clear, refreshed and hydrated. [WHAT IS GOMMAGE?] Our Peel Shot is formulated to only react to dead skin cells and excess sebum, and as the formula reacts and exfoliates, it gathers into visible clump of 'gommage' (meaning erasing / exfoliation in French) [WHY IT’S SPECIAL]

Complete Duo for Face & Body: - For Face: Helps clear away pore-clogging buildup and visible excess sebum, leaving the skin looking smoother, clearer, and more refined. - For Body: Improves rough, uneven areas such as elbows, knees, underarms, heels, and ankles, helping address concerns like rough bumps or visible pores on arms and legs. Clarify & Balance & Pore-care: - Helps sweep away hidden impurities and dead skin buildup for smoother and clearer skin tone, smaller-looking pores Low-Irritation Formula: - Designed to be gentle and suitable for all skin types without pain or harsh physical exfoliation. Targets only real dead skin cells: - Specially formulated to react only on the skin Easy to Use: - Simply massage away the excess sebum and dead skin cells for effortless at-home care with no pain or discomfort. [POWERED BY]

Hypochlorous Acid*: Purifying ingredient well known for targeting impurities that can lead to troubled skin. Helps soothe and purify visible skin concerns.  Rice Extract*: Gently removes dead skin and excess sebum while nourishing for smoother, radiant skin.  Allantoin & Panthenol*: Replenish moisture, smooth rough texture, and support a healthy natural barrier. *The above information pertains only to the characters of the raw materials. [INCLUDES]

1. Hypochlorous Acid Peel Shot (for face) - 80ml / 2.70 fl.oz- Helps lift away dead skin cells and excess oil buildup that can contribute to visible skin concerns.- Gently exfoliates to improve uneven texture for smoother skin tone and visibly clearer skin. Real Results After 1 Use:·Dead skin cells reduced by 75.41%·Excess oil reduced by 18.87%·Fewer blackheads & whiteheads appearance by 12.08%·Reduced pore impurities by 99.3% *Clinically Tested by 'Global Institute of Dermatological Sciences' / Results may vary depending on individual skin types Texture: Rich, hydrating serum formula suitable for all skin types

 2. Hypochlorous Acid Body Peel Shot (for body) – 280ml / 9.46 fl.oz - Targets ingrown hairs, rough patches & uneven texture on the body - Comes in larger size to use across all body areas - Hypochlorous Acid concentration doubled for thicker body skin - Suitable for rough, bumpy, textured body areas, dark elbows/underarms/ankles. Texture: Light watery formula designed to spread easily over larger body areas [HOW TO USE]1. Shake well before use. After cleansing, apply an appropriate amount of Hypochlorous Acid Peel Shot to dry skin, avoiding the eye area. 2. Gently massage over the skin as impurities and dead skin cells form visible clumps. 3. Rinse thoroughly with lukewarm water or wipe away. 4. For the body, use the Hypochlorous Acid Body Peel Shot to dry, rough or textured areas before shower and rinse off. 5. Follow with a moisturizer or a cream.

Note: - Use around 2 times per week for optimal results - Some residue may harden around the nozzle, but this does not affect product quality.   [FAQ] Q: Can I use it on damp/wet skin? A: For best results, apply on dry skin. This allows the formula to work most effectively, targeting buildup and impurities for smoother, refreshed skin.   Q: Can I use it daily? A: For optimal skin condition, we recommend using it around 2 times per week as a focused care. It is especially effective when dead skin cells have built up. Although our product has been formulated with gentle ingredients, please keep in mind that over exfoliation is not good for the skin and can cause irritation. Q: Can I use the Body Peel Shot on the face and vice versa? A: Each formula is tailored to the specific skin type and concerns of its intended area. Since facial and body skin differ in texture and needs, use each product only as directed for optimal results.