Реализация отмены и переноса бронирования на сайте
Возможность самостоятельно отменить или перенести запись снижает нагрузку на администраторов и улучшает опыт клиента. Ключевая задача — правильная логика: отмены за N часов до записи разрешены, отмены в последний момент — нет или со штрафом.
Правила отмены
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();
}
}
Токен-ссылки для отмены без авторизации
Клиент может отменить запись по ссылке из email без входа в аккаунт:
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);
});
}
}
// Роут для отмены
Route::get('/bookings/cancel/{token}', function (string $token) {
$booking = Booking::where('cancel_token', $token)
->where('status', 'confirmed')
->firstOrFail();
$policy = app(BookingCancellationPolicy::class)->canCancel($booking);
return Inertia::render('Booking/Cancel', [
'booking' => $booking->load('service'),
'policy' => $policy,
]);
});
Форма переноса
При переносе открывается полноценный выбор новой даты/времени (тот же компонент AvailabilityCalendar), но старый слот освобождается только после подтверждения нового:
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]);
}
Сроки
Отмена и перенос бронирований с политиками и токен-ссылками: 3–5 рабочих дней.







