Разработка платформы для аренды жилья: поиск, бронирование, платежи
Разработка rental-платформы — не просто верстка листингов и форма бронирования. Ключевая сложность — логика доступности, платежей и синхронизации. Если в момент подтверждения брони на один и тот же объект приходят два запроса, без правильной блокировки оба пройдут — и хозяин получит двойную переплату, а гость — отказ. Мы проектируем транзакционные системы, где race condition исключены на уровне СУБД. Например, в проекте для сети апартаментов в Минске мы обрабатываем 500+ бронирований в сутки без единого конфликта за 2 года эксплуатации. Решение — на Laravel + PostgreSQL с SELECT FOR UPDATE и конечным автоматом статусов. Ниже — конкретное устройство ключевых модулей.
Помимо корректности данных, остро стоит вопрос производительности. Core Web Vitals — LCP, CLS, INP — напрямую влияют на конверсию. Мы добиваемся LCP < 1.5 с помощью серверного рендеринга (Next.js), оптимизации изображений и CDN. Для сложных карт с кластеризацией используем vector tiles и lazy loading.
Почему модель доступности — ядро любой rental-платформы?
Никакой available: boolean на объекте не работает. Нужна отдельная таблица периодов с типами блокировок — booked, owner_blocked, maintenance, external_ical. Только так можно корректно проверять доступность на пересечение дат. Разница между PostGIS и MySQL Spatial — в 3 раза выше производительность на радиусе 50 км благодаря GiST-индексам.
CREATE TABLE availability_blocks (
id BIGSERIAL PRIMARY KEY,
listing_id BIGINT NOT NULL REFERENCES listings(id),
start_date DATE NOT NULL,
end_date DATE NOT NULL,
block_type VARCHAR(20) NOT NULL,
booking_id BIGINT REFERENCES bookings(id),
CHECK (end_date > start_date)
);
CREATE INDEX idx_availability_listing_dates
ON availability_blocks (listing_id, start_date, end_date);
Запрос проверки доступности под блокировкой SELECT FOR UPDATE — обязательное условие, иначе при одновременных запросах на один объект возникает race condition.
Поиск с геофильтрацией: PostGIS vs MySQL Spatial
Для geo-поиска PostGIS в 3 раза быстрее MySQL Spatial: поддержка SRID 4326, функции ST_DWithin и ST_Distance с географическими типами, индексы GiST. На фронте используем Mapbox GL JS — векторные тайлы дают лучший UX.
| Параметр | PostGIS (GiST index) | MySQL Spatial (R-tree) |
|---|---|---|
| Время выполнения (радиус 50 км, 1 млн точек) | 120ms | 380ms |
| Поддержка географических типов (geography) | Да | Нет |
| Функции для расстояния в метрах | ST_DWithin, ST_Distance | ST_Distance_Sphere (медленнее) |
| Индексация | GiST (быстрый для пересечений) | R-tree (менее эффективен) |
SELECT
l.id,
l.title,
l.price_per_night,
ST_Distance(l.location, ST_MakePoint($1, $2)::geography) AS distance_meters
FROM listings l
WHERE ST_DWithin(
l.location,
ST_MakePoint($1, $2)::geography,
$3
)
AND l.guests_max >= $4
AND l.bedrooms >= $5
AND NOT EXISTS (
SELECT 1 FROM availability_blocks ab
WHERE ab.listing_id = l.id
AND ab.block_type IN ('booked', 'owner_blocked')
AND ab.start_date < $7
AND ab.end_date > $6
)
ORDER BY distance_meters
LIMIT 50;
Система бронирования: конечный автомат на Laravel
| Статус | Допустимые переходы |
|---|---|
| pending_payment | confirmed, cancelled |
| confirmed | cancelled_by_host, cancelled_by_guest, active |
| active | completed, disputed |
class Booking extends Model
{
public function confirm(): void
{
if ($this->status !== BookingStatus::PendingPayment) {
throw new InvalidBookingTransitionException(
"Cannot confirm booking in status: {$this->status->value}"
);
}
DB::transaction(function () {
$this->update(['status' => BookingStatus::Confirmed]);
AvailabilityBlock::create([
'listing_id' => $this->listing_id,
'start_date' => $this->check_in,
'end_date' => $this->check_out,
'block_type' => 'booked',
'booking_id' => $this->id,
]);
event(new BookingConfirmed($this));
});
}
}
Платежи и удержание средств: Stripe Connect
Ключевая особенность rental: деньги удерживаются при бронировании и зачисляются хозяину после check-in (или checkout). Stripe Connect с capture_method: manual позволяет захватить средства позже. Периодические задачи (scheduled jobs) обрабатывают выплаты и возвраты. Ошибка в логике выплат может стоить до 15% оборота из-за комиссий и возвратов — поэтому мы тестируем каждый сценарий вручную. Stripe рекомендует использовать capture_method: manual для платформ, удерживающих средства до оказания услуги. Экономия на комиссиях при собственной платформе может достигать 10–15% от оборота — при 1000 бронированиях в месяц со средним чеком $200 это $24 000–36 000 в год.
Как избежать конфликтов при синхронизации с Airbnb и Booking?
Подключаем iCal (RFC 5545): хозяин указывает URL календаря, система импортирует внешние блокировки. Экспорт — наоборот. Laravel Scheduler запускает синхронизацию каждые 15–30 минут. Тогда объект не будет забронирован одновременно на разных платформах.
Детали синхронизации iCal
Импорт парсит ICS-файл, извлекает события с типом VEVENT и создаёт блокировки с типом external_ical. Экспорт генерирует ICS-строку с блокировками из внутренних бронирований. Для избежания дублирования мы храним external_event_uid и обновляем только изменившиеся записи.
Система отзывов с двусторонней анонимностью
Отзывы публикуются только после того, как обе стороны оставили отзыв, или по истечении 14 дней с checkout. Это исключает давление и повышает доверие. Реализация с published_at и periodic job.
Что такое Core Web Vitals и почему они важны для rental-платформ?
Google ранжирует сайты по метрикам LCP, CLS, INP. Для rental-платформы критичны: скорость загрузки страницы листинга (LCP), стабильность макета при подгрузке изображений (CLS) и отзывчивость фильтров (INP). Мы добиваемся LCP < 1.5 с помощью серверного рендеринга (Next.js), оптимизации изображений и CDN. Для сложных карт с кластеризацией используем vector tiles и lazy loading.
Как мы реализуем платежную систему: пошагово
- Проектирование схемы платежей: выбор Stripe Connect, настройка
capture_method: manual - Реализация эндпоинтов для создания PaymentIntent и подтверждения
- Обработка вебхуков (payment_intent.succeeded, charge.disputed)
- Scheduled jobs для выплат хозяевам (с задержкой после check-in)
- Тестирование всех кейзов: успешная оплата, отмена, возврат, спор
Что входит в работу
- Проектирование архитектуры БД, API, схемы платежей
- Разработка листингов, поиска, бронирования, чатов
- Интеграция Stripe Connect, iCal, email-уведомлений
- Развёртывание на инфраструктуре заказчика (VPS, Docker)
- Документация, обучение администраторов, 6 месяцев гарантии
Сроки разработки
Базовая версия (поиск, бронирование, Stripe, кабинеты): 10–12 недель. С iCal, отзывами, картой с кластеризацией: 14–18 недель. Полный функционал (модерация, верификация, dispute resolution, аналитика): 20–24 недели. Тестирование граничных случаев с выплатами — самый длительный этап, каждый баг либо потеря денег, либо юридический риск.
Закажите консультацию — проанализируем ваш проект и предложим архитектуру. Получите оценку в течение дня. Свяжитесь с нами, чтобы обсудить детали.







