Overview
The booking and payment flow involves several models and services working together:Step 1: Check availability
GET /api/availabilites → AvailabilityService::getAvailableTimes()
File: app/Models/Services/v1/AvailabilityService.php
The availability service:
- Resolves the tutor’s
Teachrecord for the subject - Gets the tutor’s weekly
DayAvailabilityschedule - Generates 15-minute interval time slots for the requested dates
- Filters out:
- Times that clash with existing lessons
- Times blocked by calendar events (Google Calendar sync)
- Times affected by travel time between lessons (via BlackBox)
- Handles timezone conversion (UTC offset from request)
- Returns
AvailableDateTimecollections with pricing viaLessonPriceBreakdown
Lesson duration constraints
Fromconfig/app.php:
- Minimum: 15 minutes
- Maximum: 120 minutes
- Default: 60 minutes
Travel time logic
File:app/Models/v1/AvailabilityRequestTime.php
For in-person lessons, the system calculates travel time between lesson locations and creates buffer zones around existing lessons. Uses the BlackBox microservice for transit time estimates.
Step 2: Create booking
POST /api/bookings → BookingController
Creates:
- A
Lessonrecord with start/finish times, subject, level, tutor - A
Bookingrecord linking the lesson to the student - A
LessonAddressif it’s an in-person lesson - Optionally links children via
lesson_childpivot
AWAITING_PAYMENT (3)
Step 3: Make payment
POST /api/payments/pay → PaymentController → StripeService::createCharge()
File: app/Models/Services/Payments/StripeService.php
1
Create PaymentIntent
Creates a Stripe PaymentIntent with manual capture (
capture_method: manual). This pre-authorizes the amount without charging immediately.2
Handle 3D Secure
If Stripe returns
requires_action, the response includes a redirectURL for the client to complete 3D Secure authentication. The client handles this and calls back.3
Handle requires_capture
If payment is
requires_capture, the booking state updates to REQUIRES_CAPTURE (5). The payment is authorized but not yet charged.4
Create receipt
StripeService::createReceipt() creates a PaymentReceipt with:charge_id— Stripe charge referenceamount— total chargedapplication_fee_idandapplication_fee_amountcommission_id— links to active commission for fee calculationcoupon_id— if a discount code was used
5
Apply coupon (if any)
If a coupon is provided:
- Validates via
Coupon::validate()(not expired, not exceeded usage limit) - Calculates discount (amount-based or percentage-based with max cap)
- Records usage in
coupon_usespivot
Payment status checking
GET /api/payments/{id}/status → returns current PaymentIntent status:
Step 4: Payout to tutor
Payouts are handled by theSendPendingPayouts scheduled job (runs every 15 minutes).
File: app/Jobs/SendPendingPayouts.php
1
Find eligible bookings
Queries bookings that:
- Have state
PAYED (1) - Have a
PaymentReceiptwith notransfer_idyet - Are 2+ days old (settlement period)
2
Create Stripe transfer
StripeService::createPayout() creates a Stripe Connect transfer:- Destination: tutor’s Stripe Connect account (
acct_*) - Amount: lesson price minus platform fees
- Uses either
transfer_grouporsource_transactionmethod
3
Notify internal team
Sends
SendPendingPayoutStatus email with:- Booking ID
- Transfer ID (on success)
- Error message (on failure)
Commission calculation
File:app/Models/v1/Commission.php
The active commission record defines fee percentages:
The
PaymentReceipt model auto-calculates the full fee breakdown in its boot() method using CommissionService.
Refunds
StripeService::refund() supports both full and partial refunds:
- Full refund →
REFUNDED (0) - Partial refund →
PARTIAL_REFUNDED (4)
Coupon system
File:app/Models/Coupon.php
Validation checks: expiry dates, usage count per user.