All playbooksBooking Platform

Add a no-refund window

Let each service define the last hours before the appointment in which a customer cancellation is no longer refunded.

When to use it

Today a customer who cancels late gets late_cancellation_refund_percent of the amount paid, up to the start of the appointment. This playbook adds a per-service window (for example the last 4 hours) in which the refund drops to zero.

Rules to keep

  • Migrations are append-only: never edit 202610060001_booking_domain.sql, add a new file.
  • The refund decision is pure and lives in refundForCancellation; workflows.ts applies it and must not compute amounts itself.
  • Staff cancellations always refund in full.
  • A refund already decided (refundTargetCents) never decreases.

Steps

  1. Create supabase/migrations/<YYYYMMDDHHMM>_service_no_refund_window.sql with a timestamp later than every existing migration:
    alter table public.services
      add column no_refund_hours integer not null default 0
        check (no_refund_hours between 0 and 720),
      add constraint services_no_refund_within_free_window
        check (no_refund_hours <= free_cancellation_hours);
    
    The default 0 keeps the current behaviour for existing services.
  2. In src/lib/booking/policy.ts:
    • add noRefundHours to BookingPolicy;
    • add "no_refund_window" to RefundReason;
    • in refundForCancellation, for a customer with less than noRefundHours left before the start, set reason no_refund_window and target 0. Leave the existing Math.max with refundTargetCents in place.
  3. In src/lib/booking/repository.ts, add no_refund_hours to ServiceRow, to the service pick of BookingDetails, to SERVICE_COLUMNS, to the services(...) select in BOOKING_DETAILS and to toPolicy.
  4. In the admin area, add a noRefundHours field to the service form in src/app/admin/catalog/page.tsx, and no_refund_hours: integer(formData, "noRefundHours", 0) to the insert in src/app/admin/actions.ts. The CHECK constraints validate the value.
  5. Explain the window to the customer in the cancellation box of src/app/book/[serviceId]/page.tsx, only when no_refund_hours > 0.
  6. In test/policy.test.ts, add noRefundHours to the shared policy and test:
    • no refund inside the window, with reason no_refund_window;
    • the late percentage still applies between the two thresholds;
    • staff still refunds in full inside the window;
    • an earlier refund target is not reduced.

Done when

  • the verification commands pass;
  • the migration applies on a Supabase project with existing services;
  • services created before the change behave exactly as before.