Telegram-бот программы лояльности в 1С:Рознице: регистрация карт по номеру телефона

Полноценный Telegram-бот прямо в конфигурации 1С:Розница — без сторонних сервисов и коннекторов: покупатель делится телефоном, бот автоматически регистрирует карту лояльности.

Заказчик: Розничная сеть детских товаров (Казахстан)
Стек: 1С:Розница, HTTP-сервис 1С, Telegram Bot API, JSON (ЗаписатьJSON/ПрочитатьJSON), регламентные задания, HTTPS/OpenSSL, webhook + long polling

Задача

Дать покупателям программы лояльности регистрироваться в Telegram за минуту, без визита в магазин и ручного заведения карты, с автоматическим попаданием карты в дисконтную систему 1С:Розницы на кассе.

Результат

Telegram-бот и выпуск карт лояльности полностью работают внутри 1С:Розницы: без внешнего сервера бота, без платных коннекторов и ручного труда продавца. Токен, режим работы и шаблон карты настраиваются в конфигурации, весь трафик и ошибки видны в журнале. Транспортный слой Bot API переиспользуем для других конфигураций.

Разработка Telegram-бота на 1С обычно уводит команду в сторону: ставят Node.js-прослойку, стороннюю CRM или платный коннектор, а потом синхронизируют это с учётной системой. Мы пошли иначе и собрали Telegram-бота программы лояльности прямо внутри конфигурации 1С:Розница — без единого внешнего сервиса. Покупатель детской розничной сети открывает бота, делится номером телефона, и 1С сама заводит ему карту лояльности. Ниже — как устроена интеграция 1С с Telegram Bot API на чистом встроенном языке и почему это оказалось надёжнее «прослоек».

Задача

У розничной сети детских товаров работала программа лояльности на дисконтных картах в 1С:Рознице. Нужно было дать покупателю возможность зарегистрироваться в клубе за минуту, без визита в магазин и без ручного заведения карты продавцом. Классический канал для этого — Telegram: покупатель нажимает «Старт», делится телефоном, получает карту. Требование бизнеса — чтобы регистрация сразу попадала в 1С и карта была совместима с уже действующей дисконтной системой на кассе.

Решение: Bot API целиком на BSL

Вместо внешнего бота мы реализовали слой Telegram Bot API прямо в конфигурации, в отдельной подсистеме с префиксом tl_. Базовый модуль отправки — единственная точка выхода в сеть: он открывает HTTPS-соединение с api.telegram.org через ЗащищённоеСоединениеOpenSSL, подставляет токен бота из константы и собирает адрес вида bot<token>/<method>. Обычные вызовы уходят как GET с query-параметрами, а отправка файлов (например, изображения карты) — как multipart/form-data с вручную сформированным boundary.

Функция ОтправитьHTTPЗапрос(method, method_param = Неопределено, Данные = Неопределено) Экспорт
    Результат = Неопределено;
    Попытка
        СоединениеHTTP = Новый HTTPСоединение("api.telegram.org", 443, , , , , Новый ЗащищенноеСоединениеOpenSSL());

        ПараметрыЗапроса = Новый Соответствие;
        ПараметрыЗапроса.Вставить("token", tl_ПовтИсп.ПолучитьToken());
        ПараметрыЗапроса.Вставить("method", method);
        ПараметрыЗапроса.Вставить("method_param", method_param);

        HTTPЗапрос = Новый HTTPЗапрос;
        Если Данные = Неопределено Тогда
            HTTPЗапрос.Заголовки.Вставить("Content-type", "application/json");
        Иначе
            ТекстЗапроса = СформироватьТелоЗапроса(Данные);
            HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(ТекстЗапроса);
            HTTPЗапрос.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Данные["Boundary"]);
        КонецЕсли;
        HTTPЗапрос.АдресРесурса = СформироватьМетод(ПараметрыЗапроса); // bot<token>/<method>?params

        Если Данные = Неопределено Тогда
            РезультатЗапроса = СоединениеHTTP.Получить(HTTPЗапрос);
        Иначе
            РезультатЗапроса = СоединениеHTTP.ОтправитьДляОбработки(HTTPЗапрос);
        КонецЕсли;

        Если РезультатЗапроса.КодСостояния = 200 Тогда
            Результат = РезультатЗапроса.ПолучитьТелоКакСтроку();
        Иначе
            РегистрыСведений.tl_Логи.ЛогHTTP(РезультатЗапроса);
        КонецЕсли;
    Исключение
        РегистрыСведений.tl_Логи.ЗаписатьОшибку(ИнформацияОбОшибке(), Истина);
    КонецПопытки;
    Возврат Результат;
КонецФункции

Поверх этой функции лежит библиотека методов Bot API — sendMessage, sendPhoto, getUpdates, setWebhook, answerInlineQuery и другие. Каждый метод только собирает Соответствие параметров и вызывает базовую отправку, а ответ разбирает штатным ПрочитатьJSON с кастомным преобразованием Unix-поля date в дату 1С.

Два режима приёма обновлений: webhook и long polling

Бот умеет работать обоими способами, которые поддерживает Telegram. В боевом режиме — webhook: публикуется HTTP-сервис 1С, Telegram сам шлёт на него POST с каждым обновлением. Обработчик — единая точка входа, которая маршрутизирует апдейт по типу (message, callback_query, inline_query) и всегда возвращает 200: любой другой код заставит Telegram повторять доставку, поэтому ошибки мы гасим и пишем в журнал, но наружу отдаём успех.

Функция ОбработатьВходящийHTTPЗапрос(Запрос) Экспорт
    Попытка
        РегистрыСведений.tl_Логи.ЛогHTTP(Запрос);
        Данные = tl_Служебные.ОбработатьJSON(Запрос.ПолучитьТелоКакСтроку());

        Если НЕ Данные["inline_query"] = Неопределено Тогда
            Возврат Обработать_inline_query(Данные["inline_query"]);
        ИначеЕсли НЕ Данные["message"] = Неопределено Тогда
            Возврат Обработать_message(Данные["message"]);
        ИначеЕсли НЕ Данные["callback_query"] = Неопределено Тогда
            Возврат Обработать_callback_query(Данные["callback_query"]);
        Иначе
            Возврат Новый HTTPСервисОтвет(200);
        КонецЕсли;
    Исключение
        // Telegram повторяет доставку при коде != 200 — всегда отвечаем 200,
        // а ошибку фиксируем в журнале.
        Возврат Новый HTTPСервисОтвет(200);
    КонецПопытки;
КонецФункции

Второй режим — long polling через регламентное задание: по расписанию 1С сама опрашивает getUpdates, начиная со смещения, сохранённого в константе tl_UpdateID. Это удобно на стендах, где нет «белого» IP и валидного сертификата под webhook. Один и тот же код обработки сообщений переиспользуется в обоих режимах.

Регистрация покупателя по номеру телефона

Сценарий лояльности прост для покупателя и полностью автоматичен со стороны 1С. На команду /start бот присылает клавиатуру с единственной кнопкой, у которой выставлен флаг request_contact — Telegram сам предлагает поделиться номером. Как только приходит объект contact, данные (имя, телефон, chat_id, user_id) складываются в регистр сведений tl_Карты — это очередь на выпуск карт.

Процедура Обработать_message(message)
    chat = message["chat"];
    id = chat["id"];
    Если message["text"] = "/start" Тогда
        Сообщение = "Для регистрации в программе лояльности нажмите кнопку под сообщением."
                  + " В течение 1 минуты придёт подтверждение регистрации.";
        tl_Методы.sendMessage(id, Сообщение, , , , , СформироватьКлавиатуруРегистрация());
    ИначеЕсли НЕ message.Получить("contact") = Неопределено Тогда
        Результат = ЗарегистрироватьКартуЛояльности(id, message.Получить("contact"));
        Если Результат = Ложь Тогда
            tl_Методы.sendMessage(id, "Ошибка регистрации. Обратитесь к администратору.");
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

Функция СформироватьКлавиатуруРегистрация()
    МассивРядов  = Новый Массив;
    МассивКнопок = Новый Массив;
    // request_contact = Истина — Telegram сам просит поделиться номером телефона
    МассивКнопок.Добавить(tl_Типы.KeyboardButton("Отправить телефон для регистрации", Истина));
    МассивРядов.Добавить(МассивКнопок);
    Возврат tl_Типы.ReplyKeyboardMarkup(МассивРядов, Истина, Истина);
КонецФункции

Автоматический выпуск карт лояльности

Отдельное регламентное задание разбирает очередь регистраций и заводит карты в транзакции. Телефон нормализуется к единому формату (учитываются варианты +7, 8 и казахстанская запись), проверяется по уже выпущенным картам — так исключается дубль, — и создаётся новая карта копированием эталонного шаблона из константы. Карта получает код, равный номеру телефона, и сразу работает на кассе. Покупателю уходит поздравление, а обработанная запись удаляется из очереди.

Функция СоздатьКарты() Экспорт
    Попытка
        НачатьТранзакцию();

        // Уже существующие карты — чтобы не выдать дубль
        Запрос = Новый Запрос("ВЫБРАТЬ ИнформационныеКарты.КодКарты ИЗ Справочник.ИнформационныеКарты КАК ИнформационныеКарты");
        ДанныеКарт = Новый Соответствие;
        Выборка = Запрос.Выполнить().Выбрать();
        Пока Выборка.Следующий() Цикл
            ДанныеКарт.Вставить(Выборка.КодКарты, Истина);
        КонецЦикла;

        НаборЗаписей = РегистрыСведений.tl_Карты.СоздатьНаборЗаписей();
        НаборЗаписей.Прочитать();
        Шаблон = Константы.tl_ШаблонКарты.Получить(); // эталонная карта-образец

        Для каждого Стр Из НаборЗаписей Цикл
            Телефон = ФорматироватьНомерТелефона(Стр.phone_number);
            Если ЗначениеЗаполнено(ДанныеКарт[Телефон]) Тогда
                tl_Методы.sendMessage(Стр.chat_id, "Вы уже зарегистрированы!");
                Продолжить;
            КонецЕсли;

            НоваяКарта = Шаблон.Скопировать();
            НоваяКарта.Наименование  = Телефон + "_бот";
            НоваяКарта.ВладелецКарты = Телефон;
            НоваяКарта.КодКарты      = Телефон;
            НоваяКарта.ДатаОткрытия  = ТекущаяДата();
            НоваяКарта.Записать();

            tl_Методы.sendMessage(Стр.chat_id, "Поздравляем! Вы успешно зарегистрированы!");
        КонецЦикла;

        НаборЗаписей.Очистить();
        НаборЗаписей.Записать(); // обработанные регистрации убираем из очереди
        ЗафиксироватьТранзакцию();
    Исключение
        Если ТранзакцияАктивна() Тогда ОтменитьТранзакцию(); КонецЕсли;
        РегистрыСведений.tl_Логи.ЗаписатьОшибку(ИнформацияОбОшибке(), Истина);
    КонецПопытки;
КонецФункции

Обмен, логирование (регистр tl_Логи со всеми HTTP-запросами и ответами), консоль разработчика и форма настройки токена — тоже часть подсистемы. Инлайн-режим реализован полностью: типы InputTextMessageContent, InputLocationMessageContent, InputVenueMessageContent и inline-клавиатуры вынесены в отдельный модуль на случай расширения бота.

Результат

Программа лояльности получила самостоятельный канал регистрации в Telegram, полностью живущий внутри 1С:Розницы: ни внешнего сервера бота, ни платных коннекторов, ни ручного заведения карт. Токен, режим (webhook/polling) и шаблон карты настраиваются в самой конфигурации, а весь трафик и ошибки видны в журнале. Подсистема получилась переиспользуемой — тот же слой Bot API можно навесить на любую типовую конфигурацию, где нужен Telegram-канал.

Смежный кейс по той же теме лояльности, но со стороны учёта в 1С:УТ — доработка программы лояльности с анкетой и автобонусами. Если нужен полноценный интерфейс внутри Telegram (Web App), а не только команды бота — это тема курса Telegram Web App 1С, где разбирается связка HTTP-сервиса 1С и мини-приложения Telegram.

Нужен Telegram-бот или Web App, встроенный прямо в вашу 1С? Мы проектируем и внедряем такие решения под ключ — начните с курса Telegram Web App 1С или напишите нам по задаче.

Частые вопросы

Нужен похожий проект?

Опишите задачу — оценю сроки и стоимость.