Демо-проект
Бот записи в барбершоп: BROMID
Запись за четыре нажатия, перенос и отмена прямо в чате, REST API для админки и мобильного приложения.
бэкенд и бот · Go · PostgreSQL · Telegram Bot API · Telegram Stars
в работеНет кадров диалога и аналитического API

Задача
BROMID - барбершоп в Екатеринбурге: четыре мастера, работа ежедневно с 10:00 до 22:00. Проект демонстрационный: студия вымышленная, расписание посевное. Задача при этом типовая для ниши. Запись по телефону упирается в администратора: он стрижёт, отвечает другому клиенту или уже ушёл домой, а человек, который не дозвонился, записывается к соседям. Нужен канал, где клиент записывается сам, за минуту и в любое время, а студия видит ту же запись, что и он.
Решение
- Запись за четыре нажатия: услуга, мастер, день, время. Первое же сообщение бота - прайс, отдельного шага «записаться» нет. Подтверждение приходит карточкой: услуга, мастер, цена, адрес.
- Кнопка «Любой мастер - кто раньше освободится» собирает объединённое расписание всех мастеров и отдаёт слот первому свободному.
- Перенос и отмена живут там же: под карточкой записи кнопки «Перенести» и «Отменить запись». Звонить студии не нужно.
- Прайс из восьми услуг, от воска на 15 минут до комплекса на 90. Сетка с шагом 15 минут, запись открыта на 14 дней вперёд, ближайший слот - не раньше чем через полчаса: клиенту надо доехать, мастеру - закончить предыдущего.
- Программа лояльности «каждая шестая стрижка бесплатно» подписана прямо под карточкой: «Стрижек в копилке: 3. До бесплатной осталось 2». В зачёт идут только стрижки, борода и воск - нет.
- Депозит - необязательный шаг после подтверждения записи: счёт на одну звезду Telegram, которая возвращается сразу после списания. Бот прямо говорит, что на саму запись депозит не влияет и шаг можно пропустить. Механика настоящей оплаты показана без договора с платёжным провайдером.
- То же ядро отдаёт REST API: расписание на день или неделю, фильтры по мастеру и статусу, смена статуса и перенос - для админской стороны; отдельный префикс со своими токенами - для мобильного приложения, которое на этом API и работает.
Детали, которые легко не заметить
- Гонку за последний слот разрешает база, а не проверка в коде: ограничение Postgres не даёт двум записям к одному мастеру пересечься по времени. Бот, приложение и API пишут параллельно, и проигравший получает «Это время только что заняли» вместе с новыми вариантами, а не сообщение о сбое.
- Диалог не хранит состояние на сервере: контекст шага едет внутри самой кнопки, в 64 байтах, которые на неё отводит Telegram. Перезапуск бота не рвёт начатую запись, а протухших сессий, которые надо чистить, попросту нет.
- Закрытая запись не переоткрывается: «пришёл», «неявка» и «отмена» терминальны, иначе состоявшийся визит можно было бы задним числом превратить в неявку и переписать выручку. Начавшуюся запись не переносят и не отменяют, её закрывают статусом - иначе неявки исчезнут из статистики.
- Время записи проверяется теми же правилами, по которым строится список свободных кнопок: клиент не может нажать предложенное время и получить отказ.
- Телефон просят после того, как запись создана: подтверждение клиент видит сразу, номер оставляет одним нажатием. Чужой контакт из адресной книги не принимается - телефон должен принадлежать тому, кто записывается.
- Напоминания за сутки и за пару часов до визита по умолчанию выключены: рассылка настоящая, включать её молча нельзя. Отметка о напоминании ставится в базе до отправки, поэтому перезапуск процесса не превращается во второе сообщение тому же клиенту.
- Ключ идемпотентности оплаты - идентификатор списания из Telegram, и держит его первичный ключ таблицы, а не проверка в коде: повторная доставка уведомления не проводит депозит дважды и не возвращает звезду дважды.
- Стенд убирает за собой сам: ночью фоновая задача удаляет отработавшие демо-записи и заново расстилает посевное расписание, поэтому наутро расписание снова выглядит живым и заполненным.
- Клиентское API приложения не отдаёт чужих клиентов: полей с именем и телефоном клиента в карточках записей нет, а на чужую запись оно отвечает «не найдено», а не «запрещено»: по коду ответа не должно быть видно, что она вообще существует.
Что осталось
Сценарий записи, перенос, отмена, лояльность, депозит, напоминания и ночная гигиена написаны и покрыты тестами. Не закрыто два пункта. Аналитическая часть API - клиенты с историей визитов и агрегаты по мастерам - ещё не сделана. И галерея неполная: кадров самого диалога в ней пока нет.

