Документация
Страницы для доставки еды в зал

Заказ еды в зал кинопространства mooon

Информация о доставке и оплате

Страница успеха

Неуспешная оплата

Страница-заглушка (нерабочее время)

Документация проекта

Пример меток для сканирования и заказа еды в зал
Карты для проверки
Страница альфы:
https://sandbox.alfabank.by/sandbox/ru/integration/structure/test-cards.html#testowye-karty
Тестовые карты

В целях тестирования вы можете использовать тестовые карты, указанные ниже.

 
Card number
4111 1111 1111 1111  
CVC
123 
Expiry
12/26 
Authorization
3DS2 
Result
success 
Full/Frictionless
Frictionless
 
Card number
4000 0011 1111 1118  
CVC
123 
Expiry
12/30 
Authorization
3DS2 Attempt 
Result
success
 
Card number
4444 5555 1111 3333  
CVC
123 
Expiry
12/26 
Authorization
SSL 
Result
success
 
Card number
6390 0200 0000 000003  
CVC
123 
Expiry
12/34 
Authorization
3DS1 
Result
success 
3-D Secure verification code
12345678
Карты, перечисленные ниже, можно использовать для выплат.

 
Card number
5437 8370 8630 0529  
CVC
123 
Expiry
12/34 
Authorization
3DS2 
Result
success 
Full/Frictionless
Full
 
Card number
4402 5600 1473 2639  
CVC
123 
Expiry
12/34 
Authorization
3DS2 
Result
success 
Full/Frictionless
Full
Скрипт вызова заглушки
Автоматический редирект на страницу-заглушку (/bocked) в нерабочее время согласно графику в schedule с защитой от бесконечного цикла на самой странице-заглушке. Размещен на всех страницах. Для корректной работы необходимо только поддерживать корректный график работы.
<script>     // редирект на страницу-заглушку в нерабчее время
(function() {
    // --- НАСТРОЙКИ ГРАФИКА ПО ДНЯМ НЕДЕЛИ ---
    // 1 - Понедельник ... 7 - Воскресенье
    // Если день выходной, оставьте кавычки пустыми: start: "", end: ""
    const schedule = {
        1: { start: "07:00", end: "00:00" }, // Понедельник
        2: { start: "07:00", end: "00:00" }, // Вторник
        3: { start: "07:00", end: "00:00" }, // Среда
        4: { start: "07:00", end: "00:00" }, // Четверг
        5: { start: "07:00", end: "02:00" }, // Пятница (работает до 2 ночи субботы)
        6: { start: "07:00", end: "02:00" }, // Суббота (работает до 2 ночи воскресенья)
        7: { start: "07:00", end: "02:00" }  // Воскресенье (работает до 2 ночи понедельника)
    };

    const config = {
        redirectUrl: "https://heroes.by/delivery/blocked",
        timezone: "Europe/Minsk"
    };

    // Защита от бесконечного цикла на странице-заглушке
    if (window.location.href.indexOf("/delivery/blocked") > -1) {
        return;
    }

    // Получаем текущее время в Минске
    const now = new Date();
    const minskTimeStr = now.toLocaleString("en-US", { timeZone: config.timezone });
    const minskDate = new Date(minskTimeStr);
    
    // JS считает дни от 0 (ВС) до 6 (СБ). Переводим в привычный формат 1 (ПН) - 7 (ВС)
    const jsDay = minskDate.getDay();
    const currentDay = jsDay === 0 ? 7 : jsDay;
    const prevDay = currentDay === 1 ? 7 : currentDay - 1;

    const currentTotalMinutes = minskDate.getHours() * 60 + minskDate.getMinutes();

    // Функция перевода "ЧЧ:ММ" в минуты
    const parseToMinutes = (timeStr) => {
        if (!timeStr) return null;
        const parts = timeStr.split(":");
        return parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
    };

    const todaySchedule = schedule[currentDay];
    const yesterdaySchedule = schedule[prevDay];

    let isWorkingHours = false;

    // ШАГ 1. Проверяем, не идет ли сейчас смена, начавшаяся ВЧЕРА (переход через полночь)
    if (yesterdaySchedule && yesterdaySchedule.start && yesterdaySchedule.end) {
        const yStart = parseToMinutes(yesterdaySchedule.start);
        const yEnd = parseToMinutes(yesterdaySchedule.end);
        
        // Если вчерашняя смена заканчивалась после полуночи (например, 14:00 - 02:00)
        // и сейчас время ДО окончания этой смены
        if (yStart > yEnd && currentTotalMinutes < yEnd) {
            isWorkingHours = true;
        }
    }

    // ШАГ 2. Если мы не во вчерашней смене, проверяем попадание в СЕГОДНЯШНЮЮ
    if (!isWorkingHours && todaySchedule && todaySchedule.start && todaySchedule.end) {
        const tStart = parseToMinutes(todaySchedule.start);
        const tEnd = parseToMinutes(todaySchedule.end);

        if (tStart <= tEnd) {
            // Обычный график (например, 10:00 - 23:00)
            if (currentTotalMinutes >= tStart && currentTotalMinutes < tEnd) {
                isWorkingHours = true;
            }
        } else {
            // График с переходом через полночь (например, 14:00 - 02:00)
            if (currentTotalMinutes >= tStart) {
                isWorkingHours = true;
            }
        }
    }

    // Редирект, если время нерабочее
    if (!isWorkingHours) {
        window.location.replace(config.redirectUrl);
    }
})();
</script>
Скрипт подстановки параметров из отсканированной метки
Хранение и подстановка параметров из отсканированной QR-метки. Содержит словарь кинотеатров/залов (необходимо корректно описывать их при генерации QR, в соответствии со словарём):
'dana': 'Dana Mall',
'palazzo': 'Palazzo',
'minsk-city': 'Minsk city mall',
'trinity': 'Trinity',
'arena-city': 'Arena city'

Пример ссылки:
https://heroes.by/delivery?cinema=minsk-city&hall=4-heroes&row=9&seat=11
<script>    // Подстановка параметров из QR-метки или ссылки перехода в форму заказа
document.addEventListener('DOMContentLoaded', function() {
    const urlParams = new URLSearchParams(window.location.search);
    
    // Структурированный словарь
    const translations = {
        'cinema': {
            'dana': 'Dana Mall',
            'palazzo': 'Palazzo',
            'minsk-city': 'Minsk city mall',
            'trinity': 'Trinity',
            'arena-city': 'Arena city'
        },
        'hall': {
            '2-lounge': 'Зал 2 Lounge',
            '3-mooon': 'Зал 3 mooon+',
            '4-heroes': 'Зал 4 Heroes'
        }
    };

    function fillTildaFields() {
        // Перебираем параметры из URL
        for (const [key, value] of urlParams.entries()) {
            let finalValue = value;

            // Если для этого параметра есть словарь — берем перевод
            if (translations[key] && translations[key][value]) {
                finalValue = translations[key][value];
            }

            // Ищем элементы: инпуты, селекты, текстовые поля по атрибуту name
            const selector = `[name="${key}"], .t-input[name="${key}"]`;
            const elements = document.querySelectorAll(selector);
            
            elements.forEach(el => {
                if (el.value !== finalValue) {
                    el.value = finalValue;
                    // Триггеры, чтобы Тильда увидела изменения
                    el.dispatchEvent(new Event('change', { bubbles: true }));
                    el.dispatchEvent(new Event('input', { bubbles: true }));
                }
            });
        }
    }

    // Следим за открытием корзины
    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (mutation.addedNodes.length) fillTildaFields();
        });
    });

    observer.observe(document.body, { childList: true, subtree: true });
    
    // Вызов для статических полей на странице (если есть)
    fillTildaFields();
});
</script>
Вывод цифровой клавиатуры
Принудительное использование цифровой клавиатуры при заполнении соответствующих полей (ряд, место) пользователем в корзине.
<script> // Вывод цифровой клавиатуры на мобильных устройствах для полей ряд/место
        
document.addEventListener('DOMContentLoaded', function() {
    function setMobileNumpad() {
        // Указываем имена переменных, для которых нужна цифровая клавиатура
        const numericFields = ['row', 'seat']; 
        
        numericFields.forEach(param => {
            const selector = `[name="${param}"], .t-input[name="${param}"]`;
            const elements = document.querySelectorAll(selector);
            
            elements.forEach(el => {
                // Защита от повторного срабатывания
                if (!el.hasAttribute('data-keyboard-fixed')) {
                    el.setAttribute('data-keyboard-fixed', 'true');
                    
                    // Эта связка 100% вызывает крупную цифровую клавиатуру на iOS и Android
                    el.setAttribute('inputmode', 'numeric');
                    el.setAttribute('pattern', '[0-9]*');
                    
                    // Отключаем автозаполнение и лишние подсказки браузера
                    el.setAttribute('autocomplete', 'off');
                }
            });
        });
    }

    // Следим за появлением корзины в DOM
    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (mutation.addedNodes.length) setMobileNumpad();
        });
    });

    observer.observe(document.body, { childList: true, subtree: true });
    
    // Запуск для полей, которые уже есть на странице
    setMobileNumpad();
});
</script>
Отправка метки в метрику
Скрипт для отправки uid и анных о кинотеатре/зале/месте для того чтобы проще находить сессии в аналитике.
<script> //отправляем в метрику uid и данные о кинотеатре/зале/месте
(function() {
    var count = 0;
    var maxAttempts = 20; // Ждем до 10 секунд (20 попыток по 500мс)
    
    var runAnalytics = setInterval(function() {
        count++;
        // Проверяем готовность метода ym
        if (typeof ym !== 'undefined') {
            clearInterval(runAnalytics);
            
            // 1. Извлекаем данные из URL
            var urlParams = new URLSearchParams(window.location.search);
            var cName = urlParams.get('cinema') || 'No Cinema';
            var hName = urlParams.get('hall') || 'No Hall';
            var rName = 'Ряд ' + (urlParams.get('row') || '0');
            var sName = 'Место ' + (urlParams.get('seat') || '0');

            // 2. Строим иерархию (Дерево)
            var hierarchy = {};
            hierarchy[cName] = {};
            hierarchy[cName][hName] = {};
            hierarchy[cName][hName][rName] = {};
            hierarchy[cName][hName][rName][sName] = 1;

            // 3. Отправляем иерархию в Метрику
            ym(108598627, 'params', { "География зала": hierarchy });

            // 4. Получаем ClientID и записываем в скрытое поле
            ym(108598627, 'getClientID', function(clientID) {
                var input = document.querySelector('input[name="ym_client_id"]');
                if (input) {
                    input.value = clientID;
                    console.log("ClientID записан: " + clientID);
                }
            });
            
            console.log("Данные иерархии отправлены:", hierarchy);
            
        } else if (count >= maxAttempts) {
            clearInterval(runAnalytics);
            console.warn("Метрика не загрузилась за 10 секунд");
        }
    }, 500);
})();
</script>
Пример тела данных после оплаты
Данные о заказе в формате JSON. От разработки потребуется вывести эндпойнт для приёма этих данных и реализовать последующую разработку. Отправка данных POST-запросом, передача API-токена в Header.
{
   "name":"\u0422\u0438\u043c\u043e\u0444\u0435\u0439 \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440\u043e\u0432\u0438\u0447",
   "email":"multistime@gmail.com",
   "phone":"+375 (25) 970-85-14",
   "hall":"\u0417\u0430\u043b 4 Heroes",
   "row":"9",
   "seat":"11",
   "cinema":"Minsk city mall",
   "ym_client_id":"1776431924611532459",
   "payment":{
      "sys":"alfabank",
      "systranid":"5a354b97-fee4-7e06-9c8e-dc7000ff6eb7",
      "orderid":"1222869707",
      "products":[
         {
            "name":"\u0422\u043e\u0441\u0442\u0438\u043b\u043e\u043a\u043e\u0441 HeroQueso",
            "quantity":2,
            "amount":40,
            "externalid":"NFXTEEBh68Xc4OYOx8dp",
            "img":"https:\/\/static.tildacdn.biz\/stor6431-6436-4166-b665-666664613532\/8021de41c0f798ba96b22004d87f0de7.jpg",
            "price":"20",
            "sku":"123456789"
         },
         {
            "name":"\u041d\u0430-\u043a\u0430! \u0422\u0430\u043a\u043e \u0441 \u044f\u0437\u044b\u043a\u043e\u043c",
            "quantity":1,
            "amount":27,
            "externalid":"KYzCtiFIYg9cCxkXuoRo",
            "img":"https:\/\/static.tildacdn.biz\/stor6537-6562-4135-b634-376231306136\/c1f461ec7486625f52c5607f8b4494df.jpg",
            "price":"27"
         },
         {
            "name":"\u041d\u0430-\u043a\u0430! \u0422\u0430\u043a\u043e \u0441 \u043a\u0440\u0435\u0432\u0435\u0442\u043a\u043e\u0439",
            "quantity":1,
            "amount":27,
            "externalid":"xrvjchbthhXFLsXKM74z",
            "img":"https:\/\/static.tildacdn.biz\/stor6161-6433-4537-b435-623665653963\/4cee0280ce4756f61bc7147900724f5c.jpg",
            "price":"27"
         }
      ],
      "amount":"94"
   },
   "COOKIES":"_ym_visorc=w; __ddg10_=1778493640; __ddg8_=9FLLkRdKHLIF7YLK; __ddg9_=37.214.32.85; _ym_fa=11028.iLSIbwB5S13aKYGNrclqw_UW9afXDiYduF_5bzLl_hqH3XkJShYH_tt2XHbVXSJo.J-KO65Z8DJLWtDbxtU8oJGNReDA%2C; _ym_isad=2; _ym_d=1776431924; _ym_uid=1776431924611532459;tildauid=fc32a047ff7f33fc6eeaf836315fcf53",
   "formid":"form2167783671",
   "formname":"Cart"
}