Channex — это channel manager. Он синхронизирует доступность и цены вилл с Booking.com, Airbnb, Agoda и десятком других площадок. Без него владелец виллы вручную обновляет календари на каждом сайте. Забыл закрыть даты на Agoda после бронирования на Booking — получил double booking. Гости приезжают, а вилла занята.

Интеграция с Channex заняла два дня — 23 и 24 января. Около 90 коммитов. Это была самая сложная часть Villa Metrics.

Почему Channex

На рынке несколько channel manager-ов. Для Бали популярны Channex и Zodomus. Выбрал Channex как основной: у них REST API, webhook-и, нормальная документация. Но архитектуру сделал с прицелом на dual support — чтобы потом подключить и Zodomus без переписывания.

Абстрактный класс BaseChannelManager с методами sync_availability(), sync_rates(), process_booking(). Конкретные реализации — ChannexClient и будущий ZodomusClient. Паттерн Strategy, ничего революционного.

Channex API: первые грабли

У Channex строгие rate limits. 10 запросов в секунду на аккаунт. Звучит нормально, пока у тебя 3 виллы. А когда 30 — и надо синхронизировать доступность на 365 дней вперёд по каждой — 10 RPS превращаются в проблему.

Написал rate limiter на основе token bucket. Redis хранит состояние: количество доступных токенов и время последнего обновления. Каждый запрос к Channex проходит через limiter.

class ChannexRateLimiter:
    def __init__(self, redis_client, max_tokens=10, refill_rate=10):
        self.redis = redis_client
        self.max_tokens = max_tokens
        self.refill_rate = refill_rate  # tokens per second

    def acquire(self, tokens=1):
        # Atomic check-and-decrement via Lua script
        allowed = self.redis.eval(self.lua_script, 1,
            self.key, tokens, self.max_tokens,
            self.refill_rate, time.time())
        if not allowed:
            raise RateLimitExceeded()

Lua-скрипт внутри Redis — атомарная операция. Никаких race conditions между Celery workers.

Webhook-и: реальное время

Channex шлёт webhook-и при новых бронированиях, отменах, изменениях. Endpoint /api/webhooks/channex/ принимает POST, валидирует подпись, кладёт событие в очередь.

Обработка — в Celery task. Не в самом view, потому что webhook должен ответить 200 за секунду, иначе Channex начнёт retry-ить. А обработка бронирования — это создание записи, обновление доступности, уведомление управляющего, привязка гостя. Секундой тут не обойдёшься.

@shared_task(bind=True, max_retries=3)
def process_channex_webhook(self, payload):
    event_type = payload.get('event')
    if event_type == 'booking_new':
        process_new_booking(payload['data'])
    elif event_type == 'booking_cancelled':
        process_cancellation(payload['data'])
    elif event_type == 'availability_updated':
        sync_availability(payload['data'])

Retry с exponential backoff. Если Celery упал на середине — задача повторится через 60 секунд, потом через 300, потом через 900.

Синхронизация бронирований

Бронирование из Channex содержит: даты заезда/выезда, имя гостя, email, телефон, источник (Booking/Airbnb/Agoda), сумму, статус. Маппинг в локальную модель Booking — прямолинейный, но с нюансами.

Главный нюанс — привязка гостя. На прошлом дне я сделал guest matching. Теперь при каждом бронировании из Channex система ищет существующего гостя по email и телефону. Нашла — привязывает. Не нашла — создаёт нового. Guest auto-linking.

Второй нюанс — обновления. Гость поменял даты через Booking.com. Channex шлёт webhook с обновлением. Нельзя просто перезаписать бронирование — нужно сравнить поля, обновить только изменённые, пересчитать доступность виллы.

Мониторинг интеграции

Channel manager — это внешняя зависимость. Она ломается. API возвращает 500-е. Webhook-и перестают приходить. Rate limit меняется без предупреждения.

Сделал integration monitoring dashboard. Логирую каждый API-вызов: endpoint, статус ответа, время выполнения, размер payload. В Django Admin — таблица с фильтрами по статусу и дате.

Telegram alerts — мгновенные уведомления при проблемах. API вернул 5xx три раза подряд — сообщение в Telegram. Webhook не приходил больше часа — ещё одно. Задача Celery упала после всех retry — третье.

def check_webhook_health():
    last_webhook = WebhookLog.objects.order_by('-received_at').first()
    if not last_webhook:
        return
    hours_since = (now() - last_webhook.received_at).total_seconds() / 3600
    if hours_since > 1:
        send_telegram_alert(
            f'No Channex webhooks for {hours_since:.1f} hours'
        )

Сертификация

У Channex есть процесс сертификации для интеграций. Набор тестов: правильно ли обрабатываешь бронирования, отмены, rate plans, availability updates. Написал тесты, прогнал. Пара тестов упала — оказалось, неправильно обрабатывал частичные отмены (когда гость сокращает срок, а не отменяет полностью).

Починил. Тесты прошли. Но сертификацию отправлю позже — сначала нужен реальный аккаунт с виллами.

Что не получилось

Хотел сделать двустороннюю синхронизацию цен за один день. Не вышло. Channex API для rate plans — отдельная вселенная: rate plans, room types, derived rates, restrictions. Один GET-запрос возвращает вложенную структуру на 5 уровней. Отложил rate sync на следующую итерацию, ограничился синхронизацией доступности и бронирований.

Итог дней 4-5

90 коммитов за два дня. Полный клиент Channex API с rate limiter. Webhook-обработка через Celery. Автоматическая привязка гостей к бронированиям. Мониторинг с Telegram-алертами. Архитектура готова для второго channel manager.

Channex-интеграция — фундамент всей платформы. Без неё Villa Metrics — красивый интерфейс без данных. С ней — система, которая реально управляет виллами.