A course is a file you can sell a thousand times. An appointment is an hour that can only be sold once. That one difference shapes most of the booking system in VeloxonHub. The 30-second reel shows the flow from the outside. Here's what happens inside.
1. An appointment is a product with booking rules
The host creates an appointment in a 5-step drawer: details, type and host, availability, booking rules and pricing. Under the hood it's a scheduling product with appointment settings: duration, buffers, an advance-booking window, booking, cancellation and reschedule cutoffs, capacity per slot, an approval flag, whether multiple slots can be selected, and the Join window.
2. Free or paid is decided by the price, not a flag
If the appointment has no active price, the booking page shows a plain "Confirm Booking" action and calls the booking endpoint directly. Several slots are booked one by one, and on a partial failure the UI keeps only the slots that still need booking and says exactly which failed. Package appointments use pre-paid credits and go through a batch endpoint instead.
If there is a price, the selection goes into the same cart and checkout as every other product.
3. Slots belong to hosts
Each host has weekly availability rules plus exceptions (extra hours, blocked days, vacations) and site-wide holidays. The booking page loads a month of slots, and every slot carries its host_id. Choosing a host filters the calendar to that host's slots, so the client only sees times that host can actually take. Times are stored in UTC and shown in the visitor's time zone.
4. Multi-host selections are priced per slot
When multiple selection is on, a client can mix hosts: Monday with one host, Thursday with another. Pricing doesn't apply one rate to the whole selection. Each slot is resolved through its own host:
- if the host has active prices of their own, those win;
- otherwise the product's default price applies;
- only
activeprices count, so an archived host price never sets the total.
The result is a list of lines (time, host, amount) plus a total, and a flag if the selection mixes currencies. The server resolves the final amounts again at checkout, so the browser never decides what anyone pays. There's also a conflict check, so one person can't put two overlapping slots in the cart.
5. Join is a 10-minute window, decided by the backend
After booking, the call shows up in the client's schedule. What the main button does depends on the delivery method (join a room, get directions, check in, wait for a call), and the backend sends that as joining_details.action.
For a virtual call, Join only becomes active join_available_minutes_before the start. The default is 10 minutes (COALESCE(join_available_minutes_before, 10)), and the host can change it. A small ticker on the client re-checks every 15 seconds, so the button turns on without a reload.
What we'd tell someone building the same thing
- Let "free" fall out of the price data instead of a separate flag.
- Attach every slot to a host, and price every slot through its host.
- Never trust a price from the browser, and check for overlapping slots before checkout.
- Treat "join" as a window with a status check, not a link you hand out at booking time.
Try it with a free trial: https://veloxonhub.com. Happy to go deeper into availability exceptions or multi-host pricing in the comments.
Top comments (0)