Appointments
SCP appointment management for service-based businesses. Appointments represent scheduled visits between end customers and service providers, with integrated payment collection for deposits, balances, and tips.
Contexts
SCP.AvailabilityContext- Provider schedules and available time slotsSCP.AppointmentContext- Appointment lifecycle management (booking → service → checkout)SCP.PaymentContractContext- Payment rules and contracts for appointmentsSCP.InvoiceContext- Billing and invoicing for services renderedSCP.MessageContext- Reminders, confirmations, and notifications
Key Functions
Availability & Booking
SCP.AvailabilityContext.list_available_slots/3- Fetch bookable time slots for a providerSCP.AvailabilityContext.book_appointment/4- Create new appointment with deposit collectionSCP.AppointmentContext.cancel_appointment/2- Cancel appointment with refund processingSCP.AppointmentContext.reschedule_appointment/2- Move appointment to new time slot
Check-In & Checkout
SCP.AppointmentContext.check_in/2- Mark customer arrival (QR code, SMS, kiosk)SCP.AppointmentContext.complete_appointment/2- Mark service completionSCP.AppointmentContext.process_checkout/3- Collect remaining balance and tip
Notifications
SCP.MessageContext.send_booking_confirmation/2- Confirmation at booking timeSCP.MessageContext.send_reminder/2- T-24h and T-2h remindersSCP.MessageContext.send_post_service_message/2- Thank you and review request
Appointment Booking Flow
The booking flow follows the Zoca PRD model: Discovery → Booking → Deposit → Service → Balance → Tip
Deposit Collection
Deposits are collected at booking time based on merchant payment policy:
| Service Amount | Deposit Percentage |
|---|---|
| < $50 | 50% |
| $50 - $150 | 30-40% |
| > $150 | 25-30% |
Implementation:
- Merchant configures deposit rules in
PaymentPolicyContext AvailabilityContext.book_appointment/4createsPaymentContractandInvoicefor deposit- Deposit charged immediately via
PaymentContextand Stripe plugin - Booking status:
pending_payment→confirmedon successful charge
Day-of-Service Flow
Check-In Methods
MVP: QR Code
- Unique QR code sent in T-2h reminder SMS
- Scans to check-in URL with appointment token
- Updates status to
checked_in
Future:
- SMS reply-based check-in ("Reply YES to check in")
- Kiosk check-in at location
Checkout & Payment Collection
Remaining Balance:
- Calculate:
service_amount - deposit_paid - Charge saved payment method on file
- If balance = $0, skip to tip collection
Tip Collection:
- Present options: 15%, 18%, 20%, Custom amount
- Charge tip as separate transaction
- Both balance and tip link to original
PaymentContract
Final Receipt:
- Itemized breakdown: Service, Deposit, Balance, Tip, Total
- Sent via SMS/Email
- Stored in
InvoiceContext
Appointment Lifecycle
Appointment States
| State | Description | Payment Action |
|---|---|---|
pending_payment | Booking created, awaiting deposit | Charge deposit |
confirmed | Deposit paid, appointment confirmed | Send reminders |
checked_in | Customer arrived at location | None |
in_progress | Service being delivered | None |
completed | Service done, final payment collected | Charge balance + tip |
cancelled | Appointment cancelled before service | Process refund per policy |
no_show | Customer did not arrive | Charge no-show fee |
Payment Integration
Appointments are tightly integrated with the payment system:
PaymentContract
Every appointment has a linked PaymentContract that defines:
- Total service amount
- Deposit amount and status
- Remaining balance
- Associated payment policies (cancellation, no-show)
Invoice Categories
Appointments generate multiple invoices:
- deposit - Initial payment at booking
- balance - Remaining service cost at checkout
- tip - Gratuity after service
- no_show - Fee for missed appointments
- refund - Cancellation refunds
Transaction Flow
Appointment
↓
PaymentContract (total amount, deposit rules)
↓
Invoice (deposit) → Transaction (charge)
↓
Invoice (balance) → Transaction (charge)
↓
Invoice (tip) → Transaction (charge)
↓
TransactionJournal (double-entry bookkeeping)
Reminders & Notifications
Automated messages sent via MessageContext and SMS/Email plugins:
Booking Confirmation (Immediate)
Subject: Appointment Confirmed
Body: Your appointment with [Provider] is confirmed for [DateTime].
Service: [Service Name]
Deposit Paid: $XX.XX
Remaining Balance: $XX.XX
[Cancel/Reschedule Link]
T-24h Reminder
Subject: Reminder: Appointment Tomorrow
Body: Your appointment is tomorrow at [Time].
Provider: [Name]
Location: [Address]
[Add to Calendar]
T-2h Reminder (with QR Code)
Subject: Appointment in 2 Hours
Body: Your appointment starts in 2 hours.
Check in here: [QR Code]
Location: [Address with Map Link]
Post-Service Thank You
Subject: Thank You for Visiting
Body: Thanks for visiting [Business Name]!
[View Receipt]
[Leave a Review]
[Book Again]
Cancellation & Refunds
Cancellation Policy
Merchants configure cancellation windows in PaymentPolicyContext:
- >24h notice: Full refund (100% deposit returned)
- <24h notice: Partial refund (50% deposit returned)
- <2h notice: No refund (0% deposit returned)
Refund Processing
# Cancel appointment with policy-based refund
AppointmentContext.cancel_appointment(appointment, %{
cancelled_by: end_customer_id,
reason: "scheduling conflict"
})
# Calculates refund based on:
# - Hours until appointment
# - Merchant cancellation policy
# - Deposit amount paid
# Creates refund transaction via PaymentContext
No-Show Handling
- If customer doesn't check in within 15 minutes of appointment time
- Status automatically updated to
no_show - No-show fee charged per merchant policy (typically 100% of deposit)
- Merchant can manually waive fee if needed
Search & Filtering
Merchants can search appointments using AppointmentContext.search_appointments/3:
Filter Criteria:
- Date range (start/end dates)
- Provider IDs
- Service IDs
- Location IDs
- Status (confirmed, completed, cancelled, no_show)
- End customer name/phone/email
Export:
- CSV export for reporting and accounting
- Includes payment details, provider, service, timestamps
Concurrency & Race Conditions
Slot Booking Conflicts
- Database-level row locking prevents double-booking
- Redis cache for real-time availability updates
- Optimistic locking with version numbers
Implementation:
# Lock slot during booking transaction
Repo.transaction(fn ->
slot = AvailabilityContext.lock_slot!(slot_id)
if slot.status == :available do
AvailabilityContext.book_appointment(slot, customer, service, payment_method)
else
{:error, :slot_unavailable}
end
end)
Multi-Location Support
Post-MVP Feature:
- Each appointment links to specific
Location - Providers can work across multiple locations
- Availability managed per location
- Location-specific payment policies
Revenue Attribution
Track Zoca-generated bookings vs. existing customer bookings:
Metadata on Appointments:
%Appointment{
source: :zoca_booking, # vs :manual, :phone, :walkin
attribution: %{
channel: "organic_search",
campaign: "google_ads_q1",
booking_page_id: "..."
}
}
Reporting:
- Revenue dashboard shows Zoca-attributed vs. total
- Conversion metrics (views → bookings)
- Average booking value by channel
Related API Endpoints
See the API Reference for appointment-related endpoints:
POST /api/bookings- Create a new bookingGET /api/appointments- List appointmentsPUT /api/appointments/:id- Update appointmentPOST /api/appointments/:id/check-in- Check in customerPOST /api/appointments/:id/checkout- Process checkout