namespace, '/account/affiliate', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_dashboard'], 'permission_callback' => [$this, 'check_permission'], ]); register_rest_route($this->namespace, '/account/affiliate/apply', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'apply_affiliate'], 'permission_callback' => [$this, 'check_permission'], ]); register_rest_route($this->namespace, '/account/affiliate/referrals', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_referrals'], 'permission_callback' => [$this, 'check_permission'], ]); register_rest_route($this->namespace, '/account/affiliate/payouts', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_payouts'], 'permission_callback' => [$this, 'check_permission'], ]); register_rest_route($this->namespace, '/account/affiliate/payment-details', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_payment_details'], 'permission_callback' => [$this, 'check_permission'], ]); register_rest_route($this->namespace, '/account/affiliate/payment-details', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'update_payment_details'], 'permission_callback' => [$this, 'check_permission'], ]); // Affiliate Collections register_rest_route($this->namespace, '/account/affiliate/collections', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'get_collections'], 'permission_callback' => [$this, 'check_permission'], ], [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'create_collection'], 'permission_callback' => [$this, 'check_permission'], ] ]); register_rest_route($this->namespace, '/account/affiliate/collections/(?P\d+)', [ [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [$this, 'update_collection'], 'permission_callback' => [$this, 'check_permission'], ], [ 'methods' => WP_REST_Server::DELETABLE, 'callback' => [$this, 'delete_collection'], 'permission_callback' => [$this, 'check_permission'], ] ]); } public function check_permission() { return is_user_logged_in(); } public function get_dashboard(WP_REST_Request $request) { global $wpdb; $user_id = get_current_user_id(); $table = $wpdb->prefix . 'woonoow_affiliates'; $affiliate = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE user_id = %d", $user_id), ARRAY_A); if (!$affiliate) { return new \WP_Error('not_found', 'Affiliate profile not found', ['status' => 404]); } // Get global default rate $global_rate = (float) get_option('woonoow_affiliate_default_rate', 10); // Use custom rate if set, otherwise global $effective_rate = !empty($affiliate['custom_commission_rate']) ? (float) $affiliate['custom_commission_rate'] : $global_rate; $referrals_table = $wpdb->prefix . 'woonoow_referrals'; $earnings = $wpdb->get_row($wpdb->prepare( "SELECT SUM(CASE WHEN status = 'approved' THEN commission_amount ELSE 0 END) as total_earnings, SUM(CASE WHEN status = 'pending' THEN commission_amount ELSE 0 END) as pending_earnings FROM $referrals_table WHERE affiliate_id = %d", $affiliate['id'] )); $affiliate['global_commission_rate'] = $global_rate; $affiliate['commission_rate'] = $effective_rate; $affiliate['total_earnings'] = $earnings->total_earnings ?: 0; $affiliate['pending_earnings'] = $earnings->pending_earnings ?: 0; if (class_exists('\WooNooW\Modules\Affiliate\AffiliateSettings')) { $affiliate['collections_enabled'] = \WooNooW\Modules\Affiliate\AffiliateSettings::get_setting('woonoow_affiliate_enable_curated_collections', true); } else { $affiliate['collections_enabled'] = true; } return rest_ensure_response($affiliate); } public function apply_affiliate(WP_REST_Request $request) { global $wpdb; $user_id = get_current_user_id(); $table = $wpdb->prefix . 'woonoow_affiliates'; // Check if already applied $exists = $wpdb->get_var($wpdb->prepare("SELECT id FROM $table WHERE user_id = %d", $user_id)); if ($exists) { return new \WP_Error('exists', 'You have already applied.', ['status' => 400]); } // Generate simple code $user = wp_get_current_user(); $referral_code = strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $user->user_login)) . wp_generate_password(4, false); $auto_approve = get_option('woonoow_affiliate_auto_approve', false); $status = $auto_approve ? 'active' : 'pending'; $wpdb->insert($table, [ 'user_id' => $user_id, 'referral_code' => $referral_code, 'commission_rate' => get_option('woonoow_affiliate_default_rate', 10), // 10% default 'status' => $status ]); // Trigger email notification for admin $admin_email = get_option('admin_email'); do_action('woonoow/email/trigger', 'affiliate_application_received', $admin_email, [ 'affiliate_name' => $user->display_name, 'customer_email' => $user->user_email ]); if ($auto_approve) { do_action('woonoow/email/trigger', 'affiliate_application_approved', $user->user_email, [ 'affiliate_name' => $user->display_name, 'customer_email' => $user->user_email, 'referral_code' => $referral_code ]); } return rest_ensure_response(['success' => true, 'status' => $status, 'referral_code' => $referral_code]); } public function get_referrals(WP_REST_Request $request) { global $wpdb; $user_id = get_current_user_id(); $affiliates_table = $wpdb->prefix . 'woonoow_affiliates'; $referrals_table = $wpdb->prefix . 'woonoow_referrals'; $affiliate = $wpdb->get_row($wpdb->prepare("SELECT id FROM $affiliates_table WHERE user_id = %d", $user_id)); if (!$affiliate) { return rest_ensure_response([]); } $limit = (int) $request->get_param('limit'); $page = max(1, (int) $request->get_param('page')); $order_id = $request->get_param('order_id') ? (int) $request->get_param('order_id') : null; $where = $wpdb->prepare("WHERE r.affiliate_id = %d", $affiliate->id); if ($order_id) { $where .= $wpdb->prepare(" AND r.order_id = %d", $order_id); } $sql = "SELECT r.*, COALESCE(NULLIF(r.cancelled_reason, ''), NULL) as cancelled_reason, COALESCE(r.approved_at, r.created_at) as approved_at FROM $referrals_table r $where ORDER BY r.created_at DESC"; if ($limit > 0) { $offset = ($page - 1) * $limit; $sql .= $wpdb->prepare(" LIMIT %d OFFSET %d", $limit, $offset); } $referrals = $wpdb->get_results($sql, ARRAY_A); $total = $wpdb->get_var("SELECT COUNT(r.id) FROM $referrals_table r $where"); // Attach customer data if enabled if (!empty($referrals) && AffiliateSettings::get_setting('woonoow_affiliate_share_customer_data', false)) { foreach ($referrals as &$ref) { if (!empty($ref['order_id'])) { $order = wc_get_order($ref['order_id']); if ($order) { $ref['customer_name'] = trim($order->get_billing_first_name() . ' ' . $order->get_billing_last_name()); $ref['customer_email'] = $order->get_billing_email(); } } } } return rest_ensure_response([ 'referrals' => $referrals, 'total' => (int) $total, 'page' => $page, 'limit' => $limit > 0 ? $limit : (int) $total, 'total_pages' => $limit > 0 ? ceil($total / $limit) : 1 ]); } public function get_payouts(WP_REST_Request $request) { global $wpdb; $user_id = get_current_user_id(); $affiliates_table = $wpdb->prefix . 'woonoow_affiliates'; $payouts_table = $wpdb->prefix . 'woonoow_affiliate_payouts'; $affiliate = $wpdb->get_row($wpdb->prepare("SELECT id FROM $affiliates_table WHERE user_id = %d", $user_id)); if (!$affiliate) { return rest_ensure_response([]); } $payouts = $wpdb->get_results($wpdb->prepare( "SELECT id, amount, currency, method, status, notes, created_at, completed_at FROM $payouts_table WHERE affiliate_id = %d ORDER BY created_at DESC", $affiliate->id ), ARRAY_A); return rest_ensure_response($payouts); } public function get_payment_details(WP_REST_Request $request) { global $wpdb; $user_id = get_current_user_id(); $table = $wpdb->prefix . 'woonoow_affiliates'; $affiliate = $wpdb->get_row($wpdb->prepare( "SELECT payment_method, payment_details FROM $table WHERE user_id = %d", $user_id )); if (!$affiliate) { return new \WP_Error('not_found', 'Affiliate not found', ['status' => 404]); } $payment_details = $affiliate->payment_details ? json_decode($affiliate->payment_details, true) : []; return rest_ensure_response([ 'payment_method' => $affiliate->payment_method ?: '', 'payment_details' => $payment_details ?: new \stdClass() ]); } public function update_payment_details(WP_REST_Request $request) { global $wpdb; $user_id = get_current_user_id(); $table = $wpdb->prefix . 'woonoow_affiliates'; // Get allowed payment methods from settings $settings = get_option('woonoow_module_affiliate_settings', []); $allowed_methods = $settings['woonoow_affiliate_payment_methods'] ?? ['bank_transfer']; $payment_method = sanitize_text_field($request->get_param('payment_method') ?: ''); $payment_details_raw = $request->get_param('payment_details') ?: []; // Validate payment method is allowed if (!in_array($payment_method, $allowed_methods)) { return new \WP_Error( 'invalid_payment_method', 'This payment method is not available. Please contact admin.', ['status' => 400] ); } // Sanitize payment details based on method $sanitized_details = self::sanitize_payment_details($payment_method, $payment_details_raw); $result = $wpdb->update( $table, [ 'payment_method' => $payment_method, 'payment_details' => json_encode($sanitized_details) ], ['user_id' => $user_id] ); if ($result === false) { return new \WP_Error('update_failed', 'Failed to update payment details', ['status' => 500]); } return rest_ensure_response([ 'success' => true, 'payment_method' => $payment_method, 'payment_details' => $sanitized_details ]); } /** * Sanitize payment details based on payment method */ private static function sanitize_payment_details($method, $details) { $sanitized = []; switch ($method) { case 'bank_transfer': $sanitized['bank_name'] = sanitize_text_field($details['bank_name'] ?? ''); $sanitized['account_number'] = sanitize_text_field($details['account_number'] ?? ''); $sanitized['account_holder'] = sanitize_text_field($details['account_holder'] ?? ''); $sanitized['swift_code'] = sanitize_text_field($details['swift_code'] ?? ''); $sanitized['bank_address'] = sanitize_text_field($details['bank_address'] ?? ''); break; case 'paypal': case 'wise': case 'skrill': case 'payoneer': $sanitized['email'] = sanitize_email($details['email'] ?? ''); $sanitized['name'] = sanitize_text_field($details['name'] ?? ''); break; case 'custom': $sanitized['notes'] = sanitize_textarea_field($details['notes'] ?? ''); break; } return $sanitized; } // --- Collections --- public function get_collections(WP_REST_Request $request) { if (class_exists('\WooNooW\Modules\Affiliate\AffiliateSettings') && !\WooNooW\Modules\Affiliate\AffiliateSettings::get_setting('woonoow_affiliate_enable_curated_collections', true)) { return new \WP_Error('rest_forbidden', 'Curated collections are disabled.', ['status' => 403]); } global $wpdb; $user_id = get_current_user_id(); $affiliate_table = $wpdb->prefix . 'woonoow_affiliates'; $collections_table = $wpdb->prefix . 'woonoow_affiliate_collections'; $affiliate = $wpdb->get_row($wpdb->prepare("SELECT id, referral_code FROM $affiliate_table WHERE user_id = %d", $user_id)); if (!$affiliate) { return rest_ensure_response([]); } $collections = $wpdb->get_results($wpdb->prepare("SELECT * FROM $collections_table WHERE affiliate_id = %d ORDER BY created_at DESC", $affiliate->id), ARRAY_A); foreach ($collections as &$collection) { $collection['product_ids'] = $collection['product_ids'] ? json_decode($collection['product_ids'], true) : []; $collection['link'] = site_url("/collection/{$collection['slug']}"); } return rest_ensure_response($collections); } public function create_collection(WP_REST_Request $request) { global $wpdb; $user_id = get_current_user_id(); $affiliate_table = $wpdb->prefix . 'woonoow_affiliates'; $collections_table = $wpdb->prefix . 'woonoow_affiliate_collections'; $affiliate = $wpdb->get_row($wpdb->prepare("SELECT id FROM $affiliate_table WHERE user_id = %d", $user_id)); if (!$affiliate) { return new \WP_Error('not_found', 'Affiliate profile not found', ['status' => 404]); } $title = sanitize_text_field($request->get_param('title')); $description = sanitize_textarea_field($request->get_param('description')); $product_ids = $request->get_param('product_ids'); if (!is_array($product_ids)) $product_ids = []; $product_ids = array_map('intval', $product_ids); if (count($product_ids) > 20) { return new \WP_Error('too_many_products', 'A collection can have a maximum of 20 products.', ['status' => 400]); } $slug = sanitize_title($title); // Check unique slug for this affiliate $existing = $wpdb->get_var($wpdb->prepare("SELECT id FROM $collections_table WHERE affiliate_id = %d AND slug = %s", $affiliate->id, $slug)); if ($existing) { $slug .= '-' . wp_generate_password(4, false); } $data = [ 'affiliate_id' => $affiliate->id, 'title' => $title, 'slug' => $slug, 'description' => $description, 'product_ids' => json_encode($product_ids) ]; $wpdb->insert($collections_table, $data); $data['id'] = $wpdb->insert_id; $data['product_ids'] = $product_ids; return rest_ensure_response($data); } public function update_collection(WP_REST_Request $request) { global $wpdb; $user_id = get_current_user_id(); $id = (int) $request->get_param('id'); $affiliate_table = $wpdb->prefix . 'woonoow_affiliates'; $collections_table = $wpdb->prefix . 'woonoow_affiliate_collections'; $affiliate = $wpdb->get_row($wpdb->prepare("SELECT id FROM $affiliate_table WHERE user_id = %d", $user_id)); if (!$affiliate) return new \WP_Error('unauthorized', 'Unauthorized', ['status' => 401]); $collection = $wpdb->get_row($wpdb->prepare("SELECT id FROM $collections_table WHERE id = %d AND affiliate_id = %d", $id, $affiliate->id)); if (!$collection) return new \WP_Error('not_found', 'Collection not found', ['status' => 404]); $title = sanitize_text_field($request->get_param('title')); $description = sanitize_textarea_field($request->get_param('description')); $product_ids = $request->get_param('product_ids'); if (!is_array($product_ids)) $product_ids = []; $product_ids = array_map('intval', $product_ids); if (count($product_ids) > 20) { return new \WP_Error('too_many_products', 'A collection can have a maximum of 20 products.', ['status' => 400]); } $wpdb->update($collections_table, [ 'title' => $title, 'description' => $description, 'product_ids' => json_encode($product_ids) ], ['id' => $id]); return rest_ensure_response(['success' => true]); } public function delete_collection(WP_REST_Request $request) { global $wpdb; $user_id = get_current_user_id(); $id = (int) $request->get_param('id'); $affiliate_table = $wpdb->prefix . 'woonoow_affiliates'; $collections_table = $wpdb->prefix . 'woonoow_affiliate_collections'; $affiliate = $wpdb->get_row($wpdb->prepare("SELECT id FROM $affiliate_table WHERE user_id = %d", $user_id)); if (!$affiliate) return new \WP_Error('unauthorized', 'Unauthorized', ['status' => 401]); $wpdb->delete($collections_table, ['id' => $id, 'affiliate_id' => $affiliate->id]); return rest_ensure_response(['success' => true]); } }