← На главную Портфолио
AI Automation · Бронирование

Бронирование отелей: от заявки до оплаты — без ручного труда

Туристическое агентство принимало заявки вручную: проверка номеров, письма, таблица, подтверждения. Я собрал пайплайн, который делает это сам — атомарно, без двойных броней и с онлайн-оплатой.

n8n 2.31.5 · self-hosted
Supabase · PostgreSQL 15
Robokassa API
Dokploy · Docker
Telegram Bot API
SMTP
ЗадачаЗаявки обрабатывались часами, номера бронировались дважды, неоплаченные брони блокировали номера.
Моя работаПайплайн из 4 workflow: атомарная бронь через SQL-функцию book_room(), Robokassa-оплата, письма и Telegram-отчёты.
Технологииn8n 2.31.5, Supabase (Postgres 15), Robokassa, Telegram Bot API, SMTP, Dokploy
РезультатЗаявка → оплата за секунды, 0 двойных броней (на уровне БД), автоотмена через 24 ч

Сквозной пайплайн данных

Семь нод превращают входящую заявку в подтверждённую оплату — без единого ручного действия.

Форма
idempotency-key
Webhook
POST /v2
Валидация
+ карантин
book_room()
SQL · атомарно
Robokassa
ссылка на оплату
Письмо
клиенту
Telegram
менеджеру
Форма бронирования отеля
СТАРТПользователь заполняет форму бронирования — система генерирует idempotency-key

01 · Заявки обрабатывались вручную — и номера бронировались дважды

Боль

Менеджер вручную проверял свободные номера, писал клиенту, обновлял таблицу и отправлял подтверждение. Заявки обрабатывались часами.

Из-за ручного обновления таблицы свободный номер могли забронировать дважды — конфликт, извинения, потеря клиента.

Неоплаченные брони висели вечно и блокировали номера для других гостей.

Решение

Приём заявки через форму — автоматически
Проверка свободных номеров в базе
Создание брони и смена статуса номера
Письмо клиенту со ссылкой на оплату
Уведомления и ежедневный отчёт менеджеру
Автооткрытие неоплаченных броней через 24 часа

02 · Спецификация решения

Всё развёрнуто на одном VPS через Dokploy (Docker). Секреты — в n8n Credentials и таблице AppConfig, ничего не хранится в коде.

Компонент Роль Версия
n8n Оркестрация: 4 workflow, webhook, schedule, Code-узлы 2.31.5 self-hosted
Supabase PostgreSQL: таблицы, триггеры, SQL-функции, RLS Postgres 15
Robokassa Приём платежей: ссылка на оплату + Result-уведомления sandbox → prod
Telegram Bot Мгновенные уведомления менеджеру + отчёт в 18:00 Bot API
SMTP Письма клиенту: подтверждение / отказ / оплата Mailtrap → боевой
Dokploy Деплой и окружения: n8n, Supabase, NocoDB Docker

03 · Четыре связанных сценария

Основной пайплайн обработки заявки и три сервисных. Каждый защищён от сбоев: ноды onError → continue, Global Error Handler, ретраи.

WF-01 · Обработка заявки ACTIVE

Принимает заявку, нормализует данные, атомарно создаёт бронь через book_room(), генерирует ссылку Robokassa, отправляет письмо и уведомление. Корректные HTTP-коды: 200 / 400 / 409 / 503.

IN_Webhook_Form → IN_Validate_Normalize → CORE_Book_Room_RPC → SWITCH_Booking_Result → CORE_Generate_Payment_Url → OUT_Email_Client → OUT_Notify_Manager

WF-02 · Приём оплаты ACTIVE

Принимает Result URL от Robokassa, проверяет MD5-подпись, подтверждает оплату и переводит бронь в confirmed.

WF-03 · Ежедневный отчёт ACTIVE

Каждый день в 18:00 (Europe/Moscow) отправляет менеджеру отчёт: новые брони, неподтверждённые, номера в reserved.

WF-04 · Автоотмена ACTIVE

Каждые 15 минут отменяет неоплаченные брони старше 24 часов и освобождает номера. Менеджер получает список отменённых броней в Telegram.

Сценарий обработки заявки
СКРИН 1Сценарий обработки заявки — OTELI_Bronirovanie_Otelei v2
Сценарий автоотмены
СКРИН 2Сценарий автоотмены просроченных броней
Сценарий ежедневного отчёта
СКРИН 3Сценарий ежедневного отчёта
Сценарий приёма оплаты
СКРИН 4Сценарий приёма оплаты (Robokassa Result URL → confirmed)

04 · Rooms и Bookings — сердце системы

6 таблиц, 8 SQL-функций, триггеры аудита и освобождения номеров. Ниже — живое превью данных.

Таблица public.Rooms

id Отель Тип Даты Цена Статус
3 Test Hotel Standard 01.08–31.08 10 000 ₽ reserved
4 Test Hotel Standard 01.08–31.08 10 000 ₽ reserved
6 Test Hotel Standard 01.08–31.08 10 000 ₽ free
7 Test Hotel Standard 01.08–31.08 10 000 ₽ free

Таблица public.Bookings

id Клиент Даты Бронь Оплата
9 Тест Форма 21.08–26.08 confirmed paid
11 Тест Оплата 23.08–28.08 reserved pending
6 Иван Тестов 14.08–19.08 cancelled pending
7 Иван Тестов 16.08–21.08 cancelled pending
Таблица Bookings в Supabase
СКРИН 5Таблица Bookings в Supabase
Таблица Rooms в Supabase
СКРИН 6Таблица Rooms в Supabase

05 · Защита от двойного брони — на уровне базы

Главный риск задачи — race condition: два клиента одновременно бронируют один номер. Решение — многослойное.

Атомарная транзакция [SQL]

Функция book_room() одной транзакцией: блокирует номер FOR UPDATE, проверяет status='free', создаёт броню. Любой сбой — полный откат.

Идемпотентность [UUID]

Форма генерирует idempotency_key. Повторная отправка той же заявки вернёт существующую бронь, а не создаст дубль.

Бизнес-защита от дублей [UNIQUE]

Уникальный индекс: один email + отель + тип + даты = одна активная броня. Даже с новым ключом дубль не пройдёт.

Обработка ошибок [try/catch]

Все внешние вызовы обёрнуты, ошибки логируются в workflow_errors, менеджер получает алерт. Ни одна заявка не теряется — невалидные уходят в карантин.

SQL — атомарное бронирование Supabase RPC
-- атомарное бронирование: guard от race condition UPDATE public."Rooms" SET status='reserved', updated_at=now() WHERE room_id = v_room.room_id AND status='free' RETURNING room_id, price; -- идемпотентность: один ключ = одна броня CREATE UNIQUE INDEX uq_bookings_idempotency ON public."Bookings"(idempotency_key); -- бизнес-защита от повторной брони CREATE UNIQUE INDEX uq_active_client_request ON public."Bookings"(email, hotel, dates) WHERE status IN ('reserved', 'confirmed');

06 · Robokassa: ссылка в письме → автоподтверждение

Клиент платит по ссылке из письма. Robokassa шлёт Result-уведомление, подпись проверяется, статус меняется сам.

1
Бронь созданаstatus: reserved, payment: pending
2
Генерация ссылки — MD5-подпись, сумма, InvId
3
Письмо клиенту — кнопка «Оплатить»
4
Оплата — страница Robokassa
5
Result URL → n8n — проверка подписи
6
paid + confirmed — менеджер уведомлён
Успешные executions в n8n
СКРИН 7Успешные executions в n8n
Письмо клиенту
СКРИН 8Реальное письмо клиенту со ссылкой на оплату
Ежедневный отчёт в Telegram
СКРИН 9Ежедневный отчёт менеджеру в Telegram

07 · Письмо клиенту и отчёт менеджеру

Письмо клиенту

От: admin@eduardwit.ru  ·  Кому: client@example.com

Тема: Бронирование подтверждено — Test Hotel

Здравствуйте, Тест Форма!
Ваше бронирование создано:
Отель: Test Hotel, Тип номера: Standard, Даты: 21.08.2026 → 26.08.2026, Стоимость: 10 000 ₽, Номер брони: #9
[ кнопка «Оплатить бронирование →» ]
После оплаты статус брони изменится автоматически. Ждём вас!

Отчёт менеджеру (18:00)

📊 ЕЖЕДНЕВНЫЙ ОТЧЁТ ПО БРОНИРОВАНИЯМ
🗓 01.08.2026 | 18:00
━━━━━━━━━━━━━━━━━━
✅ 1. НОВЫЕ БРОНИ ЗА ДЕНЬ
• #9 | Тест Форма | Test Hotel | Standard | 21.08 → 26.08 | 10 000 ₽
⏳ 2. НЕПОДТВЕРЖДЁННЫЕ БРОНИ
• #11 | Тест Оплата | оплата: pending
🚧 3. НОМЕРА В СТАТУСЕ reserved
• #3 | Test Hotel | Standard | 10 000 ₽
📌 ИТОГО: новых 3 · неоплаченных 5 · reserved 5

08 · Успешные запуски и метрики

4
workflow active
8
SQL-функций
6
таблиц в БД
15 мин
автоотмена
24 ч
окно оплаты
100%
покрытие ошибок

09 · Что получил бизнес

Заявка обрабатывается за секунды, а не часы
Двойное бронирование исключено на уровне БД
Оплата онлайн — статус меняется автоматически
Неоплаченные брони не блокируют номера
Менеджер видит всё в Telegram в реальном времени
Следующие шаги Боевой SMTP вместо Mailtrap · реальные ключи Robokassa · форма на домене + CORS · Cloudflare Turnstile · NocoDB-кабинет менеджера · подтверждение оплаты кнопкой в Telegram.

Для Бизнеса

  • ✅ Заявка → оплата без ручного труда, поток заказов не теряется.
  • ✅ Ноль двойных броней — конфликты и извинения исключены.
  • ✅ Оплата онлайн сразу фиксирует клиента.
  • ✅ Неоплаченные брони не забирают номера у платящих гостей.

Для Инженерии

  • ✅ Атомарные транзакции + уникальные индексы в PostgreSQL.
  • ✅ Идемпотентность (idempotency_key) защищает от дублей.
  • ✅ Корректные HTTP-коды: 200 / 400 / 409 / 503.
  • ✅ Global Error Handler + карантин для невалидных заявок.