(function () { 'use strict'; var API = 'https://api.kuzhovnikfit.ru/api/dolyami/create-by-offer'; var MSG_TYPE = 'DOLYAMI_REDIRECT'; var documentStates = new WeakMap(); function getContextWindow(context) { try { if ( context && context.window === context && context.document ) { return context; } } catch (_) {} return window; } function getDocumentState(doc) { var state = documentStates.get(doc); if (!state) { state = { initialized: false, taxSystem: '2', loaderElement: null, loaderTextElement: null, loaderActive: false, previousHtmlOverflow: '', previousBodyOverflow: '' }; documentStates.set(doc, state); } return state; } function ensureLoader(doc) { var state = getDocumentState(doc); if (state.loaderElement) { return state.loaderElement; } var style = doc.createElement('style'); style.textContent = [ '#kzh-dolyami-loader {', ' position: fixed;', ' inset: 0;', ' z-index: 2147483647;', ' display: flex;', ' align-items: center;', ' justify-content: center;', ' padding: 24px;', ' background: rgba(255, 255, 255, 0.88);', ' backdrop-filter: blur(4px);', ' -webkit-backdrop-filter: blur(4px);', ' cursor: wait;', ' touch-action: none;', ' user-select: none;', ' -webkit-user-select: none;', '}', '#kzh-dolyami-loader[hidden] {', ' display: none !important;', '}', '#kzh-dolyami-loader .kzh-dolyami-loader__content {', ' display: flex;', ' flex-direction: column;', ' align-items: center;', ' gap: 18px;', ' max-width: 360px;', ' padding: 28px 32px;', ' border-radius: 20px;', ' background: #fff;', ' box-shadow: 0 18px 60px rgba(0, 0, 0, 0.14);', ' text-align: center;', '}', '#kzh-dolyami-loader .kzh-dolyami-loader__spinner {', ' width: 52px;', ' height: 52px;', ' border: 5px solid rgba(0, 0, 0, 0.1);', ' border-top-color: #111;', ' border-radius: 50%;', ' animation: kzh-dolyami-spin 0.8s linear infinite;', '}', '#kzh-dolyami-loader .kzh-dolyami-loader__text {', ' color: #222;', ' font-family: Arial, sans-serif;', ' font-size: 17px;', ' font-weight: 600;', ' line-height: 1.4;', '}', '@keyframes kzh-dolyami-spin {', ' to { transform: rotate(360deg); }', '}' ].join('\n'); (doc.head || doc.documentElement).appendChild(style); var loader = doc.createElement('div'); loader.id = 'kzh-dolyami-loader'; loader.hidden = true; loader.setAttribute('role', 'status'); loader.setAttribute('aria-live', 'polite'); loader.setAttribute('aria-label', 'Идёт загрузка'); loader.innerHTML = [ '
', '
', '
', ' Создаём заявку на оплату…', '
', '
' ].join(''); state.loaderElement = loader; state.loaderTextElement = loader.querySelector( '.kzh-dolyami-loader__text' ); (doc.body || doc.documentElement).appendChild(loader); var contextWindow = doc.defaultView || window; var blockInteraction = function (event) { if (!state.loaderActive || event.isTrusted === false) { return; } event.preventDefault(); event.stopPropagation(); if (typeof event.stopImmediatePropagation === 'function') { event.stopImmediatePropagation(); } }; [ 'click', 'dblclick', 'pointerdown', 'mousedown', 'touchstart', 'submit', 'keydown', 'wheel' ].forEach(function (eventName) { contextWindow.addEventListener( eventName, blockInteraction, { capture: true, passive: false } ); }); return loader; } function showLoader(doc, text) { var state = getDocumentState(doc); var loader = ensureLoader(doc); state.loaderActive = true; if (state.loaderTextElement) { state.loaderTextElement.textContent = text || 'Создаём заявку на оплату…'; } state.previousHtmlOverflow = doc.documentElement.style.overflow; state.previousBodyOverflow = doc.body ? doc.body.style.overflow : ''; doc.documentElement.style.overflow = 'hidden'; if (doc.body) { doc.body.style.overflow = 'hidden'; doc.body.setAttribute('aria-busy', 'true'); } loader.hidden = false; } function updateLoaderText(doc, text) { var state = getDocumentState(doc); if (state.loaderTextElement) { state.loaderTextElement.textContent = text; } } function hideLoader(doc) { var state = getDocumentState(doc); if (!state.loaderElement) { return; } state.loaderActive = false; state.loaderElement.hidden = true; doc.documentElement.style.overflow = state.previousHtmlOverflow; if (doc.body) { doc.body.style.overflow = state.previousBodyOverflow; doc.body.removeAttribute('aria-busy'); } } function closeModalForForm(form) { var modal = form.closest('.modal.gc-modal'); if (!modal) { return; } var closeButton = modal.querySelector('.close-btn'); if (closeButton) { closeButton.click(); } else { console.warn( 'Не нашёл .close-btn внутри модалки' ); } } function findUserValues(form) { var query = function (selector) { return form.querySelector(selector); }; var firstNameElement = query('[name="formParams[first_name]"]') || query('[name*="first_name"]') || query('input[placeholder*="имя"]'); var lastNameElement = query('[name="formParams[last_name]"]') || query('[name*="last_name"]') || query('input[placeholder*="фам"]'); var emailElement = query('[name="formParams[email]"]') || query('input[type="email"]') || query('[name*="email"]'); var phoneElement = query('[name="formParams[phone]"]') || query('[name*="phone"]') || query('input[placeholder*="тел"]'); var phoneRaw = phoneElement ? String(phoneElement.value || '').trim() : ''; return { firstName: firstNameElement ? String(firstNameElement.value || '').trim() : '', lastName: lastNameElement ? String(lastNameElement.value || '').trim() : '', email: emailElement ? String(emailElement.value || '').trim() : '', phone: phoneRaw.replace(/[^\d+]/g, '') }; } function validateRequired(user, contextWindow) { var missing = []; if (!user.firstName) missing.push('Имя'); if (!user.lastName) missing.push('Фамилия'); if (!user.phone) missing.push('Телефон'); if (!user.email) missing.push('E-mail'); if (missing.length) { contextWindow.alert( 'Пожалуйста, заполните обязательные поля: ' + missing.join(', ') ); return false; } return true; } function validateConfirmCheckboxes( form, contextWindow ) { var doc = form.ownerDocument; var targets = [ { name: 'globalConfirmCheckbox', label: 'Я ознакомлен(а) с условиями публичной оферты' }, { name: 'pdpConfirmCheckbox', label: 'Я даю согласие на обработку персональных данных' } ]; var notChecked = []; targets.forEach(function (target) { var selector = 'input.global-confirm-checkbox[name="' + target.name + '"]'; var element = form.querySelector(selector) || doc.querySelector(selector); if ( element && !element.disabled && !element.checked ) { notChecked.push(target.label); } }); if (notChecked.length) { contextWindow.alert( 'Пожалуйста, отметьте чекбоксы: ' + notChecked.join(', ') ); return false; } return true; } function getSelectedOffer(form) { var checked = form.querySelector( 'input[name="formParams[offer_id][]"]:checked' ); if (!checked) { return null; } var offerBlock = checked.closest('.offerBlock') || checked.closest('.builder-item') || checked.closest('label') || checked.parentElement; var offerLabel = checked.closest('label[data-offer-id]'); if ( !offerLabel && offerBlock && offerBlock.querySelector ) { offerLabel = offerBlock.querySelector( 'label[data-offer-id]' ); } if ( !offerLabel && offerBlock && offerBlock.matches && offerBlock.matches('label[data-offer-id]') ) { offerLabel = offerBlock; } var offerId = offerLabel ? String( offerLabel.getAttribute('data-offer-id') || '' ).trim() : ''; var productNameElement = offerBlock ? offerBlock.querySelector('.offer-title') : null; if (!productNameElement && offerLabel) { productNameElement = offerLabel.querySelector('.offer-title'); } var productName = productNameElement ? String( productNameElement.textContent || '' ).trim() : ''; var price = checked.getAttribute('data-price-value') || ( checked.dataset ? checked.dataset.priceValue : '' ) || ''; var unavailable = Boolean( ( offerBlock && offerBlock.classList && offerBlock.classList.contains( 'dolyami-unavailable' ) ) || ( offerLabel && offerLabel.classList && offerLabel.classList.contains( 'dolyami-unavailable' ) ) ); return { checked: checked, offerBlock: offerBlock, offerLabel: offerLabel, offerId: offerId, productName: productName, price: price, unavailable: unavailable }; } function updateDolyamiVisibility(form) { var dolyamiWrap = form.querySelector('.dolyami-btn'); if (!dolyamiWrap) { return; } var offer = getSelectedOffer(form); if (!offer) { dolyamiWrap.style.display = ''; return; } dolyamiWrap.style.display = offer.unavailable ? 'none' : ''; } function findTelegramNick(form) { var needle = 'ник в telegram'; var doc = form.ownerDocument; var findIn = function (root) { var inputs = Array.prototype.slice.call( root.querySelectorAll('input[placeholder]') ); var element = inputs.find(function (input) { return String( input.getAttribute('placeholder') || '' ) .toLowerCase() .includes(needle); }); return element ? String(element.value || '').trim() : ''; }; return findIn(form) || findIn(doc); } function getTrackingFromUrl(contextWindow) { var params = new URLSearchParams( contextWindow.location.search ); var pick = function (name) { return String(params.get(name) || '').trim(); }; return { utmSource: pick('utm_source'), utmMedium: pick('utm_medium'), utmCampaign: pick('utm_campaign'), utmContent: pick('utm_content'), utmTerm: pick('utm_term') }; } async function createDolyami(payload) { var response = await fetch(API, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), credentials: 'omit', redirect: 'follow' }); var contentType = String( response.headers.get('content-type') || '' ).toLowerCase(); var data; if (contentType.includes('application/json')) { data = await response.json(); } else { var text = await response.text(); try { data = JSON.parse(text); } catch (_) { data = { redirectUrl: text }; } } if (!response.ok) { var message = ( data && (data.message || data.error) ) || ('HTTP ' + response.status); throw new Error( 'Dolyami API error: ' + message ); } var url = data && ( data.redirectUrl || data.url || data.redirect ); if (!url || typeof url !== 'string') { throw new Error( 'No redirectUrl from API' ); } return url; } function isHttpUrl(url, baseUrl) { try { var parsed = new URL(url, baseUrl); return ( parsed.protocol === 'https:' || parsed.protocol === 'http:' ); } catch (_) { return false; } } function redirectToBank(url, contextWindow) { if ( !isHttpUrl( url, contextWindow.location.href ) ) { throw new Error('Invalid redirect URL'); } /* * Обычная страница. */ if (contextWindow.top === contextWindow) { contextWindow.location.assign(url); return; } /* * Same-origin iframe. */ try { contextWindow.top.location.assign(url); return; } catch (_) {} /* * Cross-origin iframe. * Родительская страница должна содержать основной * скрипт, чтобы принять это сообщение. */ contextWindow.top.postMessage( { type: MSG_TYPE, url: url }, '*' ); } function lockButton(button) { button.dataset.dolyamiLocked = '1'; button.classList.add('disabled'); button.setAttribute( 'aria-disabled', 'true' ); if ('disabled' in button) { button.disabled = true; } } function unlockButton(button) { button.dataset.dolyamiLocked = '0'; button.classList.remove('disabled'); button.removeAttribute('aria-disabled'); if ('disabled' in button) { button.disabled = false; } } async function onDolyamiClick(event, doc) { var target = event.target; if ( !target || typeof target.closest !== 'function' ) { return; } var button = target.closest( '.dolyami-btn button, ' + '.dolyami-btn input[type="submit"]' ); if (!button) { return; } var form = button.closest('form'); if (!form) { return; } event.preventDefault(); event.stopPropagation(); if ( typeof event.stopImmediatePropagation === 'function' ) { event.stopImmediatePropagation(); } var state = getDocumentState(doc); if ( state.loaderActive || button.dataset.dolyamiLocked === '1' ) { return; } lockButton(button); var contextWindow = doc.defaultView || window; var user = findUserValues(form); if ( !validateRequired( user, contextWindow ) ) { unlockButton(button); return; } if ( !validateConfirmCheckboxes( form, contextWindow ) ) { unlockButton(button); return; } var offer = getSelectedOffer(form); if (!offer) { unlockButton(button); contextWindow.alert( 'Не удалось определить выбранное предложение. ' + 'Обновите страницу и выберите пакет заново.' ); return; } if (!offer.offerId) { console.error( '[Dolyami] Не найден data-offer-id ' + 'у выбранного предложения:', offer ); unlockButton(button); contextWindow.alert( 'Не удалось определить ID выбранного предложения. ' + 'Обновите страницу и выберите пакет заново.' ); return; } if (offer.unavailable) { updateDolyamiVisibility(form); unlockButton(button); contextWindow.alert( 'Оплата Долями недоступна ' + 'для выбранного предложения.' ); return; } var tracking = getTrackingFromUrl(contextWindow); var payload = { firstName: user.firstName, lastName: user.lastName, email: user.email, phone: user.phone, /* * Если taxSystem не передали в init(), * здесь используется значение 2. */ taxSystem: state.taxSystem || '2', price: offer.price, productName: offer.productName, /* * ID выбранного предложения из * label[data-offer-id]. */ offerID: offer.offerId, telegramNick: findTelegramNick(form), utmSource: tracking.utmSource, utmMedium: tracking.utmMedium, utmCampaign: tracking.utmCampaign, utmContent: tracking.utmContent, utmTerm: tracking.utmTerm }; console.log( '[Dolyami] Payload:', payload ); try { if ( doc.activeElement && typeof doc.activeElement.blur === 'function' ) { doc.activeElement.blur(); } } catch (_) {} showLoader( doc, 'Создаём заявку на оплату…' ); try { var redirectUrl = await createDolyami(payload); updateLoaderText( doc, 'Всё готово. Переходим к оплате…' ); closeModalForForm(form); redirectToBank( redirectUrl, contextWindow ); /* * После успешного ответа лоадер * намеренно не отключается. * Он останется до редиректа. */ } catch (error) { console.error( '[Dolyami] create error:', error ); hideLoader(doc); unlockButton(button); contextWindow.alert( 'Не получилось создать заявку Долями. ' + 'Попробуйте ещё раз или обратитесь в поддержку.' ); } } function onOfferChange(event) { var target = event.target; if ( !target || typeof target.matches !== 'function' ) { return; } if ( target.matches( 'input[name="formParams[offer_id][]"]' ) ) { var form = target.closest('form'); if (form) { updateDolyamiVisibility(form); } } } /* * Публичная инициализация. * * Scope передавать не нужно. * Document определяется через window, * с которым была вызвана функция. */ function init(taxSystem) { var contextWindow = getContextWindow(this); var doc = contextWindow.document; var state = getDocumentState(doc); var normalizedTaxSystem = String( taxSystem == null ? '' : taxSystem ).trim(); /* * Значение по умолчанию. */ state.taxSystem = normalizedTaxSystem || '2'; Array.prototype.forEach.call( doc.querySelectorAll('form'), updateDolyamiVisibility ); if (!state.initialized) { state.initialized = true; doc.addEventListener( 'click', function (event) { onDolyamiClick(event, doc); }, true ); doc.addEventListener( 'change', function (event) { onOfferChange(event); }, true ); } console.log( '[Dolyami] Инициализировано. taxSystem:', state.taxSystem, 'context:', contextWindow === contextWindow.top ? 'page' : 'iframe' ); return true; } /* * Проверяем, что сообщение с редиректом * поступило от iframe на текущей странице. */ function isDirectChildFrame(sourceWindow) { var frames = document.querySelectorAll('iframe'); for ( var index = 0; index < frames.length; index += 1 ) { if ( frames[index].contentWindow === sourceWindow ) { return true; } } return false; } /* * Обработчик редиректа из cross-origin iframe. */ if ( !window.__kuzhovnikDolyamiRedirectListener ) { window.__kuzhovnikDolyamiRedirectListener = true; window.addEventListener( 'message', function (event) { var data = event.data; if ( window.top !== window || !data || data.type !== MSG_TYPE || typeof data.url !== 'string' || !isDirectChildFrame(event.source) || !isHttpUrl( data.url, window.location.href ) ) { return; } window.location.assign(data.url); } ); } /* * Скрипт ничего не запускает автоматически. * Только создаёт публичный метод init(). */ window.KuzhovnikDolyami = { init: init }; })();