Jake Vanderwerf
2025-11-25 2a2303d1dccc120dd7aa5f6b6ade0f89e0064850
inc/rest/routes/ReferralRoutes.php
@@ -1,6 +1,8 @@
<?php
namespace JVBase\rest\routes;
use JVBase\importers\JaneAppClientImporter;
use JVBase\managers\JaneSalesImporter;
use JVBase\rest\RestRouteManager;
use WP_REST_Request;
use WP_REST_Response;
@@ -15,11 +17,24 @@
 */
class ReferralRoutes extends RestRouteManager
{
   protected string $referrals_table;
   protected string $rewards_table;
   protected string $treatments_table;
   protected string $jane_clients_table;
   protected $wpdb;
   public function __construct()
   {
      $this->route = 'referrals';
      $this->cache_name = 'referrals';
      parent::__construct();
      global $wpdb;
      $this->wpdb = $wpdb;
      $this->referrals_table = $wpdb->prefix . BASE . 'referrals';
      $this->rewards_table = $wpdb->prefix . BASE . 'referral_rewards';
      $this->treatments_table = $wpdb->prefix . BASE . 'referral_treatments';
      $this->jane_clients_table = $wpdb->prefix . BASE . 'jane_clients';
   }
   public function registerRoutes(): void
@@ -31,6 +46,49 @@
         'permission_callback' => [$this, 'checkPermission']
      ]);
      register_rest_route($this->namespace, "/{$this->route}/register", [
         'methods' => 'POST',
         'callback' => [$this, 'registerWithReferral'],
         'permission_callback' => [$this, 'checkRateLimit'],
         'args' => [
            'name' => [
               'required' => true,
               'type' => 'string',
               'sanitize_callback' => 'sanitize_text_field'
            ],
            'email' => [
               'required' => true,
               'type' => 'string',
               'format' => 'email',
               'validate_callback' => function($param) {
                  return is_email($param);
               }
            ],
            'code' => [
               'required' => true,
               'type' => 'string',
               'sanitize_callback' => function($code) {
                  return strtoupper(sanitize_text_field($code));
               }
            ]
         ]
      ]);
      register_rest_route($this->namespace, '/referrals/check-code', [
         'methods' => 'POST',
         'callback' => [$this, 'checkReferralCode'],
         'permission_callback' => [$this, 'checkRateLimit'],
         'args' => [
            'code' => [
               'required' => true,
               'type' => 'string',
               'sanitize_callback' => function($code) {
                  return strtoupper(sanitize_text_field($code));
               }
            ]
         ]
      ]);
      // Get or create referral code
      register_rest_route($this->namespace, "/{$this->route}/code", [
         'methods' => 'GET',
@@ -38,27 +96,11 @@
         'permission_callback' => [$this, 'checkPermission']
      ]);
      // Update referral code
      register_rest_route($this->namespace, "/{$this->route}/code", [
         'methods' => 'POST',
         'callback' => [$this, 'updateReferralCode'],
         'permission_callback' => [$this, 'checkPermission'],
         'args' => [
            'code' => [
               'required' => true,
               'type' => 'string',
               'validate_callback' => function($param) {
                  return preg_match('/^[A-Z0-9]+$/i', $param);
               }
            ]
         ]
      ]);
      // Track referral click (public endpoint)
      register_rest_route($this->namespace, "/{$this->route}/track", [
         'methods' => 'POST',
         'callback' => [$this, 'trackReferralClick'],
         'permission_callback' => '__return_true',
         'permission_callback' => [$this, 'checkRateLimit'],
         'args' => [
            'code' => [
               'required' => true,
@@ -84,13 +126,78 @@
         ]
      ]);
      // Get user stats
      register_rest_route($this->namespace, "/{$this->route}/stats", [
      // Send referral invitation
      register_rest_route($this->namespace, '/'.$this->route.'/invite', [
         'methods' => 'POST',
         'callback' => [$this, 'sendInvitation'],
         'permission_callback' => [$this, 'checkPermission'],
         'args' => [
            'email' => [
               'required' => true,
               'type' => 'string',
               'format' => 'email',
               'validate_callback' => function($param) {
                  return is_email($param);
               }
            ],
            'name' => [
               'required' => true,
               'type' => 'string',
               'sanitize_callback' => 'sanitize_text_field'
            ]
         ]
      ]);
      // Send batch invitations
      register_rest_route($this->namespace, '/'.$this->route.'/invite/batch', [
         'methods' => 'POST',
         'callback' => [$this, 'sendBatchInvitations'],
         'permission_callback' => [$this, 'checkPermission'],
         'args' => [
            'invitations' => [
               'required' => true,
               'type' => 'array',
               'validate_callback' => function($param) {
                  return is_array($param) && !empty($param);
               }
            ]
         ]
      ]);
      // Get invitation stats for current user
      register_rest_route($this->namespace, '/'.$this->route.'/invite/stats', [
         'methods' => 'GET',
         'callback' => [$this, 'getUserStats'],
         'callback' => [$this, 'getInvitationStats'],
         'permission_callback' => [$this, 'checkPermission']
      ]);
      // Export referrals for Jane App
      register_rest_route($this->namespace, '/'.$this->route.'/export', [
         'methods' => 'POST',
         'callback' => [$this, 'exportReferrals'],
         'permission_callback' => function() {
            return current_user_can('manage_options');
         },
         'args' => [
            'start_date' => [
               'required' => true,
               'type' => 'string',
               'validate_callback' => function($param) {
                  return (bool) strtotime($param);
               }
            ],
            'end_date' => [
               'required' => true,
               'type' => 'string',
               'validate_callback' => function($param) {
                  return (bool) strtotime($param);
               }
            ]
         ]
      ]);
      // Get top referrers (admin only)
      register_rest_route($this->namespace, "/{$this->route}/leaderboard", [
         'methods' => 'GET',
@@ -127,6 +234,85 @@
            }
         ]
      ]);
      register_rest_route($this->namespace, "/{$this->route}/add-code", [
         'methods'   => 'POST',
         'callback'  => [$this, 'addReferralCodeAfterRegistration'],
         'permission_callback'   => [$this, 'checkRateLimit'],
         'args'   => [
            'code'   => [
               'required'  => true,
               'type'      => 'string',
               'sanitize_callback'  => function ($code) {
                  return strtoupper(sanitize_text_field($code));
               }
            ]
         ]
      ]);
      /***************************
       * ADDITIONAL
       */
// CSV Uploads
      register_rest_route($this->namespace, '/referrals/upload-clients', [
         'methods' => 'POST',
         'callback' => [$this, 'handleClientUpload'],
         'permission_callback' => [$this, 'checkAdminPermission']
      ]);
      register_rest_route($this->namespace, '/referrals/upload-sales', [
         'methods' => 'POST',
         'callback' => [$this, 'handleSalesUpload'],
         'permission_callback' => [$this, 'checkAdminPermission']
      ]);
      // Referral List & Details
      register_rest_route($this->namespace, '/referrals/list', [
         'methods' => 'GET',
         'callback' => [$this, 'getReferralsList'],
         'permission_callback' => [$this, 'checkAdminPermission']
      ]);
      register_rest_route($this->namespace, '/referrals/(?P<id>\d+)', [
         'methods' => 'GET',
         'callback' => [$this, 'getReferralDetails'],
         'permission_callback' => [$this, 'checkAdminPermission']
      ]);
      // Manual Status Updates
      register_rest_route($this->namespace, '/referrals/mark-consulted', [
         'methods' => 'POST',
         'callback' => [$this, 'handleMarkConsulted'],
         'permission_callback' => [$this, 'checkAdminPermission']
      ]);
      register_rest_route($this->namespace, '/referrals/mark-treated', [
         'methods' => 'POST',
         'callback' => [$this, 'handleMarkTreated'],
         'permission_callback' => [$this, 'checkAdminPermission']
      ]);
      // User-facing endpoints
      register_rest_route($this->namespace, '/referrals/my-stats', [
         'methods' => 'GET',
         'callback' => [$this, 'getMyStats'],
         'permission_callback' => [$this, 'checkPermission']
      ]);
      register_rest_route($this->namespace, '/referrals/my-referrals', [
         'methods' => 'GET',
         'callback' => [$this, 'getMyReferrals'],
         'permission_callback' => [$this, 'checkPermission']
      ]);
   }
   /**
    * Check admin-only permission
    */
   public function checkAdminPermission(WP_REST_Request $request): bool
   {
      return current_user_can('manage_options') && parent::checkPermission($request);
   }
   public function checkPermission(WP_REST_Request $request): bool
@@ -310,4 +496,689 @@
         'settings' => $settings
      ]);
   }
   /**
    * Send a single referral invitation
    *
    * @param WP_REST_Request $request
    * @return WP_REST_Response
    */
   public function sendInvitation(WP_REST_Request $request): WP_REST_Response
   {
      $user_id = get_current_user_id();
      $email = sanitize_email($request->get_param('email'));
      $name = sanitize_text_field($request->get_param('name'));
      // Send invitation via ReferralManager
      $referral_manager = JVB()->referrals();
      $result = $referral_manager->sendReferralInvitation($user_id, $email, $name);
      if (is_wp_error($result)) {
         return new WP_REST_Response([
            'success' => false,
            'message' => $result->get_error_message(),
            'code' => $result->get_error_code()
         ], 400);
      }
      return new WP_REST_Response($result, 200);
   }
   /**
    * Send batch referral invitations
    *
    * @param WP_REST_Request $request
    * @return WP_REST_Response
    */
   public function sendBatchInvitations(WP_REST_Request $request): WP_REST_Response
   {
      $user_id = get_current_user_id();
      $invitations = $request->get_param('invitations');
      // Validate invitation format
      foreach ($invitations as $invite) {
         if (empty($invite['email']) || empty($invite['name'])) {
            return new WP_REST_Response([
               'success' => false,
               'message' => 'Each invitation must have email and name'
            ], 400);
         }
      }
      // Send batch via ReferralManager
      $referral_manager = JVB()->referrals();
      $result = $referral_manager->sendBatchReferralInvitations($user_id, $invitations);
      return new WP_REST_Response($result, 200);
   }
   /**
    * Get invitation stats for current user
    *
    * @param WP_REST_Request $request
    * @return WP_REST_Response
    */
   public function getInvitationStats(WP_REST_Request $request): WP_REST_Response
   {
      $user_id = get_current_user_id();
      $referral_manager = JVB()->referrals();
      $stats = $referral_manager->getUserInvitationStats($user_id);
      return new WP_REST_Response([
         'success' => true,
         'stats' => $stats
      ], 200);
   }
   /**
    * Export referrals for Jane App cross-reference
    * Admin only
    *
    * @param WP_REST_Request $request
    * @return WP_REST_Response
    */
   public function exportReferrals(WP_REST_Request $request): WP_REST_Response
   {
      $start_date = sanitize_text_field($request->get_param('start_date'));
      $end_date = sanitize_text_field($request->get_param('end_date'));
      $referral_manager = JVB()->referrals();
      $csv_content = $referral_manager->exportReferrals($start_date, $end_date);
      // Return CSV for download
      return new WP_REST_Response([
         'success' => true,
         'csv' => $csv_content,
         'filename' => sprintf('referrals_%s_to_%s.csv', $start_date, $end_date)
      ], 200);
   }
   public function registerWithReferral(WP_REST_Request $request): WP_REST_Response
   {
      $name = sanitize_text_field($request->get_param('name'));
      $email = sanitize_email($request->get_param('email'));
      $code = strtoupper(sanitize_text_field($request->get_param('code')));
      // Validate email
      if (!is_email($email)) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Invalid email address'
         ], 400);
      }
      // Check if user exists
      if (email_exists($email)) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'An account with this email already exists'
         ], 400);
      }
      // Validate referral code
      $referral_manager = JVB()->referrals();
      $referrer = $referral_manager->getUserByReferralCode($code);
      if (!$referrer) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Invalid referral code'
         ], 404);
      }
      // Get reward text
      $settings = $referral_manager->getRewardSettings();
      $reward_amount = $settings['referee_reward_amount'] ?? 20;
      $reward_type = $settings['referee_reward_type'] ?? 'percentage';
      $reward_text = $reward_type === 'percentage'
         ? "{$reward_amount}% off your first treatment!"
         : "\${$reward_amount} off your first treatment!";
      // Send magic link with referral context via MagicLinkManager
      $magic_link_manager = new \JVBase\managers\MagicLinkManager();
      $result = $magic_link_manager->sendMagicLink(
         $email,
         \JVBase\managers\MagicLinkManager::TYPE_REFERRAL,
         [
            'name' => $name,
            'referral_code' => $code,
            'referrer_id' => $referrer->ID,
            'referrer_name' => $referrer->display_name,
            'reward_text' => $reward_text
         ]
      );
      if (is_wp_error($result)) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Failed to send registration link. Please try again.'
         ], 500);
      }
      return new WP_REST_Response([
         'success' => true,
         'message' => 'Check your email! We sent you a link to complete your registration.',
         'email' => $email
      ], 200);
   }
   public function checkReferralCode(WP_REST_Request $request): WP_REST_Response
   {
      $code = strtoupper(sanitize_text_field($request->get_param('code')));
      if (empty($code)) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Code is required'
         ], 400);
      }
      $referral_manager = JVB()->referrals();
      $referrer = $referral_manager->getUserByReferralCode($code);
      if (!$referrer) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Invalid referral code'
         ], 404);
      }
      if (is_user_logged_in() && get_current_user_id() === $referrer->ID) {
         return $this->error('You cannot use your own referral code', 'self_referral', 400);
      }
      // Return basic referrer info (no sensitive data)
      return new WP_REST_Response([
         'success' => true,
         'code' => $code,
         'referrer_name' => $referrer->display_name,
      ], 200);
   }
   public function addReferralCodePostRegistration(WP_REST_Request $request): WP_REST_Response
   {
      $user_id = get_current_user_id();
      $code = $request->get_param('code');
      // Check if user already has a referral (can't change)
      $existing = JVB()->referrals()->getReferralByReferee($user_id);
      if ($existing) {
         return $this->error('You already have a referral code applied', 'already_referred', 400);
      }
      // Validate the code exists
      $referrer = JVB()->referrals()->getUserByReferralCode($code);
      if (!$referrer) {
         return $this->error('Invalid referral code', 'invalid_code', 400);
      }
      // Create the referral
      $user = wp_get_current_user();
      $result = JVB()->referrals()->createReferral($referrer->ID, $user_id, $code);
      if ($result) {
         return $this->success([
            'message' => 'Referral code applied successfully!',
            'referrer_name' => $referrer->display_name
         ]);
      }
      return $this->error('Failed to apply referral code', 'creation_failed', 500);
   }
   /**********************************
    * ADDITIONAL
    */
   /**
    * Handle client CSV upload
    */
   public function handleClientUpload(WP_REST_Request $request): WP_REST_Response
   {
      $files = $request->get_file_params();
      if (!isset($files['file'])) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'No file uploaded'
         ], 400);
      }
      $file = $files['file'];
      // Validate file type
      $allowed_types = ['text/csv', 'application/vnd.ms-excel', 'text/plain'];
      if (!in_array($file['type'], $allowed_types)) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'File must be a CSV'
         ], 400);
      }
      // Validate file size (10MB max)
      if ($file['size'] > 10 * 1024 * 1024) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'File size exceeds 10MB limit'
         ], 400);
      }
      // Import using JaneAppClientImporter
      $importer = new JaneAppClientImporter();
      $default_role = get_option(BASE . 'client_import_role', JVB_USER);
      $options = [
         'update_existing' => true,
         'send_welcome_email' => false,
         'create_users' => true,
         'default_role' => $default_role
      ];
      $result = $importer->importFromCSV($file['tmp_name'], $options);
      if (is_wp_error($result)) {
         return new WP_REST_Response([
            'success' => false,
            'message' => $result->get_error_message()
         ], 500);
      }
      // Build detailed message
      $message = sprintf(
         'Import complete: %d created, %d updated, %d skipped',
         $result['created'],
         $result['updated'],
         $result['skipped']
      );
      $details = [];
      if (!empty($result['skipped_details'])) {
         $details = $result['skipped_details'];
      }
      // Clear cache
      $this->cache->clear();
      return new WP_REST_Response([
         'success' => true,
         'message' => $message,
         'stats' => $result,
         'skipped_details' => $details
      ]);
   }
   /**
    * Handle sales CSV upload
    */
   public function handleSalesUpload(WP_REST_Request $request): WP_REST_Response
   {
      $files = $request->get_file_params();
      if (!isset($files['file'])) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'No file uploaded'
         ], 400);
      }
      $file = $files['file'];
      // Validate file type
      $allowed_types = ['text/csv', 'application/vnd.ms-excel', 'text/plain'];
      if (!in_array($file['type'], $allowed_types)) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'File must be a CSV'
         ], 400);
      }
      // Validate file size (10MB max)
      if ($file['size'] > 10 * 1024 * 1024) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'File size exceeds 10MB limit'
         ], 400);
      }
      // Import using JaneSalesImporter
      $importer = new JaneSalesImporter();
      $options = [
         'skip_existing' => true
      ];
      $result = $importer->importFromCSV($file['tmp_name'], $options);
      if (is_wp_error($result)) {
         return new WP_REST_Response([
            'success' => false,
            'message' => $result->get_error_message()
         ], 500);
      }
      // Clear cache
      $this->cache->clear();
      return new WP_REST_Response([
         'success' => true,
         'message' => 'Sales imported successfully',
         'stats' => $result
      ]);
   }
   /**
    * Get referrals list for table display
    */
   public function getReferralsList(WP_REST_Request $request): WP_REST_Response
   {
      $page = $request->get_param('page') ?: 1;
      $per_page = $request->get_param('per_page') ?: 20;
      $orderby = $request->get_param('orderby') ?: 'referred_at';
      $order = strtoupper($request->get_param('order')) === 'ASC' ? 'ASC' : 'DESC';
      $status = $request->get_param('status') ?: '';
      $search = $request->get_param('search') ?: '';
      $offset = ($page - 1) * $per_page;
      // Build WHERE clause
      $where_clauses = [];
      $where_params = [];
      if (!empty($status)) {
         $where_clauses[] = "r.status = %s";
         $where_params[] = $status;
      }
      if (!empty($search)) {
         $where_clauses[] = "(r.referee_name LIKE %s OR r.referee_email LIKE %s OR referrer.display_name LIKE %s)";
         $search_term = '%' . $this->wpdb->esc_like($search) . '%';
         $where_params[] = $search_term;
         $where_params[] = $search_term;
         $where_params[] = $search_term;
      }
      $where = !empty($where_clauses) ? ' WHERE ' . implode(' AND ', $where_clauses) : '';
      // Sanitize orderby to prevent SQL injection
      $allowed_orderby = ['referred_at', 'consulted_at', 'treated_at', 'status', 'referee_name', 'referrer_name'];
      if (!in_array($orderby, $allowed_orderby)) {
         $orderby = 'referred_at';
      }
      // Get referrals with user info
      $query = "SELECT
         r.*,
         referrer.display_name as referrer_name,
         referrer.user_email as referrer_email,
         referee.display_name as referee_display_name,
         referee.user_email as referee_display_email,
         (SELECT COUNT(*) FROM {$this->referrals_table} WHERE referrer_id = r.referrer_id) as referrer_total_referrals,
         (SELECT SUM(amount) FROM {$this->rewards_table} WHERE user_id = r.referrer_id AND status = 'available') as referrer_available_rewards
      FROM {$this->referrals_table} r
      LEFT JOIN {$this->wpdb->users} referrer ON r.referrer_id = referrer.ID
      LEFT JOIN {$this->wpdb->users} referee ON r.referee_id = referee.ID
      {$where}
      ORDER BY {$orderby} {$order}
      LIMIT %d OFFSET %d";
      $where_params[] = $per_page;
      $where_params[] = $offset;
      $prepared_query = $this->wpdb->prepare($query, $where_params);
      $referrals = $this->wpdb->get_results($prepared_query);
      // Get total count
      $count_query = "SELECT COUNT(*) FROM {$this->referrals_table} r
         LEFT JOIN {$this->wpdb->users} referrer ON r.referrer_id = referrer.ID
         {$where}";
      $total = $this->wpdb->get_var(
         !empty($where_params) && count($where_params) > 2 ?
            $this->wpdb->prepare($count_query, array_slice($where_params, 0, -2)) :
            $count_query
      );
      return new WP_REST_Response([
         'success' => true,
         'referrals' => $referrals,
         'total' => (int)$total,
         'page' => (int)$page,
         'per_page' => (int)$per_page,
         'total_pages' => ceil($total / $per_page)
      ]);
   }
   /**
    * Get details for a specific referral
    */
   public function getReferralDetails(WP_REST_Request $request): WP_REST_Response
   {
      $referral_id = $request->get_param('id');
      $referral = $this->wpdb->get_row($this->wpdb->prepare(
         "SELECT r.*,
            referrer.display_name as referrer_name,
            referee.display_name as referee_display_name
         FROM {$this->referrals_table} r
         LEFT JOIN {$this->wpdb->users} referrer ON r.referrer_id = referrer.ID
         LEFT JOIN {$this->wpdb->users} referee ON r.referee_id = referee.ID
         WHERE r.id = %d",
         $referral_id
      ));
      if (!$referral) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Referral not found'
         ], 404);
      }
      // Get associated treatments
      $treatments = $this->wpdb->get_results($this->wpdb->prepare(
         "SELECT * FROM {$this->treatments_table} WHERE referral_id = %d ORDER BY treatment_date DESC",
         $referral_id
      ));
      // Get associated rewards
      $rewards = $this->wpdb->get_results($this->wpdb->prepare(
         "SELECT * FROM {$this->rewards_table} WHERE referral_id = %d",
         $referral_id
      ));
      return new WP_REST_Response([
         'success' => true,
         'referral' => $referral,
         'treatments' => $treatments,
         'rewards' => $rewards
      ]);
   }
   /**
    * Handle manual mark as consulted
    */
   public function handleMarkConsulted(WP_REST_Request $request): WP_REST_Response
   {
      $referral_id = $request->get_param('referral_id');
      if (!$referral_id) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Referral ID required'
         ], 400);
      }
      $referral = $this->wpdb->get_row($this->wpdb->prepare(
         "SELECT * FROM {$this->referrals_table} WHERE id = %d",
         $referral_id
      ));
      if (!$referral) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Referral not found'
         ], 404);
      }
      if ($referral->status !== 'pending') {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Referral is not pending'
         ], 400);
      }
      // Update to consulted
      $this->wpdb->update(
         $this->referrals_table,
         [
            'status' => 'consulted',
            'consulted_at' => current_time('mysql')
         ],
         ['id' => $referral_id],
         ['%s', '%s'],
         ['%d']
      );
      // Create consultation reward (20% off)
      $this->wpdb->insert(
         $this->rewards_table,
         [
            'referral_id' => $referral_id,
            'user_id' => $referral->referee_id,
            'reward_type' => 'referee',
            'amount' => 20,
            'reward_calculation' => 'percentage',
            'status' => 'available',
            'created_at' => current_time('mysql'),
            'notes' => 'Consultation reward - 20% off first treatment'
         ],
         ['%d', '%d', '%s', '%f', '%s', '%s', '%s', '%s']
      );
      // Clear cache
      $this->cache->clear();
      return new WP_REST_Response([
         'success' => true,
         'message' => 'Marked as consulted and reward created'
      ]);
   }
   /**
    * Handle manual mark as treated
    */
   public function handleMarkTreated(WP_REST_Request $request): WP_REST_Response
   {
      $referral_id = $request->get_param('referral_id');
      if (!$referral_id) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Referral ID required'
         ], 400);
      }
      $referral = $this->wpdb->get_row($this->wpdb->prepare(
         "SELECT * FROM {$this->referrals_table} WHERE id = %d",
         $referral_id
      ));
      if (!$referral) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Referral not found'
         ], 404);
      }
      if ($referral->status === 'treated') {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Referral already marked as treated'
         ], 400);
      }
      // Update to treated
      $this->wpdb->update(
         $this->referrals_table,
         [
            'status' => 'treated',
            'treated_at' => current_time('mysql'),
            'treatment_count' => ($referral->treatment_count ?? 0) + 1
         ],
         ['id' => $referral_id],
         ['%s', '%s', '%d'],
         ['%d']
      );
      // Create full rewards for both parties
      $settings = JVB()->referrals()->getRewardSettings();
      // Referrer reward
      $this->wpdb->insert(
         $this->rewards_table,
         [
            'referral_id' => $referral_id,
            'user_id' => $referral->referrer_id,
            'reward_type' => 'referrer',
            'amount' => $settings['referrer_reward_amount'],
            'reward_calculation' => $settings['referrer_reward_type'],
            'status' => 'available',
            'created_at' => current_time('mysql'),
            'notes' => 'Referral reward for completed treatment'
         ],
         ['%d', '%d', '%s', '%f', '%s', '%s', '%s', '%s']
      );
      // Referee reward
      $this->wpdb->insert(
         $this->rewards_table,
         [
            'referral_id' => $referral_id,
            'user_id' => $referral->referee_id,
            'reward_type' => 'referee',
            'amount' => $settings['referee_reward_amount'],
            'reward_calculation' => $settings['referee_reward_type'],
            'status' => 'available',
            'created_at' => current_time('mysql'),
            'notes' => 'Treatment completion reward'
         ],
         ['%d', '%d', '%s', '%f', '%s', '%s', '%s', '%s']
      );
      // Clear cache
      $this->cache->clear();
      return new WP_REST_Response([
         'success' => true,
         'message' => 'Marked as treated and rewards created'
      ]);
   }
   /**
    * Get current user's referral stats
    */
   public function getMyStats(WP_REST_Request $request): WP_REST_Response
   {
      $user_id = get_current_user_id();
      $stats = JVB()->referrals()->getUserStats($user_id);
      return new WP_REST_Response([
         'success' => true,
         'stats' => $stats
      ]);
   }
   /**
    * Get current user's referrals
    */
   public function getMyReferrals(WP_REST_Request $request): WP_REST_Response
   {
      $user_id = get_current_user_id();
      $limit = $request->get_param('limit') ?: 20;
      $referrals = JVB()->referrals()->getUserReferrals($user_id, ['limit' => $limit]);
      return new WP_REST_Response([
         'success' => true,
         'referrals' => $referrals
      ]);
   }
}