(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
};
})();