intermediate
shop-rentals
cancellation
refunds

Cancellation Handling

Free-cancel windows, cancellation fees, automatic deposit refunds, and the differences between operator-cancel and customer-cancel

Levy Fleets TeamMay 7, 20266 min read

When a customer or operator cancels a booking, the system handles three things in one transaction: status update, cancellation fee calculation, and any auto-refund due. This guide covers all three.

Two cancel paths

PathTriggerWho cancels
Operator cancelCancel Booking on booking detailStaff member at the dashboard
Customer cancelCancel on the manage-link pageRider with their /manage/[token] URL

Both paths funnel through the same backend logic. The only operational difference is cancelled_by — set to 'admin' for operator cancels, 'customer' for customer self-serve.

How the fee is calculated

The system reads the pricing snapshot that was bound to the reservation at booking time. From the snapshot:

  • free_cancellation_hours (e.g., 24)
  • cancellation_fee_percent (e.g., 25%)
  • non_refundable_deposit flag (optional)

Then:

  • Within the free window (cancellation > free_hours before pickup): fee = $0
  • Outside the free window: fee = base_cost_cents * cancellation_fee_percent / 100
  • If non_refundable_deposit=true: fee = max(fee, deposit) — deposit is always retained

The fee is rounded up to the next cent.

What gets auto-refunded

After the fee is calculated, the system computes:

amount_paid_cents
- cancellation_fee_cents
- already_refunded_cents
= refund_due_cents

If refund_due_cents > 0 and the customer has a customer record, the system automatically issues a wallet refund for that amount through the same source-of-truth flow as manual refunds.

So a typical flow:

  • Customer paid $50 deposit upfront
  • Cancellation within free-cancel window → fee = $0
  • Auto-refund = $50 to wallet
  • Reservation status → cancelled

Or:

  • Customer paid $50 deposit upfront, full $200 paid
  • Cancellation outside free-cancel → fee = 25% of $200 = $50
  • Auto-refund = $200 - $50 = $150 to wallet
  • Reservation status → cancelled

What's never auto-refunded

  • Card refunds are never automatic — auto-refund always goes to wallet. If the customer wants their card refunded instead, the operator must manually do that after the cancel via the Refund modal.
  • Custom adjustments — anything in adjustment_cents (damage charges, late fees) is included in the refund-due math but the operator may want to retain specific adjustments. Override by manually refunding less.

Operator cancel flow

  1. Open the booking detail
  2. Click Cancel Booking (only visible for pending / confirmed / checked_in)
  3. Type an optional reason
  4. Click Confirm Cancel

The status flips to cancelled, the cancellation fee is computed and stored on the reservation, the auto-wallet-refund (if applicable) processes, and the page refreshes.

If the auto-refund fails (e.g., wallet helper error), the cancellation itself stays — only the refund step rolls back. The operator can manually issue the refund later from the (now-completed) booking detail.

Customer cancel flow

The customer opens their manage-link (/manage/[token]) — sent in the booking confirmation email. If their booking is in pending or confirmed status, they see a Cancel this booking button.

Clicking it:

  1. Opens an inline reason field
  2. Posts to /api/public/manage/[token] with action: cancel
  3. Same backend logic runs — fee calc + auto-refund

The customer sees a green confirmation banner. If a refund was issued, they're told it'll appear in their wallet shortly.

For more on customer self-serve, see Customer self-serve manage link.

What blocks a cancel

StatusCancellable?
pending
confirmed
checked_in
active❌ — bike is already out, treat as a return + partial refund
completed❌ — handled via post-hoc refund
cancelled❌ — already cancelled
no_show❌ — can't cancel after the no-show window expired
expired❌ — same

If a customer wants to cancel an active rental, complete the return and issue a partial refund for the unused time.

Visible to the customer

After cancellation, the customer's manage-link page shows:

  • Status: cancelled (red badge)
  • "Booking cancelled" banner
  • The amount auto-refunded (if any)
  • "Any eligible refund will appear in your wallet shortly."

The customer doesn't see internal details like "cancellation fee was $50" — your customer-facing cancellation policy text on the public booking page should set expectations upfront.

Email notifications

The cancel flow sends a booking_cancelled notification (push + email if configured). The notification includes the cancellation fee charged and the refund amount.

You can override the email template per-subaccount in subaccount_email_templates — see your subaccount settings.

Reporting cancellations

SELECT
  date_trunc('day', cancelled_at) AS day,
  cancelled_by,
  count(*) AS cancellations,
  sum(cancellation_fee_cents) / 100.0 AS total_fees_kept
FROM reservations
WHERE status = 'cancelled'
  AND cancelled_at >= '2026-05-01'
GROUP BY 1, 2
ORDER BY 1, 2;

Track these monthly — high cancellation rates can signal pricing or expectations mismatches in your public booking flow.

Edge cases

Customer cancels and re-books

Two separate reservations. The cancelled one keeps its fee; the new one charges fresh. Operators sometimes credit the cancellation fee to the new booking via a manual partial refund — that's a goodwill gesture, not automatic.

Operator cancels for inventory reasons

If you have to cancel a customer's booking because a bike broke or your shop unexpectedly closed, don't charge the cancellation fee. After the cancel, manually refund the cancellation fee amount as a partial refund with reason "Operator cancellation — fee waived."

Cancel after the no-show cron has flipped the booking

Once a booking is no_show, the cancel path no longer applies. Use a manual refund if your policy allows it.

Cancel during checked_in (pickup paperwork started)

Allowed. The customer is at the counter, paperwork is started, but they've changed their mind. The free-cancel window has obviously passed (you're at pickup time), so the cancellation fee will apply per your policy. Some shops waive this with a partial refund.