\d+)', [ 'methods' => 'GET', 'callback' => [ $this, 'get_vendor' ], 'permission_callback' => '__return_true', 'args' => [ 'id' => [ 'validate_callback' => 'is_numeric' ] ], ] ); register_rest_route( $ns, '/vendor/(?P\d+)/products', [ 'methods' => 'GET', 'callback' => [ $this, 'get_vendor_products' ], 'permission_callback' => '__return_true', ] ); register_rest_route( $ns, '/vendor/(?P\d+)/orders', [ 'methods' => 'GET', 'callback' => [ $this, 'get_vendor_orders' ], 'permission_callback' => [ $this, 'check_auth' ], ] ); register_rest_route( $ns, '/vendor/(?P\d+)/stats', [ 'methods' => 'GET', 'callback' => [ $this, 'get_vendor_stats' ], 'permission_callback' => [ $this, 'check_auth' ], ] ); register_rest_route( $ns, '/products', [ 'methods' => 'POST', 'callback' => [ $this, 'create_product' ], 'permission_callback' => [ $this, 'check_vendor_auth' ], ] ); register_rest_route( $ns, '/products/(?P\d+)', [ 'methods' => 'PUT', 'callback' => [ $this, 'update_product' ], 'permission_callback' => [ $this, 'check_vendor_auth' ], ] ); register_rest_route( $ns, '/withdrawal', [ 'methods' => 'POST', 'callback' => [ $this, 'request_withdrawal' ], 'permission_callback' => [ $this, 'check_vendor_auth' ], ] ); register_rest_route( $ns, '/reviews', [ 'methods' => 'POST', 'callback' => [ $this, 'submit_review' ], 'permission_callback' => 'is_user_logged_in', ] ); register_rest_route( $ns, '/messages', [ 'methods' => 'GET', 'callback' => [ $this, 'get_messages' ], 'permission_callback' => 'is_user_logged_in', ] ); register_rest_route( $ns, '/messages', [ 'methods' => 'POST', 'callback' => [ $this, 'send_message' ], 'permission_callback' => 'is_user_logged_in', ] ); } public function check_auth( $request ) { $vendor_id = (int) $request['id']; return is_user_logged_in() && ( get_current_user_id() === $vendor_id || current_user_can('manage_options') ); } public function check_vendor_auth( $request ) { // For mutating requests, verify WP REST nonce if ( in_array( $request->get_method(), [ 'POST', 'PUT', 'PATCH', 'DELETE' ] ) ) { $nonce = $request->get_header( 'X-WP-Nonce' ); if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) { return new WP_Error( 'rest_forbidden', 'Nonce verification failed.', [ 'status' => 403 ] ); } } return is_user_logged_in() && VV_Helpers::is_vendor(); } public function get_vendor( $request ) { $uid = (int) $request['id']; $user = get_user_by( 'id', $uid ); if ( ! $user || ! VV_Helpers::is_vendor($uid) ) return new WP_Error('not_found','Vendor not found',['status'=>404]); $stats = VV_Helpers::get_vendor_stats($uid); return rest_ensure_response( [ 'id' => $uid, 'name' => $user->display_name, 'store_name' => get_user_meta($uid,'vv_store_name',true) ?: $user->display_name, 'bio' => get_user_meta($uid,'vv_store_bio',true), 'avg_rating' => $stats['avg_rating'], 'review_count' => $stats['review_count'], 'product_count'=> $stats['total_products'], 'follower_count'=> $stats['follower_count'], 'store_url' => home_url( '/' . get_option('vv_vendor_store_slug','vendor') . '/' . $user->user_login ), ] ); } public function get_vendor_products( $request ) { $uid = (int) $request['id']; $products = get_posts( ['post_type'=>'product','post_status'=>'publish','post_author'=>$uid,'numberposts'=>50] ); $data = []; foreach ($products as $p) { $product = wc_get_product($p->ID); $data[] = [ 'id' => $p->ID, 'title' => $p->post_title, 'price' => $product ? $product->get_price() : null, 'url' => get_permalink($p->ID), 'image' => get_the_post_thumbnail_url($p->ID,'medium'), ]; } return rest_ensure_response($data); } public function get_vendor_orders( $request ) { global $wpdb; $uid = (int) $request['id']; $rows = $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT order_id FROM {$wpdb->prefix}vv_system_orders WHERE product_owner_user_id=%d LIMIT 50", $uid ) ); $data = []; foreach ($rows as $r) { $order = wc_get_order($r->order_id); if (!$order) continue; $data[] = [ 'id'=>$r->order_id,'status'=>$order->get_status(),'total'=>$order->get_total(),'date'=>$order->get_date_created()->date('c') ]; } return rest_ensure_response($data); } public function get_vendor_stats( $request ) { $uid = (int) $request['id']; return rest_ensure_response( VV_Helpers::get_vendor_stats($uid) ); } public function create_product( $request ) { $body = $request->get_json_params(); $user_id = get_current_user_id(); if ( ! $body['title'] || ! $body['price'] ) return new WP_Error('invalid','Title and price required',['status'=>400]); $product = new WC_Product_Simple(); $product->set_name( sanitize_text_field($body['title']) ); $product->set_description( sanitize_textarea_field($body['description'] ?? '') ); $product->set_regular_price( (float)$body['price'] ); $product->set_status('pending'); $pid = $product->save(); wp_update_post(['ID'=>$pid,'post_author'=>$user_id]); return rest_ensure_response(['id'=>$pid,'status'=>'pending']); } public function update_product( $request ) { $pid = (int) $request['id']; $body = $request->get_json_params(); if ( get_post_field('post_author',$pid) != get_current_user_id() ) return new WP_Error('forbidden','Not your product',['status'=>403]); $product = wc_get_product($pid); if (!$product) return new WP_Error('not_found','Product not found',['status'=>404]); if (isset($body['title'])) $product->set_name(sanitize_text_field($body['title'])); if (isset($body['price'])) $product->set_regular_price((float)$body['price']); if (isset($body['description'])) $product->set_description(sanitize_textarea_field($body['description'])); $product->save(); return rest_ensure_response(['id'=>$pid,'updated'=>true]); } public function request_withdrawal( $request ) { $body = $request->get_json_params(); $user_id = get_current_user_id(); $amount = (float)($body['amount'] ?? 0); $balance = VV_Helpers::get_vendor_balance($user_id); $min = (float)get_option('vv_min_withdrawal',20); if ($amount < $min) return new WP_Error('too_low','Amount below minimum',['status'=>400]); if ($amount > $balance) return new WP_Error('insufficient','Insufficient balance',['status'=>400]); global $wpdb; VV_Helpers::record_transaction($user_id,$amount,'debit','Withdrawal request (API)'); $wpdb->insert("{$wpdb->prefix}vv_withdrawals",['vendor_id'=>$user_id,'amount'=>$amount,'method'=>sanitize_text_field($body['method']??'bank'),'status'=>'pending']); return rest_ensure_response(['success'=>true,'id'=>$wpdb->insert_id]); } public function submit_review( $request ) { $body = $request->get_json_params(); $vid = (int)($body['vendor_id']??0); $rating = min(5,max(1,(int)($body['rating']??5))); $review = sanitize_textarea_field($body['review']??''); $oid = (int)($body['order_id']??0); if (!$vid) return new WP_Error('invalid','vendor_id required',['status'=>400]); global $wpdb; $wpdb->insert("{$wpdb->prefix}vv_vendor_reviews",['vendor_id'=>$vid,'customer_id'=>get_current_user_id(),'order_id'=>$oid,'rating'=>$rating,'review'=>$review,'is_verified'=>$oid?1:0]); VV_Helpers::flush_vendor_cache($vid); return rest_ensure_response(['success'=>true]); } public function get_messages( $request ) { $uid = get_current_user_id(); $conv = (int)($request->get_param('conv')??0); global $wpdb; if ($conv) { $rows = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->prefix}vv_messages WHERE conversation_id=%d ORDER BY created_at ASC LIMIT 100",$conv)); } else { $rows = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->prefix}vv_messages WHERE sender_id=%d OR receiver_id=%d ORDER BY created_at DESC LIMIT 50",$uid,$uid)); } return rest_ensure_response($rows); } public function send_message( $request ) { $body = $request->get_json_params(); $receiver = (int)($body['receiver_id']??0); $message = sanitize_textarea_field($body['message']??''); $sender = get_current_user_id(); $conv = (int)($body['conversation_id']??0); if (!$receiver||!$message) return new WP_Error('invalid','receiver_id and message required',['status'=>400]); if (!$conv) { $a=min($sender,$receiver);$b=max($sender,$receiver);$conv=crc32($a.'-'.$b); } global $wpdb; $wpdb->insert("{$wpdb->prefix}vv_messages",['conversation_id'=>$conv,'sender_id'=>$sender,'receiver_id'=>$receiver,'message'=>$message]); do_action('vv_new_message',$receiver,$sender,$message); return rest_ensure_response(['success'=>true,'id'=>$wpdb->insert_id]); } }