Booking Cancellation and Rescheduling
Ability to self-cancel or reschedule appointment reduces admin load and improves customer experience. Key task is correct logic: cancellations N hours before allowed, last-minute cancellations — not allowed or with penalty.
Cancellation Rules
class BookingCancellationPolicy
{
public function canCancel(Booking $booking): CancellationResult
{
$hoursUntilBooking = now()->diffInHours($booking->starts_at, absolute: false);
if ($hoursUntilBooking < 0) {
return CancellationResult::denied('Booking already passed');
}
$policy = $booking->service->cancellation_policy;
if ($hoursUntilBooking < $policy->free_cancel_hours) {
return CancellationResult::withPenalty(
"Cancellation less than {$policy->free_cancel_hours} hours: " .
"penalty {$policy->penalty_percent}%",
penaltyPercent: $policy->penalty_percent
);
}
return CancellationResult::free();
}
}
Token Links for Cancellation Without Authorization
Customer can cancel booking via email link without account login:
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);
});
}
}
// Cancellation route
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,
]);
});
Rescheduling Form
When rescheduling, full new date/time selection opens (same AvailabilityCalendar component), but old slot freed only after new confirmation:
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);
// Check new slot availability
if (!$newSlot->available) {
throw new SlotUnavailableException();
}
// Free old slot
TimeSlot::where('booking_id', $booking->id)->update(['booking_id' => null]);
// Book new slot
$newSlot->update(['booking_id' => $booking->id]);
$booking->update([
'starts_at' => $newSlot->datetime,
'status' => 'rescheduled',
]);
});
// Send reschedule confirmation
Mail::to($booking->customer_email)->send(new BookingRescheduledMail($booking));
return response()->json(['success' => true]);
}
Timeframe
Booking cancellation and rescheduling with policies and token links: 3–5 working days.







