Skip to main content

Booking Flow & Settings

Complete guide to the Salonnz booking system, covering customer booking flow, admin configuration, and technical implementation.

Overview

The booking system enables customers to book salon appointments through web and mobile apps with configurable settings that control the user experience.

System Components

Customer Booking Flow

The booking process consists of multiple steps, with some steps appearing conditionally based on admin settings.

Flow Diagram

Step-by-Step Flow

Step 1: Location Selection

Page: /[slug]/booking/page.tsx

Purpose: Select salon location (if multiple locations exist)

API Call:

GET /booking/get-location-list

Response:

{
"status": true,
"data": [
{
"id": 1,
"name": "Downtown Salon",
"address": "123 Main St",
"phone": "+1 234 567 8900"
}
]
}

Redux Action:

dispatch(setSelectedLocation(location))

Logic:

  • If only 1 location: Auto-select and navigate to service selection
  • If multiple locations: Show selection UI

Step 2: Service Selection

Page: /[slug]/booking/select-services/page.tsx

Purpose: Browse categories and select salon services

API Call:

POST /booking/get-service-by-location
Body: { location_id: number }

Response:

{
"status": true,
"data": [
{
"id": 1,
"name": "Haircuts",
"services": [
{
"id": 10,
"name": "Men's Haircut",
"price": "35",
"duration": 30,
"payment_type": 0,
"enable_online_booking": 1
}
]
}
]
}

Features:

  • Category filtering
  • Service add-ons (optional extras)
  • Pricing options (if service has variants)
  • Service details popup

Redux Actions:

dispatch(addService(service))      // Add to cart
dispatch(removeService(serviceId)) // Remove from cart
dispatch(addAddon(addon)) // Add optional addon

Step 3: Staff Selection (Conditional)

Page: /[slug]/booking/select-staff/page.tsx

Visibility: Only shown if staff_selection === 1 in booking settings

API Call:

POST /booking/get-service-staff
Body: {
service_id: number,
location_id: number
}

Response:

{
"status": true,
"data": [
{
"id": 5,
"name": "Sarah Johnson",
"rating": 4.8,
"image": "https://...",
"specialization": "Hair Styling"
}
]
}

Redux Action:

dispatch(setSelectedStaff(staff))

Step 4: Date & Time Selection

Page: /[slug]/booking/select-time/page.tsx

Purpose: Select appointment date and available time slot

Date Selection:

  • Calendar picker showing next 30 days
  • Disabled dates for salon closed days

API Call:

POST /booking/get-slot
Body: {
start_date: "2024/12/06",
end_date: "2024/12/06",
location_id: 1,
service_pricing_options: [
{
service_id: 10,
pricing_option_id: null,
staff_id: 5
}
]
}

Response:

{
"status": true,
"data": [
{
"time": "09:00 AM",
"available": true
},
{
"time": "09:30 AM",
"available": true
},
{
"time": "10:00 AM",
"available": false
}
]
}

Slot Calculation Logic (Backend):

  1. Get business hours for location
  2. Get staff working hours
  3. Get existing appointments
  4. Get block times
  5. Calculate available slots based on:
    • Booking interval (from settings)
    • Service duration
    • Staff availability
    • Existing bookings

Redux Action:

dispatch(setDateAndTime({ date: "2024/12/06", time: "09:00 AM" }))

Step 5: Authentication (Conditional)

Page: Login popup appears based on settings

Conditional Logic:

if (!isLoggedIn) {
if (guest_checkout === 0) {
// Must login or register
showLoginModal();
} else {
// Can continue as guest or login
showLoginOptions();
}
}

Login Options (based on settings):

  • Email/Password login
  • Google OAuth (if google_login_enable === 1)
  • Facebook OAuth (if facebook_login_enable === 1)
  • Guest checkout (if guest_checkout === 1)
  • Sign up (if enable_signup === 1)

API Calls:

POST /customer/login
POST /customer/register
POST /auth/google
POST /auth/facebook

Step 6: Review & Confirm

Page: /[slug]/booking/review-confirm/page.tsx

Purpose: Review booking details, add payment, and confirm

Displayed Information:

  • Selected services with prices
  • Selected staff (if applicable)
  • Date and time
  • Location details
  • Optional add-ons
  • Total price calculation

Pricing Breakdown:

{
subtotal: servicesTotal,
tax: (servicesTotal * location.service_tax) / 100,
deposit: depositAmount, // If payment_type = 1
total: subtotal + tax
}

Cancellation Policy (Conditional):

  • Shown if cancellation_key === 1
  • Checkbox must be checked to proceed
  • Loads policy from /business-contact-details

Step 7: Payment (Conditional)

Visibility: Based on service payment_type

Payment Types:

TypeValueDescriptionWhen Shown
Pay at Salon0No payment requiredDefault
Deposit1Partial payment upfrontdeposit_enable === 1 AND service.min_amount > 0
Save Card2Save card for futuresave_card_enable === 1
Pay Later3Book now, pay after servicepay_later_enable === 1

Stripe Integration:

For deposit payments:

// 1. Create payment intent
const { token } = await getToken({
amount: depositAmount
});

// 2. Confirm payment with Stripe
const { paymentIntent } = await stripe.confirmPayment({
elements,
confirmParams: { return_url: confirmationUrl }
});

// 3. Update appointment status
await updatePaymentStatus({
appointment_id: apptId,
payment_intent: paymentIntent.id
});

Redux Actions:

dispatch(setPaymentMethod(1))       // Set payment type
dispatch(setDepositAmount(amount)) // Set deposit amount
dispatch(setPaymentToken(token)) // Store Stripe token

Step 8: Confirmation

Page: /[slug]/booking/confirmed/page.tsx

Purpose: Show booking confirmation

Displayed Information:

  • Booking ID
  • Appointment details
  • QR code for check-in
  • Add to calendar button
  • Email confirmation sent

API Response (from saveBooking):

{
"status": true,
"message": "Appointment created successfully",
"data": {
"id": 1234,
"appointment_number": "APT-1234",
"status": 1,
"date": "2024/12/06",
"time": "09:00 AM"
}
}

Admin Configuration

Booking Settings Panel

Location: Admin Panel → Settings → Front-End Booking

File: Frontend-Admin-Panel/pages/setting/front-end-booking/index.vue

Available Settings

1. Guest Checkout

Field: guest_checkout
Type: Toggle (0/1)
Description: Allow customers to book without creating an account

Impact on User Flow:

  • Enabled (1): Shows "Continue as Guest" button
  • Disabled (0): Forces login/registration

Use Case: Enable for walk-in customers who want quick bookings


2. Booking Interval

Field: booking_interval
Type: Select (5-240 minutes)
Description: Time slot intervals for appointments

Options: 5, 10, 15, 20, 30, 45, 60, 90, 120, 180, 240 minutes

Impact: Determines available time slots

Example:

  • 15-minute intervals: 9:00, 9:15, 9:30, 9:45...
  • 30-minute intervals: 9:00, 9:30, 10:00...

Backend Logic (BookingController.php):

$interval = $frontendSettings->booking_interval;
while ($currentTime < $endTime) {
$slots[] = $currentTime->format('h:i A');
$currentTime->addMinutes($interval);
}

3. Enable Sign Up

Field: enable_signup
Type: Toggle (0/1)
Description: Allow new user registration

Impact on User Flow:

  • Enabled (1): Shows "Sign Up" option in login modal
  • Disabled (0): Only login available

4. Staff Selection

Field: staff_selection
Type: Toggle (0/1)
Description: Let customers choose their preferred staff member

Impact on User Flow:

  • Enabled (1): Adds staff selection step to booking flow
  • Disabled (0): Staff auto-assigned by system

Use Case: Enable when customers have preferred stylists


5. Dynamic Slot Calculation

Field: enable_dynamic_slot
Type: Toggle (0/1)
Description: Calculate time slots based on real-time staff availability

Impact:

  • Enabled (1): Slots calculated per staff member's schedule
  • Disabled (0): Slots based only on business hours

Backend Logic:

if ($enable_dynamic_slot) {
// Check each staff member's:
// - Working hours
// - Existing appointments
// - Break times
// - Block times
}

6. Google Login

Field: google_login_enable
Type: Toggle (0/1)
Description: Enable Google OAuth authentication

Requirements:

  • Google Client ID
  • Google Client Secret

Impact: Shows "Continue with Google" button


7. Facebook Login

Field: facebook_login_enable
Type: Toggle (0/1)
Description: Enable Facebook OAuth authentication

Requirements:

  • Facebook App ID
  • Facebook App Secret

Impact: Shows "Continue with Facebook" button


8. Deposit Enable

Field: deposit_enable
Type: Toggle (0/1)
Description: Enable deposit payments for services

Impact:

  • Enabled (1): Shows deposit payment option for services where payment_type = 1
  • Disabled (0): No deposit payments accepted

Service Configuration: Set min_amount on service for deposit amount


Settings API

Endpoint: POST /front_end_booking_settings/update

Request Body:

{
"guest_checkout": 1,
"booking_interval": 15,
"enable_signup": 1,
"staff_selection": 1,
"enable_dynamic_slot": 1,
"google_login_enable": 0,
"facebook_login_enable": 0,
"deposit_enable": 1
}

Backend Process (FrontEndBookingSettingController.php):

  1. Validate all fields
  2. Update frontend_settings table
  3. Clear cache: clearCacheByPattern('customers_booking_get_front_settings')
  4. Return success response

Technical Implementation

Frontend Redux Store

File: Frontend-Userapp-Web/store/bookingSlice.ts

State Structure:

interface BookingState {
selectedLocation: Location | null;
currency: string | null;
services: Service[];
addOns: Addon[];
date: string;
time: string;
bookingSettings: BookingSettings;
customer: Customer;
depositAmount: number;
paymentMethod: number;
paymentType: number;
cancellationPolicy: boolean;
}

Key Actions:

  • setBookingSettings(settings) - Store fetched settings
  • setSelectedLocation(location) - Store selected location
  • addService(service) - Add service to cart
  • removeService(serviceId) - Remove service
  • setDateAndTime({ date, time }) - Store appointment time
  • setDepositAmount(amount) - Store deposit
  • setCancellationPolicy(accepted) - Track policy agreement

Backend API Endpoints

Get Frontend Settings

GET /booking/get-front-settings

Controller: BookingController.php::getFrontendSettings()

Caching: Cached with key customers_booking_get_front_settings

Response:

{
"status": true,
"data": {
"guest_checkout": 1,
"booking_interval": 15,
"staff_selection": 1,
"STRIPE_KEY": "pk_test_..."
},
"shop": {
"currency": "USD",
"name": "My Salon"
},
"calendar": {
"timeZone": "America/New_York"
}
}

Get Available Time Slots

POST /booking/get-slot

Controller: BookingController.php::getSlots()

Request:

{
"start_date": "2024/12/06",
"end_date": "2024/12/06",
"location_id": 1,
"service_pricing_options": [
{
"service_id": 10,
"pricing_option_id": null,
"staff_id": 5
}
]
}

Validation:

  • start_date: Required, format Y/m/d
  • end_date: Required, format Y/m/d
  • location_id: Required
  • service_pricing_options: Required array

Response:

{
"status": true,
"data": [
{ "time": "09:00 AM", "available": true },
{ "time": "09:15 AM", "available": true },
{ "time": "09:30 AM", "available": false }
]
}

Slot Calculation Algorithm:

  1. Get business hours for date
  2. Get staff working hours (if staff selected)
  3. Get booking interval from settings
  4. Generate all possible slots
  5. Check existing appointments
  6. Remove conflicting slots
  7. Remove block times
  8. Remove break times
  9. Return available slots

Save Booking

POST /booking/save-booking

Controller: BookingController.php::saveBooking()

Request:

{
"location_id": 1,
"customer_id": 123,
"customer_type": 0,
"date": "2024/12/06",
"time": "09:00 AM",
"note": "Please use low-ammonia products",
"services": [
{
"service_id": 10,
"pricing_option_id": null,
"duration": 30,
"price": 35,
"staff_id": 5
}
],
"addons": [
{
"add_on_id": 3,
"add_on_price": 10,
"add_on_duration": 15
}
],
"deposit_amount": "0"
}

Validation Rules:

  • location_id: Required, exists in locations table
  • customer_id: Nullable (for guest checkout)
  • customer_type: 0 (Registered), 1 (Guest), 2 (Walk-in)
  • date: Required, format Y/m/d
  • services: Required array, minimum 1 service

Response:

{
"status": true,
"message": "Appointment created successfully",
"data": {
"id": 1234,
"appointment_number": "APT-1234",
"status": 1
}
}

Backend Process:

  1. Validate request data
  2. Create appointments record
  3. Create appointment_services records
  4. Create appointment_addons records (if any)
  5. Send confirmation email
  6. Send notification to staff
  7. Update cache
  8. Return appointment details

Payment Integration

Stripe Setup

Required Fields in frontend_settings:

  • STRIPE_KEY: Publishable key (pk_...)
  • STRIPE_SECRET: Secret key (sk_...)

Payment Flow for Deposits

Step 1: Create Payment Intent

// Frontend: Frontend-Userapp-Web/api/bookingApi.ts
const { token } = await getToken({amount: depositAmount});

Backend: PaymentController.php::create()

$paymentIntent = $stripe->paymentIntents->create([
'amount' => $request['amount'] * 100, // Convert to cents
'currency' => 'usd',
'automatic_payment_methods' => ['enabled' => true]
]);

return response()->json([
'status' => true,
'token' => $paymentIntent->client_secret
]);

Step 2: Confirm Payment

// Frontend: CheckoutForm.jsx
const { paymentIntent } = await stripe.confirmPayment({
elements,
confirmParams: { return_url: confirmationUrl },
redirect: 'if_required'
});

Step 3: Update Status

await updatePaymentStatus({
appointment_id: apptId,
payment_intent: paymentIntent.id
});

Backend: PaymentController.php::updatePaymentStatus()

$output = $stripe->paymentIntents->retrieve($request->payment_intent);

if ($output->status == 'succeeded') {
$appointment->update(['status' => 1]);

AppointmentPayment::create([
'appointment_id' => $appointment->id,
'amount' => $output->amount / 100,
'txn_id' => $output->client_secret,
'status' => '1'
]);
}

Database Schema

frontend_settings Table

CREATE TABLE frontend_settings (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
guest_checkout INT DEFAULT 0,
booking_interval INT DEFAULT 15,
enable_signup INT DEFAULT 1,
staff_selection INT DEFAULT 0,
enable_dynamic_slot INT DEFAULT 0,
google_login_enable INT DEFAULT 0,
facebook_login_enable INT DEFAULT 0,
deposit_enable INT DEFAULT 0,
save_card_enable INT DEFAULT 0,
pay_later_enable INT DEFAULT 0,
STRIPE_KEY VARCHAR(255),
STRIPE_SECRET VARCHAR(255),
created_at TIMESTAMP,
updated_at TIMESTAMP
);

appointments Table

CREATE TABLE appointments (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
appointment_number VARCHAR(50),
location_id BIGINT,
customer_id BIGINT NULL,
customer_type INT DEFAULT 0,
date DATE,
time VARCHAR(20),
status INT DEFAULT 0,
note TEXT,
created_at TIMESTAMP,
updated_at TIMESTAMP,
FOREIGN KEY (location_id) REFERENCES locations(id),
FOREIGN KEY (customer_id) REFERENCES customers(id)
);

appointment_services Table

CREATE TABLE appointment_services (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
appointment_id BIGINT,
service_id BIGINT,
pricing_option_id BIGINT NULL,
staff_id BIGINT NULL,
duration INT,
price DECIMAL(10,2),
created_at TIMESTAMP,
FOREIGN KEY (appointment_id) REFERENCES appointments(id) ON DELETE CASCADE
);

Best Practices

For Salon Administrators

  1. Booking Interval: Set based on your average service duration

    • Quick services (15-30 min): 15-minute intervals
    • Standard services (30-60 min): 30-minute intervals
    • Long services (1+ hour): 60-minute intervals
  2. Staff Selection: Enable if you have specialized staff

    • Hair color specialists
    • Senior stylists
    • Nail technicians with specialties
  3. Guest Checkout: Enable for walk-in friendly salons

    • Reduces booking friction
    • Good for first-time customers
    • Can convert to registered users later
  4. Deposit Payments: Use for high-value services

    • Reduces no-shows
    • Protects against last-minute cancellations
    • Set deposit at 25-50% of service cost

For Developers

  1. Cache Management: Always clear cache when settings change
  2. Validation: Validate date formats (Y/m/d) on both frontend and backend
  3. Error Handling: Provide clear error messages for slot unavailability
  4. Payment Security: Never store credit card details; use Stripe tokens
  5. Timezone Handling: Store appointments in salon's timezone

Troubleshooting

Common Issues

Issue: Time slots not showing

Causes:

  1. No staff assigned to service
  2. Staff has no working hours defined
  3. Block times covering all slots
  4. Service duration longer than business hours

Solution:

# Check backend logs
tail -f storage/logs/laravel.log

# Verify API response
GET /booking/get-slot

Issue: Booking settings not updating

Cause: Cache not cleared

Solution:

// Backend
clearCacheByPattern('customers_booking_get_front_settings');

Issue: Payment not processing

Causes:

  1. Invalid Stripe keys
  2. Currency mismatch
  3. Amount in wrong format (should be cents)

Solution: Check Stripe dashboard for error details


API Reference Summary

EndpointMethodPurpose
/booking/get-front-settingsGETFetch booking configuration
/booking/get-location-listGETGet available locations
/booking/get-service-by-locationPOSTGet services for location
/booking/get-service-staffPOSTGet staff for service
/booking/get-slotPOSTGet available time slots
/booking/save-bookingPOSTCreate appointment
/payment/create-orderPOSTCreate Stripe payment intent
/payment/update-statusPOSTUpdate payment status

Next Steps