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 { // Get user's referrals register_rest_route($this->namespace, "/{$this->route}", [ 'methods' => 'GET', 'callback' => [$this, 'getUserReferrals'], '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', 'callback' => [$this, 'getReferralCode'], 'permission_callback' => [$this, 'checkPermission'] ]); // Track referral click (public endpoint) register_rest_route($this->namespace, "/{$this->route}/track", [ 'methods' => 'POST', 'callback' => [$this, 'trackReferralClick'], 'permission_callback' => [$this, 'checkRateLimit'], 'args' => [ 'code' => [ 'required' => true, 'type' => 'string' ] ] ]); // Mark referral as treated register_rest_route($this->namespace, "/{$this->route}/(?P\d+)/treat", [ 'methods' => 'POST', 'callback' => [$this, 'markAsTreated'], 'permission_callback' => function() { return current_user_can('manage_options'); }, 'args' => [ 'id' => [ 'required' => true, 'validate_callback' => function($param) { return is_numeric($param); } ] ] ]); // 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, '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', 'callback' => [$this, 'getTopReferrers'], 'permission_callback' => function() { return current_user_can('manage_options'); }, 'args' => [ 'period' => [ 'default' => 'week', 'enum' => ['day', 'week', 'month', 'all'] ], 'limit' => [ 'default' => 10, 'type' => 'integer' ] ] ]); // Get/Update referral settings (admin only) register_rest_route($this->namespace, "/{$this->route}/settings", [ [ 'methods' => 'GET', 'callback' => [$this, 'getSettings'], 'permission_callback' => function() { return current_user_can('manage_options'); } ], [ 'methods' => 'POST', 'callback' => [$this, 'updateSettings'], 'permission_callback' => function() { return current_user_can('manage_options'); } ] ]); 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\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 { return is_user_logged_in(); } /** * Get user's referrals */ public function getUserReferrals(WP_REST_Request $request): WP_REST_Response { $user_id = get_current_user_id(); $args = [ 'status' => $request->get_param('status') ?? 'all', 'limit' => $request->get_param('limit') ?? 50, 'offset' => $request->get_param('offset') ?? 0 ]; $referrals = JVB()->referrals()->getUserReferrals($user_id, $args); return new WP_REST_Response([ 'success' => true, 'referrals' => $referrals ]); } /** * Get user's referral code */ public function getReferralCode(WP_REST_Request $request): WP_REST_Response { $user_id = get_current_user_id(); $code = JVB()->referrals()->getUserReferralCode($user_id); if (is_wp_error($code)) { return new WP_REST_Response([ 'success' => false, 'message' => $code->get_error_message() ], 400); } return new WP_REST_Response([ 'success' => true, 'code' => $code, 'share_url' => home_url('/?ref=' . $code) ]); } /** * Update user's referral code */ public function updateReferralCode(WP_REST_Request $request): WP_REST_Response { $user_id = get_current_user_id(); $new_code = strtoupper(sanitize_text_field($request->get_param('code'))); $result = JVB()->referrals()->getUserReferralCode($user_id, $new_code); if (is_wp_error($result)) { return new WP_REST_Response([ 'success' => false, 'message' => $result->get_error_message() ], 400); } return new WP_REST_Response([ 'success' => true, 'code' => $result, 'message' => 'Referral code updated successfully' ]); } /** * Track referral click and store in session */ public function trackReferralClick(WP_REST_Request $request): WP_REST_Response { $code = strtoupper(sanitize_text_field($request->get_param('code'))); // Start session if not already started if (session_status() === PHP_SESSION_NONE) { session_start(); } // Store referral code in both session and cookie (30 day expiry) $_SESSION[BASE . 'referral_code'] = $code; setcookie(BASE . 'referral_code', $code, time() + (30 * DAY_IN_SECONDS), '/'); return new WP_REST_Response([ 'success' => true, 'message' => 'Referral tracked' ]); } /** * Mark referral as treated */ public function markAsTreated(WP_REST_Request $request): WP_REST_Response { $referral_id = intval($request->get_param('id')); $result = JVB()->referrals()->markAsTreated($referral_id, true); if (!$result) { return new WP_REST_Response([ 'success' => false, 'message' => 'Failed to update referral' ], 400); } return new WP_REST_Response([ 'success' => true, 'message' => 'Referral marked as treated and rewards created' ]); } /** * Get user stats */ public function getUserStats(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 top referrers */ public function getTopReferrers(WP_REST_Request $request): WP_REST_Response { $period = $request->get_param('period') ?? 'week'; $limit = $request->get_param('limit') ?? 10; $top_referrers = JVB()->referrals()->getTopReferrers($limit, $period); return new WP_REST_Response([ 'success' => true, 'period' => $period, 'referrers' => $top_referrers ]); } /** * Get referral settings */ public function getSettings(WP_REST_Request $request): WP_REST_Response { $settings = get_option(BASE . 'referral_settings', []); return new WP_REST_Response([ 'success' => true, 'settings' => $settings ]); } /** * Update referral settings */ public function updateSettings(WP_REST_Request $request): WP_REST_Response { $settings = [ 'referrer_reward_type' => $request->get_param('referrer_reward_type') ?? 'per_user', 'referrer_reward_amount' => floatval($request->get_param('referrer_reward_amount') ?? 25), 'referee_reward_type' => $request->get_param('referee_reward_type') ?? 'percentage', 'referee_reward_amount' => floatval($request->get_param('referee_reward_amount') ?? 20), 'referee_reward_applies_to' => $request->get_param('referee_reward_applies_to') ?? 'first_order' ]; update_option(BASE . 'referral_settings', $settings); return new WP_REST_Response([ 'success' => true, 'message' => 'Settings updated successfully', '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 ]); } }