(function () { 'use strict'; console.log('itteration: 3'); var PREFIX = '[GSOC]'; var DEBUG = true; function log() { if (!DEBUG) return; try { console.log.apply(console, [PREFIX].concat([].slice.call(arguments))); } catch (e) {} } function warn() { try { console.warn.apply(console, [PREFIX].concat([].slice.call(arguments))); } catch (e) {} } function err() { try { console.error.apply(console, [PREFIX].concat([].slice.call(arguments))); } catch (e) {} } var CONFIG = { STORE_PATH: '/_ofert-admin', MARKER: '.gsoc-settings', KEY_SETTINGS: 'gsoc_settings_cache', SETTINGS_TTL: 5 * 60 * 1000, DEFAULT_STYLE: { bg: '#FFFFFF', text: '#333333', link: '#337AB7', fontSize: 14, radius: 4, borderColor: '#DDDDDD', borderWidth: 1 }, SNAPSHOT_TITLE: 'Вы дали следующие согласия', PAY_PATH: /\/sales\/shop\/dealPay\//, PAY_ROOTS: ['.allPayments', '.xdget-payform', '#pay-methods', 'form[action*="Init"]', '#payment-page', '.payment-page'], PAY_BUTTONS: '.gc-payment-method-button, button.pay-button, button[type=submit], a[href*="Init"], .pay-btn-sber-pay, .pay-btn-sbp-pay, .pay-btn-alpha-pay, .pay-btn-split, .pay-btn-fresh-credit', LOCK_CLASS: 'gsoc-pay-locked', HIDE_CLASS: 'gsoc-pay-hidden', ORDER_CARD_PATH: '/sales/control/userProduct/update/id/', ADD_FIELDS_URL: '/pl/user/user/get-user-additional-form-data?id=', ORDER_BLOCK_ATTR: 'data-gsoc-order-block', CONFIRM_PAGE: '/_ofert-confirm', FIELD: 'user_order_details', READY_TIMEOUT: 8000, SAVE_TIMEOUT: 15000, READ_TIMEOUT: 15000, STYLES_ID: 'gsoc-styles' }; function b64encode(str) { var bytes = new TextEncoder().encode(String(str == null ? '' : str)); var bin = ''; var chunk = 0x8000; for (var i = 0; i < bytes.length; i += chunk) { bin += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk)); } return btoa(bin); } function b64decode(b64) { var bin = atob(b64); var bytes = new Uint8Array(bin.length); for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); return new TextDecoder().decode(bytes); } function esc(str) { return String(str == null ? '' : str) .replace(/&/g, '&').replace(//g, '>') .replace(/"/g, '"').replace(/'/g, '''); } function currentUserId() { if (typeof window.accountUserId !== 'undefined' && window.accountUserId) return String(window.accountUserId); if (window.gcUser && window.gcUser.id) return String(window.gcUser.id); return null; } function isAdminViewer() { var ui = window.userInfo || {}; return !!(ui.isAdmin || ui.canCms); } function injectStyles() { if (document.getElementById(CONFIG.STYLES_ID)) return; var css = [ '.gsoc-pay{margin:0 0 24px}', '.gsoc-pay-body{border-radius:8px;padding:20px 22px}', '.gsoc-pay-title{font-weight:700;margin:0 0 14px;font-size:1.08em;line-height:1.4}', '.gsoc-pay-item{display:flex;align-items:flex-start;gap:10px;margin:0 0 12px;line-height:1.5;cursor:pointer}', '.gsoc-pay-item:last-of-type{margin-bottom:0}', '.gsoc-pay-item input{flex:0 0 auto;width:18px;height:18px;margin:3px 0 0;cursor:pointer}', '.gsoc-pay-body a{color:var(--gsoc-link, inherit);text-decoration:underline;text-underline-offset:2px}', '.gsoc-pay-status{margin-top:14px;font-size:13px;line-height:1.45;opacity:.85}', '.gsoc-pay-status.error{color:#c0392b;opacity:1}', '.gsoc-pay-hidden{display:none !important}', '.gsoc-frame{position:absolute;width:1px;height:1px;left:-9999px;top:0;border:0}', '.gsoc-order-plain .gsoc-pay-body{padding:0;background:transparent;border:0;border-radius:0;color:inherit;font-size:inherit}', '.gsoc-order-plain .gsoc-pay-title{font-weight:700;margin:0 0 8px}', '.gsoc-order-plain .gsoc-pay-item{display:block;cursor:default;margin:0 0 8px;line-height:1.45}', '.gsoc-order-when{margin-top:6px}', '.gsoc-order-empty{font-size:13px;color:#8b8b9e}' ].join(''); var el = document.createElement('style'); el.id = CONFIG.STYLES_ID; el.textContent = css; document.head.appendChild(el); } var frame = null; var frameReady = null; function ensureFrame() { if (frame && frame.parentNode && frameReady) return frameReady; frame = document.createElement('iframe'); frame.className = 'gsoc-frame'; frame.setAttribute('aria-hidden', 'true'); frame.setAttribute('tabindex', '-1'); frame.src = CONFIG.CONFIRM_PAGE; document.body.appendChild(frame); frameReady = new Promise(function (resolve, reject) { var done = false; var timer = setTimeout(function () { if (done) return; done = true; window.removeEventListener('message', onReady); reject(new Error('страница ' + CONFIG.CONFIRM_PAGE + ' не ответила')); }, CONFIG.READY_TIMEOUT); function onReady(e) { if (done) return; if (e.origin !== location.origin) return; if (e.source !== frame.contentWindow) return; var d = e.data || {}; if (d.type !== 'gsoc:ready' && d.type !== 'gsoc:pong') return; done = true; clearTimeout(timer); window.removeEventListener('message', onReady); log('страница подтверждений готова'); resolve(frame); } window.addEventListener('message', onReady); frame.addEventListener('load', function () { try { frame.contentWindow.postMessage({ type: 'gsoc:ping' }, location.origin); } catch (e) {} }); }); return frameReady; } function frameRequest(type, payload, answerType, timeout) { return ensureFrame().then(function (fr) { return new Promise(function (resolve, reject) { var timer = setTimeout(function () { window.removeEventListener('message', onReply); reject(new Error('таймаут ответа страницы подтверждений')); }, timeout || CONFIG.SAVE_TIMEOUT); function onReply(e) { if (e.origin !== location.origin) return; if (e.source !== fr.contentWindow) return; var d = e.data || {}; if (d.type !== answerType) return; clearTimeout(timer); window.removeEventListener('message', onReply); if (d.ok) resolve(d); else reject(new Error(d.error || 'ошибка страницы подтверждений')); } window.addEventListener('message', onReply); var msg = { type: type }; if (payload) msg.payload = payload; log('запрос к ' + CONFIG.CONFIRM_PAGE + ': ' + type); fr.contentWindow.postMessage(msg, location.origin); }); }); } function saveToConfirm(payload) { return frameRequest('gsoc:save', payload, 'gsoc:saved', CONFIG.SAVE_TIMEOUT).then(function (d) { return d.result || { ok: true }; }); } function readFromConfirm() { return frameRequest('gsoc:read', null, 'gsoc:value', CONFIG.READ_TIMEOUT).then(function (d) { return d.value || null; }); } function unescapeDeep(text) { var out = String(text || ''); for (var i = 0; i < 6; i++) { var next = out.replace(/\\\\/g, '\\').replace(/\\"/g, '"').replace(/\\'/g, "'"); if (next === out) break; out = next; } return out; } function parseRecordJson(text) { var marker = "'orders':"; var idx = text.indexOf(marker); if (idx === -1) return null; var start = text.lastIndexOf('{', idx); if (start === -1) return null; var depth = 0; var inStr = false; for (var i = start; i < text.length; i++) { var ch = text.charAt(i); if (inStr) { if (ch === '\\') { i++; continue; } if (ch === "'") inStr = false; continue; } if (ch === "'") { inStr = true; continue; } if (ch === '{') depth++; else if (ch === '}') { depth--; if (depth === 0) { try { return JSON.parse(text.slice(start, i + 1).replace(/'/g, '"')); } catch (e) { return null; } } } } return null; } function decodeStoredValue(rawValue) { var raw = String(rawValue || '').trim(); if (raw.indexOf('\\"') === 0 && raw.lastIndexOf('\\"') === raw.length - 2) raw = raw.slice(2, -2); return parseRecordJson(unescapeDeep(raw)); } function recordsForDeals(data, dealIds) { var orders = (data && data.orders) || {}; var found = []; dealIds.forEach(function (id) { if (orders[id]) found.push({ dealId: id, record: orders[id] }); }); return found; } function formatDate(iso) { if (!iso) return ''; var d = new Date(iso); if (isNaN(d.getTime())) return iso; var pad = function (n) { return (n < 10 ? '0' : '') + n; }; return pad(d.getDate()) + '.' + pad(d.getMonth() + 1) + '.' + d.getFullYear() + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()); } var pay = { ready: false, saving: false, saved: false, locked: false, dealId: null, offerIds: [], userId: null, settings: null, items: [], payRoot: null, blockEl: null }; function isPayPage() { if (CONFIG.PAY_PATH.test(location.pathname)) return true; var signals = 0; if (document.querySelector('input[name="deal_id"]')) signals++; if (document.querySelector('.gc-payment-method-button')) signals++; if (document.querySelector('.xdget-payform')) signals++; return signals >= 2; } function findPayRoot() { for (var i = 0; i < CONFIG.PAY_ROOTS.length; i++) { var el = document.querySelector(CONFIG.PAY_ROOTS[i]); if (el) return el; } return null; } function payContext() { var m = /\/id\/(\d+)/.exec(location.pathname); var input = document.querySelector('input[name="deal_id"]'); var dealId = m ? m[1] : (input ? String(input.value) : ''); var offers = []; var nodes = document.querySelectorAll('[data-offer-id]'); for (var i = 0; i < nodes.length; i++) { var v = String(nodes[i].getAttribute('data-offer-id') || '').trim(); if (v && offers.indexOf(v) === -1) offers.push(v); } if (!offers.length) { var lis = document.querySelectorAll('li[class*="offer-position-"]'); for (var j = 0; j < lis.length; j++) { var id = (String(lis[j].className).match(/offer-position-(\d+)/) || [])[1]; if (id && offers.indexOf(id) === -1) offers.push(id); } } return { dealId: dealId, offerIds: offers, userId: currentUserId() }; } function readSettingsFromDom() { var marker = document.querySelector(CONFIG.MARKER); var raw = marker ? marker.getAttribute('data-gsoc') : null; if (!raw) return null; try { return JSON.parse(b64decode(raw)); } catch (e) { return null; } } function cacheSettings(settings) { try { localStorage.setItem(CONFIG.KEY_SETTINGS, JSON.stringify({ at: Date.now(), settings: settings })); } catch (e) {} } function cachedSettings() { try { var parsed = JSON.parse(localStorage.getItem(CONFIG.KEY_SETTINGS) || 'null'); if (!parsed || !parsed.at || !parsed.settings) return null; if (Date.now() - parsed.at > CONFIG.SETTINGS_TTL) return null; return parsed.settings; } catch (e) { return null; } } function loadSettings() { var fromDom = readSettingsFromDom(); if (fromDom) { pay.settings = fromDom; cacheSettings(fromDom); return Promise.resolve(fromDom); } var cached = cachedSettings(); return fetch(CONFIG.STORE_PATH + '?gsoc=' + Date.now(), { credentials: 'include' }) .then(function (r) { return r.text(); }) .then(function (html) { var m = /data-gsoc="([A-Za-z0-9+/=]+)"/.exec(html || ''); if (!m) throw new Error('в ' + CONFIG.STORE_PATH + ' не найден блок настроек'); var settings = JSON.parse(b64decode(m[1])); pay.settings = settings; cacheSettings(settings); log('настройки прочитаны со страницы-хранилища'); return settings; })['catch'](function (error) { if (cached) { warn('нет доступа к настройкам, беру кэш (' + (error.message || error) + ')'); pay.settings = cached; return cached; } throw error; }); } function pickItems(settings, offerIds) { var offers = settings.offers || {}; var keys = Object.keys(offers); for (var i = 0; i < keys.length; i++) { var set = offers[keys[i]] || {}; if (offerIds.indexOf(String(set.id)) !== -1) { log('набор предложения ' + set.id + ' (' + (set.name || '') + ')'); return (set.items || []).filter(function (it) { return it.active !== false; }); } } log('набор по умолчанию'); var def = settings.default && settings.default.items ? settings.default.items : []; return def.filter(function (it) { return it.active !== false; }); } function itemMarkup(item) { var html = esc(item.prefix || ''); if (item.linkText) { html += (html ? ' ' : '') + '' + esc(item.linkText) + ''; } if (item.suffix) html += ' ' + esc(item.suffix); return html; } function payStyleCss() { var def = CONFIG.DEFAULT_STYLE; var st = (pay.settings && pay.settings.style) || {}; return 'background:' + (st.bg || def.bg) + ';color:' + (st.text || def.text) + ';--gsoc-link:' + (st.link || def.link) + ';border-radius:' + (st.radius == null ? def.radius : st.radius) + 'px' + ';border:' + (st.borderWidth == null ? def.borderWidth : st.borderWidth) + 'px solid ' + (st.borderColor || def.borderColor) + ';font-size:' + (st.fontSize || def.fontSize) + 'px'; } function payBlockHtml() { var items = pay.items.map(function (item) { return '
' + itemMarkup(item) + '
'; }).join(''); return '
' + '
' + esc(CONFIG.SNAPSHOT_TITLE) + '
' + items + '
'; } function renderPayBlock() { var itemsHtml = pay.items.map(function (item) { return [ '' ].join(''); }).join(''); var wrap = document.createElement('div'); wrap.className = 'gsoc-pay'; wrap.innerHTML = [ '
', '
', esc((pay.settings && pay.settings.title) || 'Подтвердите согласия'), '
', itemsHtml, '
', '
Чтобы перейти к оплате, отметьте все обязательные согласия.
' ].join(''); pay.blockEl = wrap; pay.payRoot.parentNode.insertBefore(wrap, pay.payRoot); wrap.addEventListener('change', onPayChange); log('блок согласий вставлен перед зоной оплаты'); } function setPayStatus(text, kind) { if (!pay.blockEl) return; var el = pay.blockEl.querySelector('[data-pay-status]'); if (!el) return; el.textContent = text || ''; el.className = 'gsoc-pay-status' + (kind ? ' ' + kind : ''); } function payButtons() { if (!pay.payRoot) return []; return [].slice.call(pay.payRoot.querySelectorAll('button, input[type=submit], a.btn, ' + CONFIG.PAY_BUTTONS)); } function applyLock(locked) { if (!pay.payRoot) return; if (locked) { pay.payRoot.classList.add(CONFIG.LOCK_CLASS); pay.payRoot.classList.add(CONFIG.HIDE_CLASS); payButtons().forEach(function (b) { if (!b.getAttribute('data-gsoc-disabled')) { b.setAttribute('data-gsoc-disabled', b.disabled ? '1' : '0'); b.setAttribute('disabled', 'disabled'); } }); } else { pay.payRoot.classList.remove(CONFIG.LOCK_CLASS); pay.payRoot.classList.remove(CONFIG.HIDE_CLASS); payButtons().forEach(function (b) { if (b.getAttribute('data-gsoc-disabled') === '0') b.removeAttribute('disabled'); b.removeAttribute('data-gsoc-disabled'); }); } pay.locked = !!locked; log('оплата ' + (locked ? 'заблокирована' : 'разблокирована') + ' (кнопок ' + payButtons().length + ')'); } function requiredChecked() { var need = pay.items.filter(function (it) { return it.required !== false; }); if (!need.length) return true; for (var i = 0; i < need.length; i++) { var input = pay.blockEl ? pay.blockEl.querySelector('input[data-item="' + need[i].id + '"]') : null; if (!input || !input.checked) return false; } return true; } function updateLock() { if (!pay.payRoot) return; var locked = !requiredChecked(); if (locked === pay.locked) return; applyLock(locked); } function buildPayPayload() { var s = pay.settings || {}; return { dealId: String(pay.dealId || ''), userId: String(pay.userId || ''), offerIds: pay.offerIds.slice(), savedAt: new Date().toISOString(), settingsVersion: s.updatedAt || '', items: [{ html: b64encode(payBlockHtml()) }] }; } function onPayChange() { if (!pay.ready) return; updateLock(); if (!requiredChecked()) { log('согласия сняты — оплата снова заблокирована'); return; } if (pay.saved || pay.saving) { log('согласия отмечены заново — повторная запись не нужна'); return; } pay.saving = true; log('все обязательные отмечены, сохраняю'); saveToConfirm(buildPayPayload()).then(function (result) { pay.saving = false; pay.saved = true; updateLock(); var ok = result && result.ok; setPayStatus(ok ? '' : 'Не удалось сохранить подтверждение. Оплата доступна.', ok ? '' : 'error'); log('запись завершена, ok=' + !!ok); })['catch'](function (error) { pay.saving = false; pay.saved = true; updateLock(); setPayStatus('Не удалось сохранить подтверждение. Оплата доступна.', 'error'); err('ошибка сохранения (fail-open)', error); }); } function startPayMode() { pay.payRoot = findPayRoot(); if (!pay.payRoot) { warn('зона оплаты не найдена — оплату не трогаю'); return; } var ctx = payContext(); pay.dealId = ctx.dealId; pay.offerIds = ctx.offerIds; pay.userId = ctx.userId; log('страница оплаты: заказ=' + pay.dealId + ', предложения=' + (pay.offerIds.join(',') || '—') + ', пользователь=' + pay.userId); if (!pay.dealId) { warn('номер заказа не определён — выхожу'); return; } loadSettings().then(function (settings) { pay.items = pickItems(settings, pay.offerIds); if (!pay.items.length) { warn('в наборе нет видимых чекбоксов — оплату не блокирую'); return; } renderPayBlock(); applyLock(true); pay.ready = true; watchPay(); log('готово: пунктов ' + pay.items.length + ', обязательных ' + pay.items.filter(function (it) { return it.required !== false; }).length); })['catch'](function (error) { warn('fail-open: ' + (error.message || error)); }); } function watchPay() { if (!window.MutationObserver || watchPay._started) return; watchPay._started = true; new MutationObserver(function () { if (!pay.ready || !pay.payRoot) return; if (pay.locked) { if (!pay.payRoot.classList.contains(CONFIG.HIDE_CLASS)) applyLock(true); else payButtons().forEach(function (b) { if (!b.hasAttribute('data-gsoc-disabled')) b.setAttribute('disabled', 'disabled'); }); } if (pay.blockEl && !document.body.contains(pay.blockEl)) { warn('блок согласий удалён страницей — вставляю заново'); pay.blockEl = null; if (document.body.contains(pay.payRoot)) renderPayBlock(); } }).observe(document.body, { childList: true, subtree: true }); } var order = { mode: null, userId: null, dealIds: [], data: null, rendered: false, isSelf: false, isAdmin: false }; function isOrderCardPage() { return location.pathname.indexOf(CONFIG.ORDER_CARD_PATH) !== -1; } function orderBuyerId() { var link = document.querySelector('.key-value-table a.user-profile-link[data-user-id]'); if (link) return link.getAttribute('data-user-id'); var panel = document.getElementById('userFormAdditionalFields'); if (panel) { var m = /[?&]id=(\d+)/.exec(panel.getAttribute('data-load-url') || ''); if (m) return m[1]; } var any = document.querySelector('a[href*="/user/control/user/update/id/"]'); if (any) { var m2 = /id\/(\d+)/.exec(any.getAttribute('href') || ''); if (m2 && m2[1] !== '-1' && m2[1] !== '-2') return m2[1]; } return null; } function orderDealIds() { var ids = []; var add = function (value) { var v = String(value || '').trim(); if (v && ids.indexOf(v) === -1) ids.push(v); }; var panels = [].slice.call(document.querySelectorAll('.panel')); var related = panels.filter(function (p) { var h = p.querySelector('.panel-heading'); return h && /Связанные заказы/i.test(h.textContent || ''); })[0]; if (related) { [].slice.call(related.querySelectorAll('a[href*="/deal/"]')).forEach(function (a) { var m = /\/deal\/(?:update\/)?id\/(\d+)/.exec(a.getAttribute('href') || ''); if (m) add(m[1]); }); } if (!ids.length) { [].slice.call(document.querySelectorAll('.key-value-table a[href*="deal"]')).forEach(function (a) { var m = /\/deal\/(?:update\/)?id\/(\d+)/.exec(a.getAttribute('href') || ''); if (m) add(m[1]); }); } if (!ids.length) { [].slice.call(document.querySelectorAll('[data-deal-id]')).forEach(function (el) { add(el.getAttribute('data-deal-id')); }); } if (!ids.length) { [].slice.call(document.querySelectorAll('input[name="deal_id"], input[name="dealId"]')).forEach(function (el) { add(el.value); }); } return ids; } function loadBuyerField(userId) { return fetch(CONFIG.ADD_FIELDS_URL + userId, { credentials: 'include' }) .then(function (r) { return r.text(); }) .then(function (html) { log('ответ доп. полей покупателя: ' + html.length + ' байт'); var data = decodeStoredValue(html) || parseRecordJson(unescapeDeep(html)); if (!data) throw new Error('в ответе нет данных поля ' + CONFIG.FIELD); return data; }); } function orderRecordHtml(record) { var items = (record && record.items) || []; var html = items.map(function (it) { try { return b64decode(it.html); } catch (e) { return ''; } }).join(''); if (!html) return ''; var tmp = document.createElement('div'); tmp.innerHTML = html; [].slice.call(tmp.querySelectorAll('[style]')).forEach(function (el) { el.removeAttribute('style'); }); return [ '
', tmp.innerHTML, '
', '
Подтверждено: ', formatDate(record.savedAt), '
' ].join(''); } function renderOrderRow() { var table = document.querySelector('.key-value-table'); if (!table || !table.tBodies.length) { warn('таблица карточки заказа не найдена'); return false; } if (table.querySelector('tr[' + CONFIG.ORDER_BLOCK_ATTR + ']')) { order.rendered = true; return true; } var found = recordsForDeals(order.data, order.dealIds); var body = found.length ? found.map(function (f) { return orderRecordHtml(f.record); }).join('') : '
Нет информации о подтверждённых согласиях по этому заказу.
'; var row = document.createElement('tr'); row.setAttribute(CONFIG.ORDER_BLOCK_ATTR, '1'); var key = document.createElement('td'); key.className = 'key'; key.textContent = 'Согласия'; var value = document.createElement('td'); value.className = 'value'; value.innerHTML = body; row.appendChild(key); row.appendChild(value); table.tBodies[0].appendChild(row); order.rendered = true; log('строка «Согласия» добавлена (записей ' + found.length + ')'); return true; } function watchOrder() { if (!window.MutationObserver || watchOrder._started) return; watchOrder._started = true; new MutationObserver(function () { if (!order.rendered) return; var table = document.querySelector('.key-value-table'); if (table && !table.querySelector('tr[' + CONFIG.ORDER_BLOCK_ATTR + ']')) renderOrderRow(); }).observe(document.body, { childList: true, subtree: true }); } function startOrderMode() { var selfId = currentUserId(); var buyerId = orderBuyerId(); order.userId = buyerId || selfId || null; order.dealIds = orderDealIds(); order.isSelf = !!selfId && String(order.userId) === String(selfId); order.isAdmin = isAdminViewer(); order.mode = order.isSelf ? 'self' : (order.isAdmin ? 'admin' : 'guest'); log('карточка заказа: смотрит=' + selfId + ' (' + order.mode + '), покупатель=' + order.userId + ', заказы=' + (order.dealIds.join(',') || '—')); injectStyles(); var loader; if (order.isSelf || !order.isAdmin) { if (!order.isSelf) warn('чужие согласия недоступны: нет прав на чтение ДП другого пользователя'); loader = readFromConfirm; } else { loader = function () { return loadBuyerField(order.userId); }; } loader() .then(function (data) { order.data = data; renderOrderRow(); watchOrder(); })['catch'](function (error) { warn('не удалось прочитать ДП: ' + (error.message || error)); order.data = null; renderOrderRow(); watchOrder(); }); } function boot() { injectStyles(); if (isPayPage()) { log('режим: страница оплаты'); startPayMode(); return; } if (isOrderCardPage()) { log('режим: карточка заказа'); startOrderMode(); return; } log('подходящий режим не найден — ничего не делаю'); } window.GSOC = { pay: pay, order: order, config: CONFIG, save: function () { return saveToConfirm(buildPayPayload()); }, snapshot: payBlockHtml, readOwn: readFromConfirm, debug: function () { return { page: isPayPage() ? 'pay' : (isOrderCardPage() ? 'order' : 'other'), pay: { dealId: pay.dealId, offerIds: pay.offerIds, items: pay.items.length, locked: pay.locked, saved: pay.saved, ready: pay.ready }, order: { mode: order.mode, userId: order.userId, dealIds: order.dealIds, rendered: order.rendered, hasData: !!order.data }, settingsVersion: pay.settings ? pay.settings.updatedAt : null }; } }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', boot); } else { boot(); } })();