Jake Vanderwerf
4 days ago 747d741293e064a979d7bf6c143ef969ea6d7629
inc/rest/routes/ReferralRoutes.php
@@ -1,7 +1,12 @@
<?php
namespace JVBase\rest\routes;
use JVBase\rest\RestRouteManager;
use JVBase\base\Site;
use JVBase\importers\JaneAppClientImporter;
use JVBase\importers\JaneAppSalesImporter;
use JVBase\managers\CustomTable;
use JVBase\rest\Rest;
use JVBase\rest\Route;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
@@ -13,587 +18,545 @@
/**
 * REST API routes for referral system
 */
class ReferralRoutes extends RestRouteManager
class ReferralRoutes extends Rest
{
   public function __construct()
   {
      $this->route = 'referrals';
      $this->cacheName = 'referrals';
      $this->cacheTtl = (int)HOUR_IN_SECONDS;
      parent::__construct();
      add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
   }
   public function registerRoutes(): void
   {
      // Get user's referrals
      register_rest_route($this->namespace, "/{$this->route}", [
         'methods' => 'GET',
         'callback' => [$this, 'getUserReferrals'],
         'permission_callback' => [$this, 'checkPermission']
      ]);
      // Main referrals endpoint - list and manage referrals
      Route::for('referrals')
         ->get([$this, 'getReferrals'])
         ->args([
            'user' => 'integer',
            'status' => 'string|enum:all,pending,consulted,treated,unused,registered,completed|default:all',
            'date_start' => 'string',
            'date_end' => 'string',
            'limit' => 'integer|default:50',
            'offset' => 'integer|default:0',
            'search' => 'string'
         ])
         ->rateLimit()
         ->post([$this, 'handleAction'])
         ->args([
            'action' => 'string|required|enum:invite,consulted,treated,remove,resend'
         ])
         ->auth('user')
         ->rateLimit(10)
         ->register();
      register_rest_route($this->namespace, '/referrals/register', [
         'methods' => 'POST',
         'callback' => [$this, 'registerWithReferral'],
         'permission_callback' => '__return_true',
         '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));
               }
            ]
         ]
      ]);
      // Referral code endpoint
      Route::for('referrals/code')
         ->get([$this, 'getCode'])
         ->args(['user' => 'integer'])
         ->auth('user')
         ->rateLimit(30)
         ->post([$this, 'validateCode'])
         ->args(['code' => 'string|required'])
         ->auth('public')
         ->rateLimit(10)
         ->register();
      register_rest_route($this->namespace, '/referrals/check-code', [
         'methods' => 'POST',
         'callback' => [$this, 'checkReferralCode'],
         'permission_callback' => '__return_true',
         'args' => [
            'code' => [
               'required' => true,
               'type' => 'string',
               'sanitize_callback' => function($code) {
                  return strtoupper(sanitize_text_field($code));
               }
            ]
         ]
      ]);
      // Stats endpoint
      Route::for('referrals/stats')
         ->get([$this, 'getStats'])
         ->args(['user' => 'integer'])
         ->auth('user')
         ->rateLimit(30)
         ->register();
      // Get or create referral code
      register_rest_route($this->namespace, "/{$this->route}/code", [
         'methods' => 'GET',
         'callback' => [$this, 'getReferralCode'],
         'permission_callback' => [$this, 'checkPermission']
      ]);
      // Settings endpoint (admin only)
      Route::for('referrals/settings')
         ->get([$this, 'getSettings'])
         ->post([$this, 'updateSettings'])
         ->auth('admin')
         ->rateLimit(10)
         ->register();
      // Track referral click (public endpoint)
      register_rest_route($this->namespace, "/{$this->route}/track", [
         'methods' => 'POST',
         'callback' => [$this, 'trackReferralClick'],
         'permission_callback' => '__return_true',
         'args' => [
            'code' => [
               'required' => true,
               'type' => 'string'
            ]
         ]
      ]);
      // CSV Upload endpoints (admin only)
      Route::for('referrals/upload-clients')
         ->post([$this, 'handleClientUpload'])
         ->auth('admin')
         ->rateLimit(3)
         ->register();
      // Mark referral as treated
      register_rest_route($this->namespace, "/{$this->route}/(?P<id>\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');
            }
         ]
      ]);
   }
   public function checkPermission(WP_REST_Request $request): bool
   {
      return is_user_logged_in();
      Route::for('referrals/upload-sales')
         ->post([$this, 'handleSalesUpload'])
         ->auth('admin')
         ->rateLimit(3)
         ->register();
   }
   /**
    * Get user's referrals
    * GET /referrals
    * Get referrals with optional filters
    * - User gets their own referrals
    * - Admin with no user param gets all referrals
    */
   public function getUserReferrals(WP_REST_Request $request): WP_REST_Response
   public function getReferrals(WP_REST_Request $request): WP_REST_Response
   {
      $user_id = get_current_user_id();
      $user_id = $request->get_param('user');
      // Determine scope: admin without user param gets all referrals
      if (!$user_id) {
         $current_user_id = get_current_user_id();
         if (current_user_can('manage_options')) {
            return $this->getAllReferrals($request);
         }
         $user_id = $current_user_id;
      }
      // Build cache key
      $args = [
         'status' => $request->get_param('status') ?? 'all',
         'limit' => $request->get_param('limit') ?? 50,
         'offset' => $request->get_param('offset') ?? 0
         'offset' => $request->get_param('offset') ?? 0,
         'date_start' => $request->get_param('date_start'),
         'date_end' => $request->get_param('date_end'),
      ];
      $cache_key = "user_{$user_id}_" . $this->cache->generateKey($args);
      // Check 304 Not Modified
      $cache_check = $this->checkHeaders($request, $cache_key);
      if ($cache_check instanceof WP_REST_Response) {
         return $cache_check;
      }
      // Get referrals from manager
      $referrals = JVB()->referrals()->getUserReferrals($user_id, $args);
      return new WP_REST_Response([
         'success' => true,
         'referrals' => $referrals
      ]);
      $data = [
         'items' => $referrals,
         'total' => count($referrals)
      ];
      $response = $this->success($data);
      return $this->addCacheHeaders($response);
   }
   /**
    * POST /referrals
    * Handle various referral actions based on 'action' parameter
    */
   public function handleAction(WP_REST_Request $request): WP_REST_Response
   {
      $action = $request->get_param('action');
      return match($action) {
         'invite' => $this->actionInvite($request),
         'consulted' => $this->actionUpdateStatus($request, 'consulted'),
         'treated' => $this->actionUpdateStatus($request, 'treated'),
         'remove' => $this->actionRemove($request),
         'resend' => $this->actionResend($request),
         default => $this->error('Invalid action', 'invalid_action', 400)
      };
   }
   /**
    * Action: Send batch referral invitations
    */
   protected function actionInvite(WP_REST_Request $request): WP_REST_Response
   {
      $user = absint($request->get_param('user'));
      if (!$user || !get_userdata($user)) {
         return $this->error('Invalid user', 'invalid_user', 400);
      }
      //Additional check to not send too many emails in an hour
      $user = absint($request->get_param('user'));
      $transient_key = "referral_invite_limit_{$user}";
      $recent_invites = get_transient($transient_key) ?: 0;
      if ($recent_invites >= 20) { // Max 5 batch invites per hour
         return $this->error('Too many invitations sent. Please try again later.', 'rate_limit', 429);
      }
      set_transient($transient_key, $recent_invites + 1, HOUR_IN_SECONDS);
      $subject = sanitize_text_field($request->get_param('subject'));
      $message = sanitize_textarea_field($request->get_param('message'));
      $invitations = $request->get_param('invite');
      // Validate and sanitize invitations
      $sanitized_invitations = [];
      foreach ($invitations as $invite) {
         if (isset($invite['name'], $invite['email'])) {
            $sanitized_invitations[] = [
               'name' => sanitize_text_field($invite['name']),
               'email' => sanitize_email($invite['email'])
            ];
         }
      }
      if (empty($sanitized_invitations)) {
         return $this->error('No valid invitations provided', 'no_invitations', 400);
      }
      $operationID = sanitize_text_field($request->get_param('id'));
      JVB()->queue()->queueOperation(
         'referral_invite',
         $user,
         [
            'subject' => $subject,
            'message' => $message,
            'invitations' => $sanitized_invitations
         ],
         ['operation_id' => $operationID]
      );
      return $this->queued($operationID, 'Referral invitations queued');
   }
   /**
    * Action: Update referral status (admin only)
    */
   protected function actionUpdateStatus(WP_REST_Request $request, string $status): WP_REST_Response
   {
      if (!current_user_can('manage_options')) {
         return $this->forbidden('Admin permission required');
      }
      $referral_id = $request->get_param('referral_id');
      if (!$referral_id) {
         return $this->error('referral_id required', 'missing_id', 400);
      }
      $result = JVB()->referrals()->updateStatus($referral_id, $status);
      if (is_wp_error($result)) {
         $code = $result->get_error_code();
         return $code === 'not_found'
            ? $this->notFound($result->get_error_message())
            : $this->error($result->get_error_message(), $code, 500);
      }
      $this->cache->flush();
      return $this->success(['message' => "Referral marked as {$status}"]);
   }
   /**
    * Action: Remove referral
    */
   protected function actionRemove(WP_REST_Request $request): WP_REST_Response
   {
      $referral_id = $request->get_param('referral_id');
      if (!$referral_id) {
         return $this->error('referral_id required', 'missing_id', 400);
      }
      $result = JVB()->referrals()->removeReferral($referral_id, get_current_user_id());
      if (is_wp_error($result)) {
         $code = $result->get_error_code();
         $status = match($code) {
            'not_found'      => 404,
            'unauthorized'   => 403,
            'invalid_status' => 400,
            default          => 500
         };
         return $this->error($result->get_error_message(), $code, $status);
      }
      $this->cache->flush();
      return $this->success(['message' => 'Referral removed']);
   }
   /**
    * Action: Resend invitation
    */
   protected function actionResend(WP_REST_Request $request): WP_REST_Response
   {
      $referral_id = $request->get_param('referral_id');
      if (!$referral_id) {
         return $this->error('referral_id required', 'missing_id', 400);
      }
      $result = JVB()->referrals()->resendInvitation($referral_id, get_current_user_id());
      if (is_wp_error($result)) {
         $code = $result->get_error_code();
         $status = match($code) {
            'not_found'  => 404,
            'rate_limit' => 429,
            default      => 500
         };
         return $this->error($result->get_error_message(), $code, $status);
      }
      return $this->success(['message' => 'Invitation resent']);
   }
   /**
    * GET /referrals/code
    * Get user's referral code
    */
   public function getReferralCode(WP_REST_Request $request): WP_REST_Response
   public function getCode(WP_REST_Request $request): WP_REST_Response
   {
      $user_id = get_current_user_id();
      $user_id = $request->get_param('user') ?? get_current_user_id();
      // Check permission
      if ($user_id != get_current_user_id() && !current_user_can('manage_options')) {
         return $this->forbidden('Unauthorized');
      }
      $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 $this->error($code->get_error_message(), 'code_error', 400);
      }
      return new WP_REST_Response([
         'success' => true,
      return $this->success([
         'code' => $code,
         'share_url' => home_url('/?ref=' . $code)
      ]);
   }
   /**
    * Update user's referral code
    * POST /referrals/code
    * Validate a 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
   public function validateCode(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();
      if (empty($code)) {
         return $this->error('Code required', 'missing_code', 400);
      }
      // 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), '/');
      $referrer = JVB()->referrals()->getUserByReferralCode($code);
      return new WP_REST_Response([
         'success' => true,
         'message' => 'Referral tracked'
      if (!$referrer) {
         return $this->error('Invalid referral code', 'invalid_code', 404);
      }
      // Check self-referral
      if (is_user_logged_in() && get_current_user_id() === $referrer->ID) {
         return $this->error('Cannot use your own referral code', 'self_referral', 400);
      }
      return $this->success([
         'valid' => true,
         'code' => $code,
         'referrer_name' => $referrer->display_name
      ]);
   }
   /**
    * Mark referral as treated
    * GET /referrals/stats
    * Get user's referral statistics
    */
   public function markAsTreated(WP_REST_Request $request): WP_REST_Response
   public function getStats(WP_REST_Request $request): WP_REST_Response
   {
      $referral_id = intval($request->get_param('id'));
      $user_id = $request->get_param('user') ?? get_current_user_id();
      $cache_key = "stats_{$user_id}";
      $result = JVB()->referrals()->markAsTreated($referral_id, true);
      if (!$result) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Failed to update referral'
         ], 400);
      // Check 304 Not Modified
      $cache_check = $this->checkHeaders($request, $cache_key);
      if ($cache_check instanceof WP_REST_Response) {
         return $cache_check;
      }
      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
      ]);
      $response = $this->success(['items' => [$stats]]);
      return $this->addCacheHeaders($response);
   }
   /**
    * 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
    * GET /referrals/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
      ]);
      $settings = JVB()->referrals()->getRewardSettings();
      return $this->success(['settings' => $settings]);
   }
   /**
    * Update referral settings
    * POST /referrals/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_type' => $request->get_param('referrer_reward_type') ?? 'fixed',
         'referrer_reward_amount' => floatval($request->get_param('referrer_reward_amount') ?? 25),
         'referrer_reward_applies_to' => $request->get_param('referrer_reward_applies_to') ?? 'per_user',
         '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);
      $this->cache->flush();
      return new WP_REST_Response([
         'success' => true,
         'message' => 'Settings updated successfully',
      return $this->success([
         'message' => 'Settings updated',
         'settings' => $settings
      ]);
   }
   /**
    * Send a single referral invitation
    *
    * @param WP_REST_Request $request
    * @return WP_REST_Response
    * Helper: Get all referrals (admin only)
    */
   public function sendInvitation(WP_REST_Request $request): WP_REST_Response
   protected function getAllReferrals(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'));
      $items = JVB()->referrals()->getAllReferrals([
         'status'     => $request->get_param('status') ?? 'all',
         'search'     => $request->get_param('search'),
         'date_start' => $request->get_param('date_start'),
         'date_end'   => $request->get_param('date_end'),
         'limit'      => $request->get_param('limit') ?? 50,
         'offset'     => $request->get_param('offset') ?? 0,
      ]);
      // 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);
      return $this->success(['items' => $items, 'total' => count($items)]);
   }
   /**
    * Send batch referral invitations
    *
    * @param WP_REST_Request $request
    * @return WP_REST_Response
    * Process queued referral operations
    */
   public function sendBatchInvitations(WP_REST_Request $request): WP_REST_Response
   public function processOperation(WP_Error|array $result, object $operation, array $data): array|WP_Error
   {
      $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);
         }
      if ($operation->type !== 'referral_invite') {
         return $result;
      }
      // 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
         ]
      $result = JVB()->referrals()->sendBatchReferralInvitations(
         $operation->user_id,
         $data['invitations'],
         $data['subject'],
         $data['message']
      );
      if (is_wp_error($result)) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Failed to send registration link. Please try again.'
         ], 500);
      if ($result['success']) {
         $this->cache->flush();
      }
      return new WP_REST_Response([
      return [
         'success' => true,
         'message' => 'Check your email! We sent you a link to complete your registration.',
         'email' => $email
      ], 200);
         'message' => sprintf(
            'Sent invitations. Success: %d. Failed: %d.',
            count($result['result']['success']),
            count($result['result']['failed'])
         ),
         'details' => [
            'successful' => $result['result']['success'],
            'failed' => $result['result']['failed'],
            'total' => count($data['invitations'])
         ]
      ];
   }
   public function checkReferralCode(WP_REST_Request $request): WP_REST_Response
   /**
    * Handle client CSV upload
    */
   public function handleClientUpload(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);
      if (empty($_FILES['file'])) {
         return $this->error('No file uploaded', 'no_file', 400);
      }
      $referral_manager = JVB()->referrals();
      $referrer = $referral_manager->getUserByReferralCode($code);
      $file = $_FILES['file'];
      if (!$referrer) {
         return new WP_REST_Response([
            'success' => false,
            'message' => 'Invalid referral code'
         ], 404);
      if ($file['error'] !== UPLOAD_ERR_OK) {
         return $this->error('File upload error: ' . $file['error'], 'upload_error', 400);
      }
      // Return basic referrer info (no sensitive data)
      return new WP_REST_Response([
         'success' => true,
         'code' => $code,
         'referrer_name' => $referrer->display_name,
      ], 200);
      // Validate file type
      $allowed_types = ['text/csv', 'application/vnd.ms-excel', 'text/plain'];
      $finfo = finfo_open(FILEINFO_MIME_TYPE);
      $mime_type = finfo_file($finfo, $file['tmp_name']);
      finfo_close($finfo);
      if (!in_array($mime_type, $allowed_types) && !in_array($file['type'], $allowed_types)) {
         return $this->error('File must be a CSV', 'invalid_file_type', 400);
      }
      // Validate file size (10MB max)
      if ($file['size'] > 10 * 1024 * 1024) {
         return $this->error('File size exceeds 10MB limit', 'file_too_large', 400);
      }
      // Import using JaneAppClientImporter
      $importer = new JaneAppClientImporter();
      $default_role = get_option(BASE . 'referral_role', Site::getDefaultReferralRole());
      $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 $this->error($result->get_error_message(), 'import_failed', 500);
      }
      $this->cache->flush();
      return $this->success([
         'message' => sprintf(
            'Import complete: %d created, %d updated, %d skipped',
            $result['created'],
            $result['updated'],
            $result['skipped']
         ),
         'stats' => $result,
         'skipped_details' => $result['skipped_details'] ?? []
      ]);
   }
   /**
    * Handle sales CSV upload
    */
   public function handleSalesUpload(WP_REST_Request $request): WP_REST_Response
   {
      if (empty($_FILES['file'])) {
         return $this->error('No file uploaded', 'no_file', 400);
      }
      $file = $_FILES['file'];
      if ($file['error'] !== UPLOAD_ERR_OK) {
         return $this->error('File upload error: ' . $file['error'], 'upload_error', 400);
      }
      // Validate file type
      $allowed_types = ['text/csv', 'application/vnd.ms-excel', 'text/plain'];
      $finfo = finfo_open(FILEINFO_MIME_TYPE);
      $mime_type = finfo_file($finfo, $file['tmp_name']);
      finfo_close($finfo);
      if (!in_array($mime_type, $allowed_types) && !in_array($file['type'], $allowed_types)) {
         return $this->error('File must be a CSV', 'invalid_file_type', 400);
      }
      // Validate file size (10MB max)
      if ($file['size'] > 10 * 1024 * 1024) {
         return $this->error('File size exceeds 10MB limit', 'file_too_large', 400);
      }
      // Import using JaneAppSalesImporter
      $importer = new JaneAppSalesImporter();
      $result = $importer->importFromCSV($file['tmp_name'], ['skip_existing' => true]);
      if (is_wp_error($result)) {
         return $this->error($result->get_error_message(), 'import_failed', 500);
      }
      $this->cache->flush();
      return $this->success([
         'message' => 'Sales imported successfully',
         'stats' => $result
      ]);
   }
}