| | |
| | | */ |
| | | class ReferralRoutes extends Rest |
| | | { |
| | | protected CustomTable $referrals; |
| | | protected CustomTable $rewards; |
| | | protected CustomTable $treatments; |
| | | |
| | | public function __construct() |
| | | { |
| | | $this->cacheName = 'referrals'; |
| | | $this->cacheTtl = (int)HOUR_IN_SECONDS; |
| | | parent::__construct(); |
| | | |
| | | $this->referrals = CustomTable::for('referrals'); |
| | | $this->rewards = CustomTable::for('referral_rewards'); |
| | | $this->treatments = CustomTable::for('referral_treatments'); |
| | | |
| | | add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3); |
| | | } |
| | | |
| | |
| | | return $this->error('referral_id required', 'missing_id', 400); |
| | | } |
| | | |
| | | // Get referral using CustomTable |
| | | $referral = $this->referrals->get(['id' => $referral_id]); |
| | | if (!$referral) { |
| | | return $this->notFound('Referral not found'); |
| | | $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); |
| | | } |
| | | |
| | | // Update status |
| | | $update_data = ['status' => $status]; |
| | | $update_data["{$status}_at"] = current_time('mysql'); |
| | | |
| | | if ($status === 'treated') { |
| | | $update_data['treatment_count'] = ($referral->treatment_count ?? 0) + 1; |
| | | } |
| | | |
| | | $updated = $this->referrals->update($update_data, ['id' => $referral_id]); |
| | | |
| | | if ($updated !== false) { |
| | | // Create rewards if treated |
| | | if ($status === 'treated') { |
| | | $this->createRewards($referral); |
| | | } |
| | | |
| | | $this->cache->flush(); |
| | | return $this->success(['message' => "Referral marked as {$status}"]); |
| | | } |
| | | |
| | | return $this->error('Failed to update referral', 'update_failed', 500); |
| | | $this->cache->flush(); |
| | | return $this->success(['message' => "Referral marked as {$status}"]); |
| | | } |
| | | |
| | | /** |
| | |
| | | return $this->error('referral_id required', 'missing_id', 400); |
| | | } |
| | | |
| | | // Get referral |
| | | $referral = $this->referrals->get(['id' => $referral_id]); |
| | | if (!$referral) { |
| | | return $this->notFound('Referral not found'); |
| | | $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); |
| | | } |
| | | |
| | | // Check ownership |
| | | $current_user_id = get_current_user_id(); |
| | | if ($referral->referrer_id != $current_user_id && !current_user_can('manage_options')) { |
| | | return $this->forbidden('Unauthorized'); |
| | | } |
| | | |
| | | // Can only remove pending referrals |
| | | if ($referral->status !== 'pending') { |
| | | return $this->error('Can only remove pending referrals', 'invalid_status', 400); |
| | | } |
| | | |
| | | $this->referrals->delete(['id' => $referral_id]); |
| | | $this->cache->flush(); |
| | | |
| | | return $this->success(['message' => 'Referral removed']); |
| | | } |
| | | |
| | |
| | | return $this->error('referral_id required', 'missing_id', 400); |
| | | } |
| | | |
| | | $current_user_id = get_current_user_id(); |
| | | |
| | | // Get referral with ownership check |
| | | $referral = $this->referrals->where([ |
| | | 'id' => $referral_id, |
| | | 'referrer_id' => $current_user_id |
| | | ])->first(); |
| | | |
| | | if (!$referral) { |
| | | return $this->notFound('Referral not found'); |
| | | } |
| | | |
| | | // Check rate limit (once per week) |
| | | $transient_key = 'referral_last_invite_' . md5($referral->referee_email); |
| | | $last_invite = get_transient($transient_key); |
| | | |
| | | if ($last_invite && (time() - $last_invite) < WEEK_IN_SECONDS) { |
| | | return $this->error( |
| | | 'Can only resend once per week', |
| | | 'rate_limit', |
| | | 429 |
| | | ); |
| | | } |
| | | |
| | | // Resend via referral manager |
| | | $result = JVB()->referrals()->sendReferralInvitation( |
| | | $current_user_id, |
| | | $referral->referee_email, |
| | | $referral->referee_name, |
| | | sprintf('Reminder: Join %s', get_bloginfo('name')), |
| | | 'Just a friendly reminder about my invitation!' |
| | | ); |
| | | $result = JVB()->referrals()->resendInvitation($referral_id, get_current_user_id()); |
| | | |
| | | if (is_wp_error($result)) { |
| | | return $this->error($result->get_error_message(), 'send_failed', 500); |
| | | $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); |
| | | } |
| | | |
| | | set_transient($transient_key, time(), WEEK_IN_SECONDS); |
| | | return $this->success(['message' => 'Invitation resent']); |
| | | } |
| | | |
| | |
| | | */ |
| | | protected function getAllReferrals(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | global $wpdb; |
| | | |
| | | $where = ['1=1']; |
| | | $where_params = []; |
| | | |
| | | // Build WHERE conditions |
| | | $status = $request->get_param('status'); |
| | | if ($status && $status !== 'all') { |
| | | $where[] = 'status = %s'; |
| | | $where_params[] = $status; |
| | | } |
| | | |
| | | if ($date_start = $request->get_param('date_start')) { |
| | | $where[] = 'referred_at >= %s'; |
| | | $where_params[] = $date_start; |
| | | } |
| | | |
| | | if ($date_end = $request->get_param('date_end')) { |
| | | $where[] = 'referred_at <= %s'; |
| | | $where_params[] = $date_end; |
| | | } |
| | | |
| | | $search = $request->get_param('search'); |
| | | if (!empty($search)) { |
| | | $search_term = '%' . $wpdb->esc_like($search) . '%'; |
| | | $where[] = '(r.referee_name LIKE %s OR r.referee_email LIKE %s OR r.referral_code LIKE %s OR u.display_name LIKE %s)'; |
| | | $where_params[] = $search_term; |
| | | $where_params[] = $search_term; |
| | | $where_params[] = $search_term; |
| | | $where_params[] = $search_term; |
| | | } |
| | | |
| | | $limit = $request->get_param('limit') ?? 50; |
| | | $offset = $request->get_param('offset') ?? 0; |
| | | |
| | | $where_params[] = $limit; |
| | | $where_params[] = $offset; |
| | | |
| | | // Use CustomTable's query method |
| | | $query = "SELECT r.*, u.display_name as referrer_name |
| | | FROM {table} r |
| | | LEFT JOIN {$wpdb->users} u ON r.referrer_id = u.ID |
| | | WHERE " . implode(' AND ', $where) . " |
| | | ORDER BY referred_at DESC |
| | | LIMIT %d OFFSET %d"; |
| | | |
| | | $items = $this->referrals->queryResults($query, $where_params); |
| | | |
| | | return $this->success([ |
| | | 'items' => $items, |
| | | 'total' => count($items) |
| | | ]); |
| | | } |
| | | |
| | | /** |
| | | * Helper: Create rewards for completed referral |
| | | */ |
| | | protected function createRewards(object $referral): void |
| | | { |
| | | $settings = JVB()->referrals()->getRewardSettings(); |
| | | |
| | | // Referrer reward |
| | | $this->rewards->insert([ |
| | | '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' |
| | | $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, |
| | | ]); |
| | | |
| | | // Referee reward |
| | | if ($referral->referee_id) { |
| | | $this->rewards->insert([ |
| | | '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' |
| | | ]); |
| | | } |
| | | return $this->success(['items' => $items, 'total' => count($items)]); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Process queued referral operations |
| | | */ |