AIventory Connector requires WooCommerce to be installed and active.

'; }); return; } class AIventory_Connector { private $api_key_option = 'aiventory_api_key'; private $version = '1.0.0'; public function __construct() { register_activation_hook(__FILE__, array($this, 'activate')); add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); add_action('rest_api_init', array($this, 'register_routes')); } public function enqueue_admin_assets($hook) { // Only load on our plugin page if ($hook !== 'woocommerce_page_aiventory-connector') { return; } // Register and enqueue inline script wp_register_script('aiventory-admin', false, array(), $this->version, true); wp_enqueue_script('aiventory-admin'); wp_add_inline_script('aiventory-admin', ' function copyToClipboard(text) { navigator.clipboard.writeText(text).then(function() { alert("Copied to clipboard!"); }); } '); // Register and enqueue inline styles wp_register_style('aiventory-admin', false, array(), $this->version); wp_enqueue_style('aiventory-admin'); wp_add_inline_style('aiventory-admin', ' .card { background: white; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .card h2 { margin-top: 0; } '); } public function activate() { if (!get_option($this->api_key_option)) { $api_key = 'aiv_' . bin2hex(random_bytes(32)); update_option($this->api_key_option, $api_key); } } public function add_admin_menu() { add_submenu_page( 'woocommerce', 'AIventory Connector', 'AIventory', 'manage_woocommerce', 'aiventory-connector', array($this, 'admin_page') ); } public function admin_page() { $api_key = get_option($this->api_key_option); $store_url = get_site_url(); if (isset($_POST['regenerate_key']) && check_admin_referer('aiventory_regenerate')) { $api_key = 'aiv_' . bin2hex(random_bytes(32)); update_option($this->api_key_option, $api_key); echo '

API Key regenerated successfully!

'; } include(plugin_dir_path(__FILE__) . 'admin-page.php'); } public function register_routes() { $endpoints = array( array('path' => '/test', 'callback' => 'test_connection'), array('path' => '/store', 'callback' => 'get_store_info'), array('path' => '/products', 'callback' => 'get_products'), array('path' => '/variants', 'callback' => 'get_variants'), array('path' => '/collections', 'callback' => 'get_collections'), array('path' => '/customers', 'callback' => 'get_customers'), array('path' => '/customer-addresses', 'callback' => 'get_customer_addresses'), array('path' => '/orders', 'callback' => 'get_orders'), array('path' => '/order-line-items', 'callback' => 'get_order_line_items'), array('path' => '/fulfillments', 'callback' => 'get_fulfillments'), array('path' => '/refunds', 'callback' => 'get_refunds'), array('path' => '/refund-line-items', 'callback' => 'get_refund_line_items'), array('path' => '/discounts', 'callback' => 'get_discounts'), array('path' => '/inventory-ledger', 'callback' => 'get_inventory_ledger') ); foreach ($endpoints as $endpoint) { register_rest_route('aiventory/v1', $endpoint['path'], array( 'methods' => 'GET', 'callback' => array($this, $endpoint['callback']), 'permission_callback' => array($this, 'verify_api_key') )); } } public function verify_api_key($request) { $api_key = $request->get_header('X-API-Key') ?: $request->get_param('api_key'); $stored_key = get_option($this->api_key_option); return !empty($api_key) && hash_equals($stored_key, $api_key); } public function test_connection($request) { return rest_ensure_response(array( 'success' => true, 'message' => 'AIventory Connector is working!', 'store_name' => get_bloginfo('name'), 'woocommerce_version' => WC()->version, 'plugin_version' => $this->version )); } public function get_store_info($request) { return rest_ensure_response(array( 'success' => true, 'data' => array( 'name' => get_bloginfo('name'), 'url' => get_site_url(), 'email' => get_option('admin_email'), 'currency' => get_woocommerce_currency(), 'timezone' => wp_timezone_string(), 'woocommerce_version' => WC()->version ) )); } public function get_products($request) { $page = intval($request->get_param('page') ?: 1); $per_page = intval($request->get_param('per_page') ?: 50); $products = wc_get_products(array( 'status' => 'publish', 'limit' => $per_page, 'page' => $page, 'orderby' => 'date', 'order' => 'DESC' )); $data = array(); foreach ($products as $product) { $data[] = array( 'id' => $product->get_id(), 'external_product_id' => (string) $product->get_id(), 'title' => $product->get_name(), 'description' => $product->get_description(), 'handle' => $product->get_slug(), 'product_type' => $product->get_type(), 'vendor' => get_post_meta($product->get_id(), '_vendor', true) ?: '', 'tags' => array_map(function($tag) { return $tag->name; }, wp_get_post_terms($product->get_id(), 'product_tag')), 'status' => $product->get_status(), 'published' => $product->get_status() === 'publish', 'image_url' => wp_get_attachment_url($product->get_image_id()) ?: '', 'created_at' => $product->get_date_created() ? $product->get_date_created()->date('c') : null, 'updated_at' => $product->get_date_modified() ? $product->get_date_modified()->date('c') : null, 'platform' => 'woocommerce' ); } return $this->paginated_response($data, $page, $per_page); } public function get_variants($request) { $page = intval($request->get_param('page') ?: 1); $per_page = intval($request->get_param('per_page') ?: 50); $product_id = $request->get_param('product_id'); $args = array( 'type' => array('simple', 'variation'), 'limit' => $per_page, 'page' => $page ); if ($product_id) { $args['parent'] = $product_id; } $products = wc_get_products($args); $data = array(); foreach ($products as $product) { $parent_id = $product->get_parent_id() ?: $product->get_id(); $parent = $product->get_parent_id() ? wc_get_product($product->get_parent_id()) : $product; $data[] = array( 'id' => $product->get_id(), 'product_id' => $parent_id, 'external_variant_id' => (string) $product->get_id(), 'external_product_id' => (string) $parent_id, 'sku' => $product->get_sku() ?: 'product-' . $product->get_id(), 'title' => $product->get_name(), 'price' => (float) $product->get_price(), 'compare_at_price' => (float) $product->get_regular_price(), 'cost' => (float) $product->get_meta('_cost'), 'inventory_quantity' => $product->get_stock_quantity() ?: 0, 'barcode' => $product->get_meta('_barcode') ?: '', 'product_title' => $parent ? $parent->get_name() : '', 'product_type' => $product->get_type(), 'product_status' => $product->get_status(), 'image_url' => wp_get_attachment_url($product->get_image_id()) ?: '', 'created_at' => $product->get_date_created() ? $product->get_date_created()->date('c') : null, 'platform' => 'woocommerce' ); } return $this->paginated_response($data, $page, $per_page); } public function get_collections($request) { $page = intval($request->get_param('page') ?: 1); $per_page = intval($request->get_param('per_page') ?: 50); $categories = get_terms(array( 'taxonomy' => 'product_cat', 'number' => $per_page, 'offset' => ($page - 1) * $per_page, 'hide_empty' => false )); $data = array(); foreach ($categories as $category) { $data[] = array( 'id' => $category->term_id, 'external_collection_id' => (string) $category->term_id, 'title' => $category->name, 'handle' => $category->slug, 'description' => $category->description, 'published' => true, 'platform' => 'woocommerce' ); } return $this->paginated_response($data, $page, $per_page); } public function get_customers($request) { $page = intval($request->get_param('page') ?: 1); $per_page = intval($request->get_param('per_page') ?: 50); $customers = get_users(array( 'role__in' => array('customer', 'subscriber'), 'number' => $per_page, 'paged' => $page )); $data = array(); foreach ($customers as $customer) { $wc_customer = new WC_Customer($customer->ID); $first_order = wc_get_orders(array( 'customer_id' => $customer->ID, 'limit' => 1, 'orderby' => 'date', 'order' => 'ASC' )); $data[] = array( 'id' => $customer->ID, 'external_customer_id' => (string) $customer->ID, 'customer_name' => trim($wc_customer->get_first_name() . ' ' . $wc_customer->get_last_name()) ?: $customer->user_login, 'email' => $customer->user_email, 'phone' => $wc_customer->get_billing_phone(), 'first_name' => $wc_customer->get_first_name(), 'last_name' => $wc_customer->get_last_name(), 'orders_count' => wc_get_customer_order_count($customer->ID), 'total_spent' => (float) wc_get_customer_total_spent($customer->ID), 'first_order_date' => !empty($first_order) ? $first_order[0]->get_date_created()->date('Y-m-d') : null, 'accepts_marketing' => (bool) $wc_customer->get_meta('accepts_marketing'), 'tax_exempt' => $wc_customer->is_vat_exempt(), 'created_at' => $customer->user_registered, 'platform' => 'woocommerce' ); } return $this->paginated_response($data, $page, $per_page); } public function get_customer_addresses($request) { $page = intval($request->get_param('page') ?: 1); $per_page = intval($request->get_param('per_page') ?: 50); $customers = get_users(array( 'role__in' => array('customer', 'subscriber'), 'number' => $per_page, 'paged' => $page )); $data = array(); foreach ($customers as $customer) { $wc_customer = new WC_Customer($customer->ID); if ($wc_customer->get_billing_address_1()) { $data[] = array( 'customer_id' => $customer->ID, 'address_type' => 'billing', 'first_name' => $wc_customer->get_billing_first_name(), 'last_name' => $wc_customer->get_billing_last_name(), 'company' => $wc_customer->get_billing_company(), 'address1' => $wc_customer->get_billing_address_1(), 'address2' => $wc_customer->get_billing_address_2(), 'city' => $wc_customer->get_billing_city(), 'province_code' => $wc_customer->get_billing_state(), 'country_code' => $wc_customer->get_billing_country(), 'zip' => $wc_customer->get_billing_postcode(), 'phone' => $wc_customer->get_billing_phone(), 'is_default' => true, 'platform' => 'woocommerce' ); } if ($wc_customer->get_shipping_address_1()) { $data[] = array( 'customer_id' => $customer->ID, 'address_type' => 'shipping', 'first_name' => $wc_customer->get_shipping_first_name(), 'last_name' => $wc_customer->get_shipping_last_name(), 'company' => $wc_customer->get_shipping_company(), 'address1' => $wc_customer->get_shipping_address_1(), 'address2' => $wc_customer->get_shipping_address_2(), 'city' => $wc_customer->get_shipping_city(), 'province_code' => $wc_customer->get_shipping_state(), 'country_code' => $wc_customer->get_shipping_country(), 'zip' => $wc_customer->get_shipping_postcode(), 'phone' => $wc_customer->get_billing_phone(), 'is_default' => false, 'platform' => 'woocommerce' ); } } return $this->paginated_response($data, $page, $per_page); } public function get_orders($request) { $page = intval($request->get_param('page') ?: 1); $per_page = intval($request->get_param('per_page') ?: 50); $status = $request->get_param('status') ?: 'any'; $args = array( 'limit' => $per_page, 'page' => $page, 'orderby' => 'date', 'order' => 'DESC' ); if ($status !== 'any') { $args['status'] = $status; } $orders = wc_get_orders($args); $data = array(); foreach ($orders as $order) { $data[] = array( 'id' => $order->get_id(), 'external_order_id' => (string) $order->get_id(), 'order_number' => (string) $order->get_order_number(), 'external_customer_id' => $order->get_customer_id() ? (string) $order->get_customer_id() : null, 'financial_status' => $this->map_financial_status($order->get_status()), 'fulfillment_status' => $this->map_fulfillment_status($order->get_status()), 'currency' => $order->get_currency(), 'subtotal' => (float) $order->get_subtotal(), 'total_tax' => (float) $order->get_total_tax(), 'total_shipping' => (float) $order->get_shipping_total(), 'total_discounts' => (float) $order->get_total_discount(), 'total_amount' => (float) $order->get_total(), 'payment_method' => $order->get_payment_method_title(), 'notes' => $order->get_customer_note(), 'source_name' => $order->get_created_via(), 'processed_at' => $order->get_date_paid() ? $order->get_date_paid()->date('c') : null, 'created_at' => $order->get_date_created()->date('c'), 'updated_at' => $order->get_date_modified()->date('c'), 'platform' => 'woocommerce' ); } return $this->paginated_response($data, $page, $per_page); } public function get_order_line_items($request) { $page = intval($request->get_param('page') ?: 1); $per_page = intval($request->get_param('per_page') ?: 50); $order_id = $request->get_param('order_id'); if ($order_id) { $orders = array(wc_get_order($order_id)); } else { $orders = wc_get_orders(array('limit' => $per_page, 'page' => $page)); } $data = array(); foreach ($orders as $order) { if (!$order) continue; foreach ($order->get_items() as $item_id => $item) { $product = $item->get_product(); $data[] = array( 'id' => $item_id, 'order_id' => $order->get_id(), 'external_line_item_id' => (string) $item_id, 'external_order_id' => (string) $order->get_id(), 'product_id' => $item->get_product_id(), 'variant_id' => $item->get_variation_id() ?: $item->get_product_id(), 'external_product_id' => (string) $item->get_product_id(), 'external_variant_id' => $item->get_variation_id() ? (string) $item->get_variation_id() : (string) $item->get_product_id(), 'product_name' => $item->get_name(), 'sku' => $product ? $product->get_sku() : '', 'quantity' => $item->get_quantity(), 'price' => (float) $order->get_item_total($item, false), 'total_discount' => (float) ($item->get_subtotal() - $item->get_total()), 'tax_amount' => (float) $item->get_total_tax(), 'fulfillment_status' => $order->get_status() === 'completed' ? 'fulfilled' : 'unfulfilled', 'fulfillable_quantity' => $order->get_status() === 'completed' ? 0 : $item->get_quantity(), 'requires_shipping' => $product ? !$product->is_virtual() : true, 'cost_at_time' => $product ? (float) $product->get_meta('_cost') : null, 'currency' => $order->get_currency(), 'platform' => 'woocommerce' ); } } return $this->paginated_response($data, $page, $per_page); } public function get_fulfillments($request) { $page = intval($request->get_param('page') ?: 1); $per_page = intval($request->get_param('per_page') ?: 50); $orders = wc_get_orders(array( 'limit' => $per_page, 'page' => $page, 'status' => array('completed', 'processing') )); $data = array(); foreach ($orders as $order) { if ($order->get_status() === 'completed') { $data[] = array( 'id' => 'fulfillment-' . $order->get_id(), 'order_id' => $order->get_id(), 'external_fulfillment_id' => 'fulfillment-' . $order->get_id(), 'external_order_id' => (string) $order->get_id(), 'status' => 'success', 'shipped_at' => $order->get_date_completed() ? $order->get_date_completed()->date('c') : null, 'delivered_at' => $order->get_date_completed() ? $order->get_date_completed()->date('c') : null, 'platform' => 'woocommerce' ); } } return $this->paginated_response($data, $page, $per_page); } public function get_refunds($request) { $page = intval($request->get_param('page') ?: 1); $per_page = intval($request->get_param('per_page') ?: 50); $refunds = wc_get_orders(array( 'limit' => $per_page, 'page' => $page, 'type' => 'shop_order_refund' )); $data = array(); foreach ($refunds as $refund) { $data[] = array( 'id' => $refund->get_id(), 'order_id' => $refund->get_parent_id(), 'external_refund_id' => (string) $refund->get_id(), 'external_order_id' => (string) $refund->get_parent_id(), 'refund_number' => 'refund-' . $refund->get_id(), 'status' => 'completed', 'reason' => $refund->get_reason(), 'note' => $refund->get_reason(), 'total_amount' => abs((float) $refund->get_amount()), 'created_at' => $refund->get_date_created()->date('c'), 'platform' => 'woocommerce' ); } return $this->paginated_response($data, $page, $per_page); } public function get_refund_line_items($request) { $page = intval($request->get_param('page') ?: 1); $per_page = intval($request->get_param('per_page') ?: 50); $refunds = wc_get_orders(array( 'limit' => $per_page, 'page' => $page, 'type' => 'shop_order_refund' )); $data = array(); foreach ($refunds as $refund) { foreach ($refund->get_items() as $item_id => $item) { $data[] = array( 'id' => $item_id, 'refund_id' => $refund->get_id(), 'external_refund_id' => (string) $refund->get_id(), 'quantity' => abs($item->get_quantity()), 'amount' => abs((float) $item->get_total()), 'restock' => true, 'platform' => 'woocommerce' ); } } return $this->paginated_response($data, $page, $per_page); } public function get_discounts($request) { $page = intval($request->get_param('page') ?: 1); $per_page = intval($request->get_param('per_page') ?: 50); $coupons = get_posts(array( 'posts_per_page' => $per_page, 'paged' => $page, 'post_type' => 'shop_coupon', 'post_status' => 'publish' )); $data = array(); foreach ($coupons as $coupon_post) { $coupon = new WC_Coupon($coupon_post->ID); $data[] = array( 'id' => $coupon->get_id(), 'external_discount_id' => (string) $coupon->get_id(), 'code' => $coupon->get_code(), 'type' => $coupon->get_discount_type(), 'value' => (float) $coupon->get_amount(), 'minimum_purchase' => (float) $coupon->get_minimum_amount(), 'usage_limit' => $coupon->get_usage_limit() ?: null, 'usage_count' => $coupon->get_usage_count(), 'starts_at' => $coupon->get_date_created() ? $coupon->get_date_created()->date('c') : null, 'ends_at' => $coupon->get_date_expires() ? $coupon->get_date_expires()->date('c') : null, 'is_active' => !$coupon->get_date_expires() || $coupon->get_date_expires() > new DateTime(), 'platform' => 'woocommerce' ); } return $this->paginated_response($data, $page, $per_page); } public function get_inventory_ledger($request) { $page = intval($request->get_param('page') ?: 1); $per_page = intval($request->get_param('per_page') ?: 50); global $wpdb; $offset = ($page - 1) * $per_page; $query = " SELECT oi.order_item_id as id, oi.order_id as related_id, p.post_date as created_at, oim1.meta_value as product_id, oim2.meta_value as quantity FROM {$wpdb->prefix}woocommerce_order_items oi LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta oim1 ON oi.order_item_id = oim1.order_item_id AND oim1.meta_key = '_product_id' LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta oim2 ON oi.order_item_id = oim2.order_item_id AND oim2.meta_key = '_qty' LEFT JOIN {$wpdb->posts} p ON oi.order_id = p.ID WHERE oi.order_item_type = 'line_item' ORDER BY p.post_date DESC LIMIT %d OFFSET %d "; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is properly prepared with placeholders // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query required for real-time order sync; caching would return stale data $results = $wpdb->get_results($wpdb->prepare($query, $per_page, $offset)); $data = array(); foreach ($results as $row) { $product = wc_get_product($row->product_id); if (!$product) continue; $data[] = array( 'id' => $row->id, 'variant_id' => $row->product_id, 'change_type' => 'sale', 'quantity_change' => -abs((int) $row->quantity), 'new_quantity' => $product->get_stock_quantity(), 'related_id' => $row->related_id, 'notes' => 'Order #' . $row->related_id, 'created_at' => $row->created_at, 'platform' => 'woocommerce' ); } return $this->paginated_response($data, $page, $per_page); } private function map_financial_status($wc_status) { $map = array( 'pending' => 'pending', 'processing' => 'paid', 'on-hold' => 'pending', 'completed' => 'paid', 'cancelled' => 'voided', 'refunded' => 'refunded', 'failed' => 'voided' ); return isset($map[$wc_status]) ? $map[$wc_status] : 'pending'; } private function map_fulfillment_status($wc_status) { $map = array( 'pending' => 'unfulfilled', 'processing' => 'unfulfilled', 'on-hold' => 'unfulfilled', 'completed' => 'fulfilled', 'cancelled' => 'unfulfilled', 'refunded' => 'unfulfilled', 'failed' => 'unfulfilled' ); return isset($map[$wc_status]) ? $map[$wc_status] : 'unfulfilled'; } private function paginated_response($data, $page, $per_page) { return rest_ensure_response(array( 'success' => true, 'data' => $data, 'pagination' => array( 'page' => $page, 'per_page' => $per_page, 'total' => count($data) ) )); } } new AIventory_Connector();