Skip to main content

Overview

The booking and payment flow involves several models and services working together:
Student checks availability → Creates booking → Makes payment → Tutor approves → Lesson happens → Payout to tutor

Step 1: Check availability

GET /api/availabilitesAvailabilityService::getAvailableTimes() File: app/Models/Services/v1/AvailabilityService.php The availability service:
  1. Resolves the tutor’s Teach record for the subject
  2. Gets the tutor’s weekly DayAvailability schedule
  3. Generates 15-minute interval time slots for the requested dates
  4. 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)
  5. Handles timezone conversion (UTC offset from request)
  6. Returns AvailableDateTime collections with pricing via LessonPriceBreakdown

Lesson duration constraints

From config/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/bookingsBookingController Creates:
  1. A Lesson record with start/finish times, subject, level, tutor
  2. A Booking record linking the lesson to the student
  3. A LessonAddress if it’s an in-person lesson
  4. Optionally links children via lesson_child pivot
Initial booking state: AWAITING_PAYMENT (3)

Step 3: Make payment

POST /api/payments/payPaymentControllerStripeService::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.
// Key parameters:
- amount (in pence/cents)
- currency
- payment_method (card token)
- customer (Stripe customer ID)
- transfer_group (for Connect payouts)
- confirm: true
- capture_method: 'manual'
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 reference
  • amount — total charged
  • application_fee_id and application_fee_amount
  • commission_id — links to active commission for fee calculation
  • coupon_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_uses pivot

Payment status checking

GET /api/payments/{id}/status → returns current PaymentIntent status:
StatusConstantMeaning
processingSTATUS_PROCESSINGPayment being processed
succeededSTATUS_SUCCEEDEDPayment completed
canceledSTATUS_CANCELLEDPayment cancelled
requires_actionSTATUS_REQUIRES_ACTION3D Secure needed
requires_captureSTATUS_REQUIRES_CAPTUREPre-authorized, awaiting capture

Step 4: Payout to tutor

Payouts are handled by the SendPendingPayouts scheduled job (runs every 15 minutes). File: app/Jobs/SendPendingPayouts.php
1

Find eligible bookings

Queries bookings that:
  • Have state PAYED (1)
  • Have a PaymentReceipt with no transfer_id yet
  • 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_group or source_transaction method
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:
FeeFieldDescription
Commission feecommission_feePlatform commission on lesson price
Service feeservice_feeFee charged to student
Payout feepayout_feeFee on tutor payout
Payout amount feepayout_amount_feeFixed payout fee
Management feeaccount_management_amount_feeMonthly account management (first lesson of month)
Only one commission can be active at a time. Commission::addNewModel() disables all existing commissions before inserting a new one.
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
$stripeService->refund($paymentReceipt);

// Partial refund
$stripeService->refund($paymentReceipt, $amount);
Booking state transitions:
  • Full refund → REFUNDED (0)
  • Partial refund → PARTIAL_REFUNDED (4)

Coupon system

File: app/Models/Coupon.php
TypeConstantDescription
One-time useONE_TIME_USECan be used once per user
Two-time useTWO_TIME_USECan be used twice per user
Discount typeConstantDescription
AmountDISCOUNT_TYPE_AMOUNTFixed amount off
PercentageDISCOUNT_TYPE_PERCENTPercentage off (with optional max_discount_amount cap)
Validation checks: expiry dates, usage count per user.