(function(){ class SPZCustomSubscribeCancelReason extends SPZ.BaseElement { constructor(element) { super(element); /** 当前选中的取消原因文案。 */ this.selectedReason = ''; /** “Other” 选项对应的补充说明原始输入值。 */ this.otherReason = ''; /** 是否需要展示 Other 的错误提示。 */ this.shouldValidateOther = false; /** 避免组件重复挂载时重复绑定事件。 */ this.bound = false; this.handleOptionClick = this.handleOptionClick.bind(this); this.handleOtherInput = this.handleOtherInput.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } isLayoutSupported(layout) { return layout === SPZCore.Layout.LOGIC; } buildCallback() { this.registerAction('open', (invocation) => { const date = invocation?.args?.date || "" this.open(date); }); this.registerAction('reset', () => { this.reset(); }); } mountCallback() { this.cacheDom(); if (this.bound) return; this.optionsWrapper?.addEventListener('click', this.handleOptionClick); this.otherTextarea?.addEventListener('input', this.handleOtherInput); this.submitButton?.addEventListener('click', this.handleSubmit); this.bound = true; this.syncUI(); } unmountCallback() { this.optionsWrapper?.removeEventListener('click', this.handleOptionClick); this.otherTextarea?.removeEventListener('input', this.handleOtherInput); this.submitButton?.removeEventListener('click', this.handleSubmit); this.bound = false; } /** 缓存弹窗内的关键 DOM,减少重复查询。 */ cacheDom() { this.lightbox = document.querySelector('#subscribe_cancel_reason_lightbox'); this.bodyWrapper = this.lightbox?.querySelector('.subscribe_cancel_reason_body'); this.optionsWrapper = this.lightbox?.querySelector('.subscribe_cancel_reason_options'); this.optionButtons = Array.from(this.lightbox?.querySelectorAll('.subscribe_cancel_reason_options .subscribe_cancel_reason_option') || []); this.otherWrapper = this.lightbox?.querySelector('.subscribe_cancel_reason_other'); this.otherTextarea = this.lightbox?.querySelector('.subscribe_cancel_reason_textarea'); this.otherCount = this.lightbox?.querySelector('.subscribe_cancel_reason_count'); this.helperText = this.lightbox?.querySelector('.subscribe_cancel_reason_helper'); this.submitButton = this.lightbox?.querySelector('.subscribe_cancel_reason_submit'); } /** 每次打开都回到初始态,避免沿用上一次的输入。 */ open(date) { /* 保存日期 */ this.date = date; this.reset(); SPZ.whenApiDefined(this.lightbox).then((apis) => { apis.open(); }); } /** 清空内部状态并刷新界面显示。 */ reset() { this.selectedReason = ''; this.otherReason = ''; this.shouldValidateOther = false; if (this.otherTextarea) { this.otherTextarea.value = ''; } this.syncUI(); } /** 统一返回去除首尾空格后的 Other 内容,用于校验和提交。 */ getTrimmedOtherReason() { return (this.otherReason || '').trim(); } /** 从按钮文案读取原因值,避免依赖重复的 data 属性。 */ getOptionValue(option) { return option?.querySelector('.subscribe_cancel_reason_option_text')?.innerText?.trim() || ''; } /** 选中 Other 后把中间滚动区滚到底部,确保输入框完整露出。 */ scrollBodyToBottom() { if (!this.bodyWrapper) return; requestAnimationFrame(() => { this.bodyWrapper.scrollTo({ top: this.bodyWrapper.scrollHeight, behavior: 'smooth', }); }); } /** 处理原因选中态切换,并在离开 Other 时清空补充说明。 */ handleOptionClick(event) { const option = event.target.closest('.subscribe_cancel_reason_option'); if (!option) return; this.selectedReason = this.getOptionValue(option); this.shouldValidateOther = this.selectedReason === 'Other'; if (this.selectedReason !== 'Other') { this.otherReason = ''; if (this.otherTextarea) { this.otherTextarea.value = ''; } } this.syncUI(); if (this.selectedReason === 'Other') { this.scrollBodyToBottom(); } } /** 限制 Other 输入长度,并在输入后同步错误态和计数。 */ handleOtherInput(event) { this.otherReason = (event.target.value || '').slice(0, 100); if (event.target.value !== this.otherReason) { event.target.value = this.otherReason; } this.shouldValidateOther = this.selectedReason === 'Other' && !this.getTrimmedOtherReason(); this.syncUI(); } /** 关闭原因弹窗后,把确认过的原因广播给后续取消流程。 */ handleSubmit() { if (!this.selectedReason) return; if (this.selectedReason === 'Other' && !this.getTrimmedOtherReason()) { this.shouldValidateOther = true; this.syncUI(); this.otherTextarea?.focus(); return; } SPZ.whenApiDefined(this.lightbox).then((apis) => { apis.close(); this.notifyConfirmed(); }); } notifyConfirmed() { const detail = { reason: this.selectedReason, otherReason: this.selectedReason === 'Other' ? this.getTrimmedOtherReason() : '', date: this.date, }; const cancelLightbox = document.querySelector('#subscribe_cancel_logic'); SPZ.whenApiDefined(cancelLightbox).then((apis) => { apis.showCancel(detail); }); } /** 把内部状态映射到 UI:选中态、Other 区域、提示文案和提交按钮。 */ syncUI() { if (!this.optionButtons || !this.submitButton) return; this.optionButtons.forEach((button) => { const selected = this.getOptionValue(button) === this.selectedReason; button.classList.toggle('selected', selected); button.setAttribute('aria-checked', selected ? 'true' : 'false'); }); const showOther = this.selectedReason === 'Other'; this.otherWrapper?.classList.toggle('show', showOther); const showOtherError = showOther && this.shouldValidateOther && !this.getTrimmedOtherReason(); this.otherWrapper?.classList.toggle('error', showOtherError); this.helperText?.classList.toggle('hidden', !showOtherError); if (this.otherCount) { this.otherCount.textContent = `${this.otherReason.length}/100`; } const enabled = Boolean(this.selectedReason) && (this.selectedReason !== 'Other' || Boolean(this.getTrimmedOtherReason())); this.submitButton.disabled = !enabled; this.submitButton.classList.toggle('enabled', enabled); } } SPZ.defineElement('spz-custom-subscribe-cancel-reason', SPZCustomSubscribeCancelReason); })() (function(){ class SPZCustomSubscribeCancel extends SPZ.BaseElement { constructor(element) { super(element); this.action_ = SPZServices.actionServiceForDoc(this.element); } isLayoutSupported(layout) { return layout === SPZCore.Layout.LOGIC; } buildCallback(){ this.registerAction('cancel', (invocation) => { this.cancelSubscribe(); }); this.registerAction('goback', (invocation) => { this.goBack(); }); /* this.registerAction('showviewcount', (invocation) => { this.showViewCount(); }); */ this.registerAction('showretain', (invocation) => { this.showRetain(); }); } showCancel(detail){ const date = detail?.date || ""; this.reason = detail?.reason || ""; this.otherReason = detail?.otherReason || ""; /* 显示取消订阅弹窗 加入时间 */ const descContent = document.querySelector('#subscribe_cancel_lightbox .desc_content'); if(descContent){ descContent.innerText = `Your subscription will be canceled on the last day of the billing cycle (${date}), and we will no longer charge you any fees. You can choose to renew at any time before that date.` } /* 打开弹窗 */ const cancelBox = document.querySelector('#subscribe_cancel_lightbox_prev'); SPZ.whenApiDefined(cancelBox).then(apis=>{ apis.open(); }) } cancelSubscribe(){ const body = { cancel_reason: { code: this.reason, detail: this.otherReason, }, }; fetch(location.origin+'/apps/best-short/api/v1/customer/subscribe/cancel',{ method:'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) .then(res=>res.json()) .then(res=>{ if(res.errors && res.errors[0]){ window.customToast(res.errors[0], 'error') }else{ const confirm = document.querySelector('#subscribe_cancel_lightbox'); SPZ.whenApiDefined(confirm).then(apis=>{ const name = 'canceled'; const event = SPZUtils.Event.create(this.win, `spz-custom-subscribe-cancel.${name}`,{}); this.action_.trigger(this.element, name, event); apis.close(); }) } }).catch(err=>console.error('[cancelSubscribe-err]', err)) } goBack(){ const cancelLightbox = document.querySelector('#subscribe_cancel_lightbox'); SPZ.whenApiDefined(cancelLightbox).then(apis=>{ apis.close(); window.history.back(); }); } formatPlayCount(num) { // 处理非数字类型输入 if (typeof num !== 'number' || isNaN(num)) return '0'; // 处理负数(播放量通常不会为负) if (num < 0) num = 0; if (num >= 1e6) { const value = num / 1e6; return `${value % 1 === 0 ? value : value.toFixed(1).replace(/\.0$/, '')}M`; } if (num >= 1e3) { const value = num / 1e3; return `${value % 1 === 0 ? value : value.toFixed(1).replace(/\.0$/, '')}K`; } return num.toString(); } showRetain(){ let prods = []; prods = prods.map(ele=>({ cover: { src: ele.image.src}, id: ele.id})); fetch(location.origin+'/apps/best-short/api/v1/customer/subscribe/cancel_retain') .then(res=>res.json()) .then(res=>{ if(res && res.skit_infos){ res.skit_infos = res.skit_infos.map(ele=>({...ele, view_count: this.formatPlayCount(Number(ele.view_count) || 0)})) prods = prods.filter(ele=> !res.skit_infos.find(item=>item.skit_id == ele.id)); const cancelList = document.querySelector('#subscribe_cancel_list'); SPZ.whenApiDefined(cancelList).then(apis=>{ const finalList = [...res.skit_infos, ...prods].slice(0,18); apis.render(finalList); }) } }) } /* showViewCount(){ const items = document.querySelectorAll('.like_list .like_item'); if(!items.length) return; const ids = []; items.forEach(ele=> ids.push(ele.dataset.pid)); fetch(location.origin+'/apps/best-short/api/v1/preview/skit/views',{method:'POST',body: JSON.stringify({product_ids: ids})}) .then(res=>res.json()) .then(res=>{ if(res && res.data){ const vals = Object.values(res.data); vals.forEach((ele,index)=>{ const item = items[index]; const count = Number(ele.views); if(count){ const viewCountDom = item.querySelector('.viewcount'); viewCountDom.style = "display: block;" const countDom = item.querySelector('.count'); countDom.innerText = this.formatPlayCount(count); } }) } }).catch(err=>console.error('[showViewCount]', err)); } */ } SPZ.defineElement('spz-custom-subscribe-cancel', SPZCustomSubscribeCancel); })() (function(){ class SPZCustomSubscribeList extends SPZ.BaseElement { trackMode = 1; trackProductId = undefined; mounting = false; marketActivitieIds = new Set(); constructor(element) { super(element); this.subscribeList = []; this.paymentSettings = null; } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } setMarketActivitieIds(marketActivities) { const set = new Set(); if(marketActivities && marketActivities.length > 0){ marketActivities.forEach(item=> set.add(item.entitled_product_ids?.[0])); this.marketActivitieIds = set; } } buildCallback() { this.registerAction('onRender', (invocation) => { this.fixHandleChange(); this.handleConfirm(); this.setMarketActivitieIds(invocation?.args?.data?.data?.market_activities || []); const data = invocation?.args?.data?.data || {}; this.listData = invocation?.args?.data?.data || []; this.refreshSubscribeList(data); setTimeout(()=>{ this.preloadAbandonImg(); }, 100); }); this.registerAction('track', (invocation) => { if ('currentVideoPlayer' in window) { this.trackMode = 2; this.trackProductId = window.currentVideoPlayer.getCurrentVideoId(); } else { this.trackMode = 1; this.trackProductId = undefined; } this.trackViewContent(); setTimeout(()=>{ this.trackImpression(); }, 2000); }); this.registerAction('onCloseClick', () => { this.onCloseClick(); }); this.registerAction('onOpen', () => { const list = document.querySelector('#subscribe_list_render .subscribe_list'); if(list){ list.scrollTo(0,0); if (!this.paymentSettings || !this.mounting) { this.renderECList(); } } }); this.registerAction('storeData', (invocation) => { window.shortSellingType = invocation?.args?.data?.data?.selling_type; }); this.registerAction('startSmartPointer', () => { this.startSmartPointer(); }); this.registerAction('stopSmartPointer', () => { this.stopSmartPointer(); }); } async getPaymentSettings() { try { const res = await fetch("/apps/bs-pay/api/v1/checkout/payment/setting"); const data = await res.json(); return data; } catch (error) { console.error('[getPaymentSettings] error', error); return undefined; } finally { const confirm = document.querySelector('#subscribe_lightbox .confirm'); if(confirm){ confirm.style.pointerEvents = 'auto'; } } } async renderECList(isFirst) { const domSelector = { ...(isFirst ? { paypal: "#payment-ec-paypal-container" } : {}), apple: "#payment-ec-applepay-container", google: "#payment-ec-googlepay-container", } const checked = document.querySelector('#subscribe_lightbox .subscribe_list .item-checked'); const pid = checked?.dataset?.pid; const vid = checked?.dataset?.vid; const price = checked?.dataset?.price; const name = checked?.dataset?.name; const cycleDays = +checked?.dataset?.cycleDays; const firstCycleDays = +checked?.dataset?.firstCycleDays; const listItem = this.subscribeList.find(ele=>ele.id === pid); const sellingPlanOptionId = listItem.selling_plan_info.selected_selling_plan_option_id; const lineItem = { variant_id: vid, product_id: pid, product_title: name, price, selling_plan_option_id: sellingPlanOptionId, cycle_days: cycleDays, first_cycle_days: firstCycleDays, ...(listItem?._renewal && +listItem._renewal.renewal_start_num === 1 ? { renewal_price: listItem._renewal.renewal_price } : {}), }; window.shortLineItem = lineItem; this.mounting = true; window.BSPublicJSReady(() => { window.mountBSECPayment(domSelector, this.paymentSettings, lineItem, { onConfirm: () => { this.showLoading(); }, onFinish: () => { this.hideLoading(); }, onError: () => { this.hideLoading(); }, onCancel: () => { this.hideLoading(); }, onInit: (channel) => { this.hideLoading(); } }).then(() => { this.hideLoading(); this.mounting = false; }).catch((err) => { this.hideLoading(); this.mounting = false; }); }); } preloadAbandonImg() { const abandonLogic = document.getElementById('subscribe_ab_discount_logic'); const activities = this.getAbandonActivities(); if (!abandonLogic || !activities) return; SPZ.whenApiDefined(abandonLogic).then((apis) =>{ activities.forEach(activity => { apis.preloadImg(activity); }); }); } async refreshSubscribeList(data){ const plans = data.subscribe_plans || []; const info = data.selling_plan_info || {}; const items = data.line_items || []; const mergedPlans = plans.map(plan=>{ let finalPrice = plan.price; const discount = info[plan.id] || {}; const item = items.find(i=>i.product_id === plan.id); finalPrice = item?.final_line_price; return { ...plan, final_price: finalPrice, selling_plan_info: discount, _renewal: item?.properties?._renewal || null } }); this.subscribeList = mergedPlans; this.paymentSettings = await this.getPaymentSettings(); this.renderECList(true); this.updatePlayerSubscribeData(); } getAbandonActivities() { const marketActivities = this.listData?.market_activities; if (!marketActivities || !Array.isArray(marketActivities)) { return []; } const abandonActivities = []; marketActivities.forEach(marketActivity => { const abandonActivityId = marketActivity?.entitled_product_ids?.[0]; if (abandonActivityId) { const subscribePlan = this.listData.subscribe_plans?.find(ele => ele.id == abandonActivityId); if (!subscribePlan) return; if (!this.listData.line_items) return; const item = this.listData.line_items.find(ele => ele.product_id == abandonActivityId) || {}; const info = this.subscribeList.find(ele => ele.id == abandonActivityId); const config = marketActivity?.config; const priority = marketActivity?.priority; const sellingPlan = this.listData?.selling_plan_info?.[abandonActivityId]; const sellingPlanId = sellingPlan?.selling_plan_options?.[0]?.selling_plan_option_id; const abandonActivity = { ...item, ...info, config, priority, _selling_plan_option_id: sellingPlanId }; abandonActivities.push(abandonActivity); } }); return abandonActivities; } onCloseClick(){ const listBox = document.querySelector('#subscribe_lightbox'); SPZ.whenApiDefined(listBox).then(apis=>{ apis.close(); }) const abandonActivities = this.getAbandonActivities(); const abandonLogic = document.querySelector('#subscribe_ab_discount_logic'); SPZ.whenApiDefined(abandonLogic).then(async (apis)=>{ await apis.startModalSequence(abandonActivities); }); } trackImpression() { window.csTracker.track('function_expose', { event_name: 'function_expose', event_type: 'popup_expose', event_info: JSON.stringify({ skit_user_id: window.csTracker.getSkitUid(), mode: this.trackMode, product_id: this.trackProductId, }), }, ['sa']); /* 需要排除掉支付挽留,并与实际渲染列表保持一致 */ let showList = this.listData?.subscribe_plans || []; const line_items = this.listData?.line_items || []; if(this.marketActivitieIds.size > 0){ showList = showList.filter(item=>!this.marketActivitieIds.has(item.id)); } showList = showList.filter(item=> +line_items?.find(ele=>ele?.product_id === item?.id)?.final_line_price > 0); const maxCount = Number("6") || 4; showList = showList.slice(0, maxCount); const defaultPosition = Number("2") || 2; let defaultItem = showList?.[0]; if(defaultPosition > 0 && showList.length >= defaultPosition){ defaultItem = showList?.[defaultPosition - 1]; } const checkedItemDOM = document.querySelector('#subscribe_lightbox .item-wrapper.item-checked'); const checkedProductId = checkedItemDOM?.dataset?.pid; const defaultLineItem = line_items.find(ele=>ele.product_id === checkedProductId) || {}; defaultItem = { ...defaultItem, ...defaultLineItem } if(defaultItem){ this.pureTrackAddToCart({ id: defaultItem.id, title: defaultItem.title, price: defaultItem.final_line_price, variant_id: defaultItem.variant_id, }); } window.csTracker.track('function_click', { event_name: 'function_click', event_type: 'click', event_info: JSON.stringify({ skit_user_id: window.csTracker.getSkitUid(), mode: this.trackMode, opt_type: 5, product_id: this.trackProductId, plan_id: defaultItem.id, }), }, ['sa']); } trackViewContent(){ let showlist = this.subscribeList.slice(); if(this.marketActivitieIds.size > 0){ showlist = showlist.filter(item=>!this.marketActivitieIds.has(item.id)); } showlist = showlist.filter(ele=>!!ele.final_price); const maxCount = Number("6") || 4; showlist = showlist.slice(0,maxCount); showlist.forEach(item=>{ window.csTracker.track('ViewContent', { name: item.title, id: item.variant_id, content_ids: [item.variant_id], content_name: item.title, num_items: 1, currency: window.C_SETTINGS.currency_code, value: item.final_price }, ['fb']); }); /* 套餐曝光 */ const product_ids = showlist.map(item=>item.id); window.csTracker.track('function_expose', { event_name: 'function_expose', event_type: 'expose', event_info: JSON.stringify({ skit_user_id: window.csTracker.getSkitUid(), product_ids, expose_type: 2, }), }, ['sa']); } formatDate(date) { const year = date.getFullYear(); const month = date.getMonth() + 1; const day = date.getDate(); return `${year}-${month}-${day}`; } pureTrackAddToCart(item) { const currentDate = new Date(); const skitUid = window.csTracker.getSkitUid(); const today = this.formatDate(currentDate); const todayCacheKey = `${today}-${skitUid}`; const cacheKey = `addToCartEventCache`; const cacheData = JSON.parse(localStorage.getItem(cacheKey)) || {}; const todayCache = cacheData[todayCacheKey] || []; if(todayCache.includes(item.id)){ return; } todayCache.push(item.id); cacheData[todayCacheKey] = todayCache; Object.keys(cacheData).forEach(key => { if(!key.startsWith(today)){ delete cacheData[key]; } }); localStorage.setItem(cacheKey, JSON.stringify(cacheData)); document.dispatchEvent(new CustomEvent('dj.addToCart', { detail: { event_time: new Date().getTime(), product_id: item.id, name: item.title, item_price: item.price, variant_id: item.variant_id, variant: { option1: item.title, }, quantity: '1', number: '1' } })); } /* 上报 initiateCheckRate */ trackOrder(name, pid){ window.csTracker.track('function_click', { event_name: 'function_click', event_type: 'click', event_info: JSON.stringify({ skit_user_id: window.csTracker.getSkitUid(), opt_type: 1, mode: this.trackMode, product_id: this.trackProductId, }), }, ['sa']); } showLoading(){ const loading = document.querySelector('#subscribe_loading'); SPZ.whenApiDefined(loading).then(apis=>{ apis.show_(); }) } hideLoading(){ const loading = document.querySelector('#subscribe_loading'); SPZ.whenApiDefined(loading).then(apis=>{ apis.close_(); }) } mountCallback() { const listRender = document.querySelector('#subscribe_list_render'); SPZ.whenApiDefined(listRender).then(apis=>{ apis.render(); }); } fixHandleChange(){ const list = document.querySelectorAll('#subscribe_lightbox .subscribe_list .item-wrapper'); list.forEach(item => { item.onclick = () => { const checkedItem = document.querySelector('.subscribe_list .item-checked'); if(item.dataset.pid == checkedItem.dataset.pid){ return; } const list = document.querySelectorAll('#subscribe_lightbox .subscribe_list .item-wrapper'); list.forEach(item=>{ item.classList.remove('item-checked')}) item.classList.add('item-checked'); const listItem = this.subscribeList.find(ele=>ele.id === item.dataset.pid); this.renderECList(); // GPay APay 刷新 this.pureTrackAddToCart({ id: listItem.id, title: listItem.title, price: listItem.final_price, variant_id: listItem.variant_id, }); window.csTracker.track('function_click', { event_name: 'function_click', event_type: 'click', event_info: JSON.stringify({ skit_user_id: window.csTracker.getSkitUid(), mode: this.trackMode, opt_type: 5, product_id: this.trackProductId, plan_id: listItem.id, }), }, ['sa']); } }) } getCookie(name) { // 创建正则表达式来匹配指定名称的 Cookie const regex = new RegExp('(^|; )' + encodeURIComponent(name) + '=([^;]*)'); const match = document.cookie.match(regex); // 如果找到匹配项,则返回解码后的值,否则返回 null return match ? decodeURIComponent(match[2]) : null; } handleConfirm(){ const skit_uid = this.getCookie('skit-uid'); const confirm = document.querySelector('#subscribe_lightbox .confirm'); confirm.onclick = async()=>{ document.dispatchEvent(new CustomEvent('dj.checkoutSubmit', { detail: {} })); this.showLoading(); const checked = document.querySelector('#subscribe_lightbox .subscribe_list .item-checked'); const pid = checked.dataset.pid; const vid = checked.dataset.vid; const name = checked.dataset.name; const firstCycleDays = +checked.dataset.firstCycleDays; const cycleDays = +checked.dataset.cycleDays; let userEmail = ''; let lastName = ''; try{ await fetch(location.origin+'/api/customers/show').then(res=>res.json()).then(res=>{ if(res && res.customer && res.customer.id){ userEmail = res.customer.email; lastName = res.customer.last_name; localStorage.setItem('CUSTOMER_INFO', JSON.stringify(res.customer)); }else{ localStorage.removeItem('CUSTOMER_INFO'); } }) }catch(e){ console.error('[customer-show]', e) } window._local_id_email = userEmail; window._local_last_name = lastName; window._disable_show_agree = true; let params = { _best_short_uid: skit_uid }; params['_dj_selling_type'] = window.shortSellingType; params['first_cycle_days'] = firstCycleDays; params['cycle_days'] = cycleDays; const listItem = this.subscribeList.find(ele => ele.id == pid); if (listItem?._renewal && +listItem._renewal.renewal_start_num === 1) { params['renewal_price'] = listItem._renewal.renewal_price; } if (window.shortSellingType === 'DJ') { params['_selling_plan_dj_option_id'] = listItem.selling_plan_info.selected_selling_plan_option_id; } else { params['_selling_plan_option_id'] = listItem.selling_plan_info.selected_selling_plan_option_id; } params['link'] = location.href; params['entry'] = 'page'; params['short_id'] = ''; params['_sa_track_params'] = window.sa?.trackParams; params['_is_login'] = !!window.C_SETTINGS?.customer?.customer_id; if(location.pathname.includes('/products/')){ params['entry'] = 'product'; params['short_id'] = window.currentVideoPlayer.getCurrentVideoId(); params['short_title'] = null || ''; params['short_variant_id'] = window.currentVideoPlayer?.getCurrentEpisodeId?.() || ''; } const referrer = decodeURIComponent(this.getCookie('latest_referrer') || ''); if(referrer){ const referrerHost = new URL(referrer).hostname; params["_ad_from"] = referrerHost; } fetch(`${location.origin}/apps/bs-pay/api/v1/checkout/order`, { method: 'POST', headers:{ "Content-Type": "application/json" }, body: JSON.stringify({ line_items: [{quantity:"1", product_id: pid, properties: params, note:"",variant_id:vid }], refer_info: { source :"buy_now", create_identity: btoa(window.C_SETTINGS.shop.shop_id)}, options: { with_uid: true, selling_type: window.shortSellingType } }) }) .then(response => response.json()) .then(res=>{ if(res && res.check_out_url){ const lightbox = document.querySelector('#subscribe_lightbox'); SPZ.whenApiDefined(lightbox).then((box)=>{ localStorage.setItem('COMMON_NAMESPACE', window.SHOPLAZZA.namespace); localStorage.setItem('BS_PREV_URL', location.href); localStorage.setItem('SHOP_ID', window.C_SETTINGS.shop.shop_id); if(window.fbq){ localStorage.setItem('FBQ_PIXELS', JSON.stringify(window.fbq?.getState?.() || {})); } window.csTracker.track('function_click', { event_name: 'function_click', event_type: 'click', event_info: JSON.stringify({ skit_user_id: window.csTracker.getSkitUid(), mode: this.trackMode, opt_type: 1, product_id: this.trackProductId, plan_id: pid }), }, ['sa']); let orderId = ''; if (res && res.check_out_url) { const match = res.check_out_url.match(/\/order\/(\d+)/); if (match && match[1]) { orderId = `/order/${match[1]}`; } } document.dispatchEvent(new CustomEvent('dj.initiateCheckout', { detail: { id: orderId, checkout_page_type: 'single', order_id: orderId, currency_code: window.C_SETTINGS.currency_code, quantity: '1', line_items: [{ product_id: pid, variant_id: vid, quantity: 1, properties: params, }], prices: { total_price: this.subscribeList.find(item => item.id === pid).final_price, } } })); if(location.pathname.includes('/products/')){ window.sa?.track?.('begin_checkout', { product_id: null, product_title: null, price: null, variant_id: window.currentVideoPlayer.getCurrentEpisodeId(), quantity: 1, sku_quantity: 1, entrance: 'product', currency: window.C_SETTINGS.currency_code, content_ids: null }); const gtagAdsCountry = "shoplaza_" + (window.ADS_COUNTRY || "US") + "_"; const variantId = window.currentVideoPlayer.getCurrentEpisodeId(); const trackId = gtagAdsCountry + variantId; window.csTracker.track('begin_checkout', { name: null, value: null, coupon: "", currency: window.C_SETTINGS.currency_code, user_id: window.csTracker.getClientId(), item_id: trackId, items:{ id: trackId, item_id: trackId, item_name: null, item_brand: "", item_category: "", item_variant: window.currentVideoPlayer.getCurrentEpisode().no.toString(), price: null, quantity:1, google_business_vertical: "retail", } }, ['ga']); } this.hideLoading(); const blockOr = this.paymentSettings?.settings?.express_checkout_config?.express_channels?.join(','); const url = blockOr ? `${res.check_out_url}?block_or=${blockOr}&select_payment_method=shoplazzapayment` : `${res.check_out_url}?select_payment_method=shoplazzapayment`; setTimeout(()=>{ location.href = url; }); }); }else{ if(res.message || res.error){ window.customToast(res.message || res.error,'error'); }else{ window.customToast('Order failed','error'); } this.hideLoading(); } }).catch((error)=>{ console.error('[error-fetch]', error); this.hideLoading(); }) } } updatePlayerSubscribeData() { if (!window.BSPlayerMountReady) return; window.BSPlayerMountReady(() => { if (!window.currentVideoPlayer) return; const marketActivities = this.listData?.market_activities; let filteredList = this.subscribeList; if(marketActivities && marketActivities.length > 0){ const set = new Set(); marketActivities.forEach(item=> set.add(item.entitled_product_ids?.[0])); filteredList = this.subscribeList.filter(item=>!set.has(item.id)); } filteredList = filteredList.filter(item=> !!item.final_price); const maxCount = Number("6") || 4; filteredList = filteredList.slice(0, maxCount); window.currentVideoPlayer.setSubscriptionData(filteredList); }); } startSmartPointer() { const fingerWrapper = document.querySelector('#subscribe_lightbox .finger-wrapper'); if(fingerWrapper){ fingerWrapper.style.display = 'block'; } } stopSmartPointer() { const fingerWrapper = document.querySelector('#subscribe_lightbox .finger-wrapper'); if(fingerWrapper){ fingerWrapper.style.display = 'none'; } } } SPZ.defineElement('spz-custom-subscribe-list', SPZCustomSubscribeList); })() (function(){ class SPZCustomTransReportLogic extends SPZ.BaseElement { constructor(element) { super(element); this.transReportList = null this.transReportListScrollInterval = 3000; this.transReportListHeight = 28; this.scrollTime = 200; this.transReportTimer = null; this.candidatePlanCount = 2; // 播报最多展示前2个订阅方案的名称 this.planNames = []; } isLayoutSupported(layout) { return layout === SPZCore.Layout.LOGIC; } buildCallback() { this.registerAction('showTransReport', () => { this.showTransReport(); }); } showTransReport() { this.clearTransReportTimer(); this.transReportList = document.querySelector('.subscribe_trans_report_list'); if (!this.transReportList) { return; } const planDoms = document.querySelectorAll('.subscribe_list .item-wrapper'); this.planNames = Array.from(planDoms) .slice(0, this.candidatePlanCount) .map(dom => dom.getAttribute('data-name') || '') .filter(name => name); if (this.planNames.length === 0) { this.transReportList.classList.remove('is-visible'); return; } this.transReportList.innerHTML = ''; this.transReportList.scrollTop = 0; this.transReportList.classList.add('is-visible'); this.transReportList.appendChild(this.generateRandomTransReportItem()); this.transReportTimer = setInterval(() => { this.addNewTransReportItem(); }, this.transReportListScrollInterval); } addNewTransReportItem() { this.transReportList.appendChild(this.generateRandomTransReportItem()); this.animateScroll(); } animateScroll() { const startTime = Date.now(); const animate = () => { const nowTime = Date.now(); const timeDiff = nowTime - startTime; const dis = timeDiff * (this.transReportListHeight / this.scrollTime); this.transReportList.scrollTop = dis; if (dis < this.transReportListHeight) { requestAnimationFrame(animate); } else { this.transReportList.removeChild(this.transReportList.firstChild); } } requestAnimationFrame(animate); } generateRandomTransReportItem() { const div = document.createElement('div'); div.classList.add('subscribe_trans_report_item'); const { time, name, planName } = this.generateRandomTransReport(); div.innerText = `${time} ${name} purchased `; const span = document.createElement('span'); span.classList.add('subscribe_trans_report_plan_name'); span.innerText = planName; div.appendChild(span); return div; } generateRandomTransReport() { const time = this.generateRandomTime(); const username = this.generateRandomUsername(); const name = this.nameDesensitization(username); const planName = this.pickPlanName(); return { time, username, name, planName }; } pickPlanName() { if (this.planNames.length === 0) { return ''; } const randomIndex = Math.floor(Math.random() * this.planNames.length); return this.planNames[randomIndex]; } generateRandomTime() { const random = Math.random() * 100; // 生成 0-100 的随机数 let timeText = ''; if (random < 30) { // 30% 的数据:5秒 ~ 59秒前,显示 "Just now" timeText = 'Just now'; } else if (random < 80) { // 50% 的数据:1分钟 ~ 5分钟前,显示 "X mins ago" const minutes = Math.floor(Math.random() * 5) + 1; // 1-5 分钟 timeText = `${minutes} ${minutes === 1 ? 'min' : 'mins'} ago`; } else { // 20% 的数据:6分钟 ~ 15分钟前,显示 "X mins ago" const minutes = Math.floor(Math.random() * 10) + 6; // 6-15 分钟 timeText = `${minutes} mins ago`; } return timeText; } generateRandomUsername() { const names = [ "James", "Mary", "John", "Patricia", "Robert", "Jennifer", "Michael", "Linda", "William", "Elizabeth", "David", "Barbara", "Richard", "Susan", "Joseph", "Jessica", "Thomas", "Sarah", "Charles", "Karen", "Christopher", "Nancy", "Daniel", "Lisa", "Matthew", "Betty", "Anthony", "Margaret", "Mark", "Sandra" ]; const randomIndex = Math.floor(Math.random() * names.length); return names[randomIndex]; } nameDesensitization(name) { if (!name || name.length < 2) { return name + '****'; } return name.substring(0, 2) + '****'; } clearTransReportTimer() { if (this.transReportTimer) { clearInterval(this.transReportTimer); this.transReportTimer = null; } } } SPZ.defineElement('spz-custom-trans-report-logic', SPZCustomTransReportLogic); })() (function(){ class SPZCustomSubscribeSubscript extends SPZ.BaseElement { constructor(element) { super(element); this.payResult = null; } isLayoutSupported(layout) { return layout === SPZCore.Layout.LOGIC; } buildCallback(){ this.registerAction('showsubscript', () => { this.showSubscript(); }); } async showSubscript(){ const listItems = document.querySelectorAll('.subscribe_list .item-wrapper:not(:has(.custom_subscript_item))'); if(listItems.length<=0) return; const productIds = []; listItems.forEach(ele=>{ productIds.push(ele.getAttribute('data-pid')); }); if(productIds.length==0) return; let result = []; try{ const res = await fetch(location.origin+"/apps/best-short/api/v1/subscript", { method:"POST", body: JSON.stringify({ type:'subscription', resource_ids: productIds })}) .then(res=>res.json()); result = res?.data || []; }catch(e){ console.warn('[subscript get error]', e); } result.forEach(ele=>{ const index = productIds.indexOf(ele.id); if(index>=0 && ele.styles?.length>0){ const listItem = listItems[index]; ele.styles.forEach(style=>{ const _item = document.createElement('div'); _item.classList.add('custom_subscript_item'); _item.classList.add('custom_position_'+style.position); _item.classList.add(style.icon); const _span = document.createElement('span'); _span.classList.add('custom_subscript_item_text'); _span.innerHTML = style.content; _item.appendChild(_span); listItem.appendChild(_item.cloneNode(true)); }) } }) } } SPZ.defineElement('spz-custom-subscribe-subscript', SPZCustomSubscribeSubscript); })() (function(){ class SPZCustomSubscribeCheckout extends SPZ.BaseElement { constructor(element) { super(element); } isLayoutSupported(layout) { return layout === SPZCore.Layout.CONTAINER; } buildCallback() { /* console.info('[window]', window); */ this.addIFrameCallback(); } hideLoading(){ const loading = document.querySelector('#subscribe_loading'); SPZ.whenApiDefined(loading).then(apis=>{ apis.close_(); }) } trackPayCount(params){ window.csTracker.track('checkoutStepPayment', params, ['ga','fb']); if(location.pathname.includes('/products/')){ const name = ""; const id = window.currentVideoPlayer.getCurrentVideoId(); window.csTracker.track('checkoutStepPayment', { name: name, id: id }, ['ga','fb']); } } trackPaySuccess(params){ window.csTracker.track('purchase', params, ['ga','fb']); if(location.pathname.includes('/products/')){ const name = ""; const id = window.currentVideoPlayer.getCurrentVideoId(); window.csTracker.track('purchase', { ...params, name: name, id: id }, ['ga','fb']); } } payCallback(val){ if(val.res.state == 'success'){ const checkout = document.querySelector('#subscribe_checkout_lightbox'); SPZ.whenApiDefined(checkout).then(async(apis)=>{ apis.close(); let hasLoggedIn = false; try{ await fetch(location.origin+'/api/customers/show').then(res=>res.json()).then(res=>{ if(res && res.customer && res.customer.id){ hasLoggedIn = true; } }) }catch(e){ console.error('[pay-customers/show]', e); } if(location.pathname.includes('pages/short-me')){ const customer = document.querySelector('#me_customer_info'); SPZ.whenApiDefined(customer).then((apis) => { apis.fresh(); }); } /* 已登录 提示成功,未登录 让注册 */ if(hasLoggedIn){ const okBox = document.querySelector('#subscribe_pay_logged_ok_box'); SPZ.whenApiDefined(okBox).then((apis) => { apis.open(); }); }else{ location.href = '/account/register'; /* const meElement =document.querySelector('#me_account_element'); SPZ.whenApiDefined(meElement).then((apis)=>{ if(val.res.email){ apis.presetEmail(val.res.email); } apis.showType('account_register'); }) const accountBox = document.querySelector('#me_account_lightbox'); SPZ.whenApiDefined(accountBox).then((apis)=>{ apis.open(); }) */ } }); this.trackPaySuccess(val.params) } } addIFrameCallback(){ window.receivedata = (val) => { console.info('[receive]', val); /* Checkout Ready */ if(val && val.type=='READY'){ const lightbox = document.querySelector('#subscribe_lightbox'); SPZ.whenApiDefined(lightbox).then((box)=>{ box.close(); const checkout = document.querySelector('#subscribe_checkout_lightbox'); SPZ.whenApiDefined(checkout).then((apis)=>{ apis.open(); this.hideLoading(); }) }); const loader = document.querySelector('#subscribe_checkout_lightbox .cs_checkout_mask'); loader && (loader.style = 'display: none') } /* 支付结果 */ if(val && val.type=='PAY'){ this.payCallback(val); } /* LOADING */ if(val && val.type=="LOADING"){ const loader = document.querySelector('#subscribe_checkout_lightbox .cs_checkout_mask'); loader && (loader.style = 'display: flex'); } /* 上报 */ if(val && val.type=="PAY_TRACK"){ this.trackPayCount(val.params); } } } } SPZ.defineElement('spz-custom-subscribe-checkout', SPZCustomSubscribeCheckout); })() (function(){ class SPZCustomLoggedPayOk extends SPZ.BaseElement { constructor(element) { super(element); } isLayoutSupported(layout) { return layout === SPZCore.Layout.LOGIC; } buildCallback(){ this.registerAction('onOpen', (invocation) => { const btn = document.querySelector('#subscribe_pay_logged_ok_box .main_btn'); btn && (btn.innerText = '3s close'); let count = 2; let interval = setInterval(()=>{ if(count==0){ clearInterval(interval); const box = document.querySelector('#subscribe_pay_logged_ok_box'); SPZ.whenApiDefined(box).then((apis) => { apis.close(); }); } btn && (btn.innerText = count+'s close'); count--; },1000); }); this.registerAction('reloadstate', (invocation) => { this.reloadstate(); }); } reloadstate(){ location.reload(); } } SPZ.defineElement('spz-custom-logged-pay-ok', SPZCustomLoggedPayOk); })()