Streamlining Recurring Services: Our Journey to Cycle Enrollment in Landing

In the landing project, which facilitates a variety of mentorship services, we recently embarked on a significant enhancement to how recurring mentorships are managed. Our goal was to simplify the enrollment process for both mentees and mentors, moving away from a cumbersome per-slot booking system to a more intuitive, subscription-like model. This journey led us to implement a new 'Cycle Enrollment' feature, the first phase of which dramatically improves the user experience for recurring services.

The Situation

Previously, for mentorships structured with a MentorSchedule (i.e., recurring sessions), mentees had to book individual slots. While this worked for one-off sessions, it created friction for long-term engagements. Imagine signing up for a six-month mentorship and having to manually select and book every single weekly session! This not only led to poor user experience but also administrative overhead in tracking individual bookings and managing full slots.

The Challenge

Our primary challenge was to create a system that could intelligently handle recurring bookings based on a cycle, integrate seamlessly with our existing payment infrastructure (Stripe), and gracefully manage capacity constraints without requiring manual intervention for each session. We needed a robust mechanism to:

  • Allow mentees to 'enroll' in a cycle rather than booking individual slots.
  • Automatically generate and book all upcoming sessions for the duration of their enrollment.
  • Differentiate between free and paid recurring services, with paid ones requiring a fixed-term commitment via Stripe subscriptions.
  • Handle situations where specific slots were already full, placing mentees on a waitlist automatically.
  • Ensure idempotency when generating bookings to prevent duplicates.
  • Automate booking generation upon monthly renewals for ongoing paid subscriptions.

The Solution: Cycle Enrollment (Phase 1)

To address these challenges, we introduced the 'Enroll in cycle' flow. When a mentee enrolls in a recurring mentorship service, all scheduled sessions up to their next Stripe renewal date (for paid services) or a predefined contract end date (for free services) are automatically booked. If a session is already at capacity, the mentee is placed on a Waitlist for that specific slot, improving fairness and managing demand efficiently.

Architectural Pillars

At the heart of this new feature are several key architectural components:

  • MentorCycleEnrollmentService: This new service acts as the orchestrator for the entire enrollment process. It's responsible for validating enrollment requests, interacting with other services to generate bookings, and managing the state of the MentorSubscription.

    namespace App\Services;
    
    use App\Models\MentorService;
    use App\Models\User;
    
    class MentorCycleEnrollmentService
    {
        public function enrollInCycle(User $mentee, MentorService $service, array $options = []): MentorSubscription
        {
            // Validate input, check service type, T&C acceptance
            // ...
    
            // Determine contract duration and payment type (free/paid)
            // ...
    
            // Generate initial bookings for the enrollment period
            // (delegates to MentorScheduleExpander)
            // ...
    
            // Create/update MentorSubscription record
            // ...
    
            // If paid, initiate Stripe Checkout session
            // ...
    
            // Return the created/updated subscription
            return $subscription;
        }
    
        public function generateBookingsForPeriod(MentorSubscription $subscription, 
                                                CarbonInterface $startDate, 
                                                CarbonInterface $endDate): void
        {
            // Uses MentorScheduleExpander to get available slots
            // Creates MentorBooking records, handles waitlists, ensures idempotency
            // ...
        }
    }
    
  • MentorScheduleExpander: This service is crucial for dynamically generating concrete session slots from a recurring MentorSchedule. It takes a schedule and a time range, then outputs all valid sessions within that period, factoring in any MentorScheduleOverride.

  • MentorSubscription Model Enhancements: The existing MentorSubscription model was extended with new fields like contract_started_at, contract_ends_at, cycle_ends_at, and stripe_subscription_schedule_id to manage the new cycle-based contracts.

  • MentorBooking Source Tracking: A new source column (manual | subscription) was added to MentorBooking to distinguish between individually booked sessions and those auto-generated by the cycle enrollment.

  • Webhook Handling for Stripe:

    • HandleMentorshipCheckoutCompleted: Now recognizes the mentorship_cycle_enrollment type to finalize the subscription after a successful Stripe checkout.
    • HandleMentorshipInvoicePaid: A critical new listener that triggers MentorCycleEnrollmentService::generateBookingsForPeriod whenever a monthly invoice is successfully paid for a recurring subscription. This ensures that the next period's bookings are automatically created, providing a seamless ongoing experience.

Looking Ahead

This initial phase lays a robust foundation. Future phases are planned to introduce automatic attendance detection, warning systems for missed sessions, reprobation flows with automated future-session cancellation, mentor reactivation, and a re-enrollment acceptance process. These subsequent stages will further enhance the automation and reliability of our mentorship services.

The Takeaway

Implementing 'Cycle Enrollment' has transformed our recurring mentorship services from a manual, slot-by-slot headache into an automated, subscription-driven experience. The key takeaway here is the power of a well-defined Service Layer to orchestrate complex business logic, combined with effective webhook integration for managing external payment flows like Stripe. By designing for idempotency and clearly segmenting responsibilities, we've built a scalable solution that significantly improves user experience and reduces operational burden. When building systems with recurring elements, always prioritize an automated cycle management over manual, piecemeal interactions.

Streamlining Recurring Services: Our Journey to Cycle Enrollment in Landing
GERARDO RUIZ

GERARDO RUIZ

Author

Share: