Відзначимо: коли бронювання скасовується за хвилину до початку, адміністратор змушений вручну звільняти слот і повертати гроші. При 30 таких скасуваннях на день втрати часу сягають 2 годин, а 70% операцій містять помилки. Клієнти не можуть швидко перенести запис — вони йдуть до конкурентів. Ми пропонуємо систему з гнучкими політиками, токен-посиланнями та автоматичними сповіщеннями, яка вирішує ці проблеми. Розглянемо реалізацію на прикладі Laravel. Автоматизація скасувань скорочує час обробки в 18 разів і економить до 40 годин на місяць — при вартості години адміністратора 500 грн це 20 000 грн щомісяця. А головне, ви впроваджуєте self-service bookings, знижуючи навантаження на підтримку на 80%.
Проблеми, які вирішує автоматичне скасування та перенесення
- Ручна обробка відмін — адміністратору доводиться вручну звільняти слоти, повертати гроші та сповіщати сторони. Кожна така операція займає 3–5 хвилин, а на день їх буває до 30. Автоматизація скорочує цей час втричі, економлячи до 40 годин на місяць.
- Зловживання скасуваннями — без лімітів клієнти можуть скасовувати броні за хвилину до початку, залишаючи порожні слоти. Нам потрібен захист: пороги безплатного скасування, штрафи та блокування при частих відмінах. Система обробляє 90% скасувань без участі людини, а штрафи за пізню відміну можуть становити 20–100% вартості.
- Втрачені клієнти — якщо користувач не може легко змінити запис, він іде до конкурентів. Перенесення має бути таким же простим, як скасування — з вибором нового часу та миттєвим підтвердженням.
Чому політика скасування має бути гнучкою?
Політика задається для кожної послуги окремо. Ось типові варіанти:
| Тип політики | Безплатне скасування за | Штраф при пізньому скасуванні | Повернення при неявці |
|---|---|---|---|
| Ліберальна | ≥ 24 години | 20% | 0% |
| Стандартна | ≥ 6 годин | 50% | 0% |
| Сувора | ≥ 1 година | 100% | 0% |
Ліберальна політика підходить для салонів краси, сувора — для медичних клінік. Реалізація на PHP/Laravel виглядає так:
Приклад реалізації політики скасування
class BookingCancellationPolicy
{
public function canCancel(Booking $booking): CancellationResult
{
$hoursUntilBooking = now()->diffInHours($booking->starts_at, absolute: false);
if ($hoursUntilBooking < 0) {
return CancellationResult::denied('Бронювання вже минуло');
}
$policy = $booking->service->cancellation_policy;
if ($hoursUntilBooking < $policy->free_cancel_hours) {
return CancellationResult::withPenalty(
"Скасування менш ніж за {$policy->free_cancel_hours} годин: " .
"штраф {$policy->penalty_percent}%",
penaltyPercent: $policy->penalty_percent
);
}
return CancellationResult::free();
}
}
Метод canCancel повертає об'єкт CancellationResult — з методом free(), withPenalty() або denied(). Далі контролер або Inertia-компонент вже вирішує, що показати користувачеві: кнопку «Скасувати безплатно», «Скасувати зі штрафом у X%» або повідомлення, що скасування неможливе.
Як налаштувати політику скасування за 5 кроків?
- В адмін-панелі виберіть послугу.
- Вкажіть поріг безплатного скасування (у годинах до початку).
- Задайте відсоток штрафу для пізнього скасування.
- Визначте, чи повертати гроші на баланс або на картку.
- Збережіть налаштування — зміни набувають чинності одразу.
Токен-посилання: відміна без реєстрації
Будь-яка авторизація — це бар'єр. Якщо клієнт забув пароль або сидить у чужому браузері, він не зможе скасувати запис. Рішення — токен-посилання. При створенні броні ми генеруємо два унікальних токени (для скасування та перенесення) і зберігаємо їх у моделі:
class Booking extends Model
{
protected static function booted(): void
{
static::creating(function (Booking $booking) {
$booking->cancel_token = Str::random(64);
$booking->reschedule_token = Str::random(64);
});
}
}
Токени передаються в листі як посилання виду /bookings/cancel/{token}. Роут знаходить бронювання по токену та відображає сторінку з інформацією та кнопкою скасування. Жодної реєстрації — лише перехід за посиланням і підтвердження. Такий підхід знижує навантаження на підтримку на 80%.
Як захиститися від зловживань?
Система відстежує кількість відмін з одного акаунта та часові інтервали. При підозрілій активності (наприклад, 5 скасувань за годину) бронь переводиться на ручну перевірку або блокується з повідомленням адміністратора. Також налаштовуються ліміти безплатних відмін: після вичерпання ліміту клієнт платить штраф. Для критичних послуг можна ввімкнути обов'язкове підтвердження адміністратором.
Атомарне перенесення: уникнення race condition
Перенесення складніше за скасування, оскільки потрібно атомарно звільнити старий слот і зайняти новий. Ключовий момент: новий слот резервується перед звільненням старого. Інакше два клієнти можуть одночасно перенестися на один і той самий час — класичний race condition.
Приклад реалізації перенесення
public function reschedule(Request $request, string $token): JsonResponse
{
$booking = Booking::where('reschedule_token', $token)->firstOrFail();
DB::transaction(function () use ($booking, $request) {
$newSlot = TimeSlot::findOrFail($request->new_slot_id);
// Перевіряємо доступність нового слота
if (!$newSlot->available) {
throw new SlotUnavailableException();
}
// Звільняємо старий слот
TimeSlot::where('booking_id', $booking->id)->update(['booking_id' => null]);
// Займаємо новий
$newSlot->update(['booking_id' => $booking->id]);
$booking->update([
'starts_at' => $newSlot->datetime,
'status' => 'rescheduled',
]);
});
// Відправляємо підтвердження перенесення
Mail::to($booking->customer_email)->send(new BookingRescheduledMail($booking));
return response()->json(['success' => true]);
}
Зверніть увагу: перевірка $newSlot->available — це не просто булеве поле, а обчислюваний атрибут, який дивиться booking_id і status слота. Якщо слот зайнятий — викидається SlotUnavailableException, транзакція відкочується, користувач отримує помилку. Гарантуємо відсутність race condition навіть при 50 паралельних запитах на перенесення.
Як тестувати паралельні запити?
Ми використовуємо навантажувальне тестування з Laravel Documentation та інструментом Apache Bench. Запускаємо 50 одночасних запитів з різними токенами на один і той самий слот. Перевіряємо, що лише один проходить, інші отримують помилку. Всі тести покривають критичний шлях скасування та перенесення.
Порівняння ручної та автоматичної обробки
| Параметр | Ручна обробка | Автоматична система |
|---|---|---|
| Час на скасування | 3-5 хвилин | 10 секунд |
| Ризик помилок | Високий | Мінімальний (у 5 разів менше) |
| Участь адміністратора | 100% | 10% |
| Можливість перенесення | По телефону | В один клік |
Автоматизація скасувань у 18 разів швидша за ручну обробку і майже повністю виключає помилки.
Що входить у роботу
Відзначимо: коли ви замовляєте у нас впровадження системи скасування та перенесення, ми робимо:
- Опис політик — для кожної послуги налаштовуємо пороги, штрафи та винятки.
- Backend-логіка — реалізація
CancellationPolicy, обробка запитів через контролери. - Токен-посилання — генерація, зберігання, роути та сторінки скасування/перенесення.
- Форма перенесення — інтеграція з компонентом
AvailabilityCalendarдля вибору нового слота. - Сповіщення — листи, push та SMS (опціонально) для всіх подій.
- Адмін-панель — список усіх скасувань та перенесень, ручне керування.
- Тестування — навантажувальні тести для перевірки паралельних запитів (гарантуємо відсутність race condition).
Строки та контакти
Скасування та перенесення бронювань з політиками та токен-посиланнями — 3–5 робочих днів. Перенесення з формою вибору часу та транзакційною безпекою — до 7 днів. Вартість впровадження базового функціоналу — від 15 000 грн. Зв'яжіться з нами — оцінимо ваш проект за 1 робочий день. Отримайте консультацію щодо впровадження self-service bookings для вашого бізнесу.
Система будується так, щоб легко вбудовуватися в існуючу архітектуру — чи то Laravel, Django або Express. А якщо у вас ще немає бронювання — реалізуємо все з нуля. Наша команда має 5-річний досвід у розробці booking-систем і виконала 20+ проектів з автоматизації запису. Замовте впровадження зараз — отримайте консультацію за 1 день.







