logger = $logger; $this->price_updater = $price_updater; // Add admin menu. add_action( 'admin_menu', array( $this, 'add_admin_menu' ) ); // Register settings. add_action( 'admin_init', array( $this, 'register_settings' ) ); // Handle OAuth callback. add_action( 'admin_init', array( $this, 'handle_oauth_callback' ) ); // Handle AJAX requests. add_action( 'wp_ajax_informatiq_sp_manual_sync', array( $this, 'handle_manual_sync' ) ); add_action( 'wp_ajax_informatiq_sp_test_connection', array( $this, 'handle_test_connection' ) ); add_action( 'wp_ajax_informatiq_sp_revoke_auth', array( $this, 'handle_revoke_auth' ) ); add_action( 'wp_ajax_informatiq_sp_compare_products', array( $this, 'handle_compare_products' ) ); add_action( 'wp_ajax_informatiq_sp_update_price', array( $this, 'handle_update_price' ) ); add_action( 'wp_ajax_informatiq_sp_bulk_update_prices', array( $this, 'handle_bulk_update_prices' ) ); // Enqueue admin assets. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) ); } /** * Add admin menu under WooCommerce. */ public function add_admin_menu() { add_submenu_page( 'woocommerce', __( 'Smart Pricing', 'informatiq-smart-pricing' ), __( 'Smart Pricing', 'informatiq-smart-pricing' ), 'manage_woocommerce', 'informatiq-smart-pricing', array( $this, 'render_admin_page' ) ); } /** * Register plugin settings. */ public function register_settings() { // Register settings. register_setting( 'informatiq_sp_settings', 'informatiq_sp_merchant_id' ); register_setting( 'informatiq_sp_settings', 'informatiq_sp_client_id' ); register_setting( 'informatiq_sp_settings', 'informatiq_sp_client_secret' ); register_setting( 'informatiq_sp_settings', 'informatiq_sp_minimum_margin' ); register_setting( 'informatiq_sp_settings', 'informatiq_sp_auto_update_enabled' ); register_setting( 'informatiq_sp_settings', 'informatiq_sp_update_frequency' ); // Add settings sections. add_settings_section( 'informatiq_sp_google_settings', __( 'Google Merchant Center Settings', 'informatiq-smart-pricing' ), array( $this, 'render_google_settings_section' ), 'informatiq_sp_settings' ); add_settings_section( 'informatiq_sp_pricing_settings', __( 'Pricing Settings', 'informatiq-smart-pricing' ), array( $this, 'render_pricing_settings_section' ), 'informatiq_sp_settings' ); add_settings_section( 'informatiq_sp_automation_settings', __( 'Automation Settings', 'informatiq-smart-pricing' ), array( $this, 'render_automation_settings_section' ), 'informatiq_sp_settings' ); // Add settings fields. add_settings_field( 'informatiq_sp_merchant_id', __( 'Google Merchant ID', 'informatiq-smart-pricing' ), array( $this, 'render_merchant_id_field' ), 'informatiq_sp_settings', 'informatiq_sp_google_settings' ); add_settings_field( 'informatiq_sp_oauth_credentials', __( 'OAuth 2.0 Credentials', 'informatiq-smart-pricing' ), array( $this, 'render_oauth_credentials_field' ), 'informatiq_sp_settings', 'informatiq_sp_google_settings' ); add_settings_field( 'informatiq_sp_authorization', __( 'Google Authorization', 'informatiq-smart-pricing' ), array( $this, 'render_authorization_field' ), 'informatiq_sp_settings', 'informatiq_sp_google_settings' ); add_settings_field( 'informatiq_sp_minimum_margin', __( 'Minimum Margin (%)', 'informatiq-smart-pricing' ), array( $this, 'render_minimum_margin_field' ), 'informatiq_sp_settings', 'informatiq_sp_pricing_settings' ); add_settings_field( 'informatiq_sp_auto_update_enabled', __( 'Enable Automatic Updates', 'informatiq-smart-pricing' ), array( $this, 'render_auto_update_field' ), 'informatiq_sp_settings', 'informatiq_sp_automation_settings' ); add_settings_field( 'informatiq_sp_update_frequency', __( 'Update Frequency', 'informatiq-smart-pricing' ), array( $this, 'render_update_frequency_field' ), 'informatiq_sp_settings', 'informatiq_sp_automation_settings' ); } /** * Handle OAuth callback from Google. */ public function handle_oauth_callback() { // Check if this is an OAuth callback. if ( ! isset( $_GET['informatiq_sp_oauth'] ) || $_GET['informatiq_sp_oauth'] !== 'callback' ) { return; } // Verify state parameter. $state = isset( $_GET['state'] ) ? sanitize_text_field( $_GET['state'] ) : ''; $saved_state = get_transient( 'informatiq_sp_oauth_state' ); if ( empty( $state ) || $state !== $saved_state ) { add_settings_error( 'informatiq_sp_messages', 'informatiq_sp_oauth_error', __( 'OAuth state mismatch. Please try again.', 'informatiq-smart-pricing' ), 'error' ); return; } delete_transient( 'informatiq_sp_oauth_state' ); // Check for errors. if ( isset( $_GET['error'] ) ) { $error = sanitize_text_field( $_GET['error'] ); $error_desc = isset( $_GET['error_description'] ) ? sanitize_text_field( $_GET['error_description'] ) : $error; add_settings_error( 'informatiq_sp_messages', 'informatiq_sp_oauth_error', sprintf( __( 'Authorization failed: %s', 'informatiq-smart-pricing' ), $error_desc ), 'error' ); return; } // Get authorization code. $code = isset( $_GET['code'] ) ? sanitize_text_field( $_GET['code'] ) : ''; if ( empty( $code ) ) { add_settings_error( 'informatiq_sp_messages', 'informatiq_sp_oauth_error', __( 'No authorization code received.', 'informatiq-smart-pricing' ), 'error' ); return; } // Exchange code for tokens. $client_id = get_option( 'informatiq_sp_client_id' ); $client_secret = get_option( 'informatiq_sp_client_secret' ); $redirect_uri = $this->get_oauth_redirect_uri(); try { $tokens = Informatiq_SP_Google_API::exchange_code_for_tokens( $code, $client_id, $client_secret, $redirect_uri ); // Save refresh token. update_option( 'informatiq_sp_refresh_token', $tokens['refresh_token'] ); $this->logger->info( 'Google OAuth authorization successful' ); // Redirect to settings page with success message. wp_redirect( admin_url( 'admin.php?page=informatiq-smart-pricing&oauth=success' ) ); exit; } catch ( Exception $e ) { $this->logger->error( 'OAuth token exchange failed: ' . $e->getMessage() ); add_settings_error( 'informatiq_sp_messages', 'informatiq_sp_oauth_error', sprintf( __( 'Token exchange failed: %s', 'informatiq-smart-pricing' ), $e->getMessage() ), 'error' ); } } /** * Get OAuth redirect URI. * * @return string Redirect URI. */ private function get_oauth_redirect_uri() { return admin_url( 'admin.php?page=informatiq-smart-pricing&informatiq_sp_oauth=callback' ); } /** * Check if plugin is authorized. * * @return bool True if authorized. */ private function is_authorized() { return ! empty( get_option( 'informatiq_sp_refresh_token' ) ); } /** * Render admin page. */ public function render_admin_page() { if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } // Check for OAuth success message. if ( isset( $_GET['oauth'] ) && $_GET['oauth'] === 'success' ) { add_settings_error( 'informatiq_sp_messages', 'informatiq_sp_message', __( 'Google authorization successful!', 'informatiq-smart-pricing' ), 'success' ); } // Check if form was submitted. if ( isset( $_GET['settings-updated'] ) ) { // Update cron schedule if frequency changed. $frequency = get_option( 'informatiq_sp_update_frequency', 'daily' ); Informatiq_SP_Scheduler::reschedule( $frequency ); add_settings_error( 'informatiq_sp_messages', 'informatiq_sp_message', __( 'Settings saved successfully.', 'informatiq-smart-pricing' ), 'success' ); } ?>


is_authorized() ) : ?>


render_schedule_info(); ?>
render_product_comparison(); ?>
render_logs_section(); ?>
render_sidebar(); ?>
' . esc_html__( 'Configure your Google Merchant Center API credentials using OAuth 2.0.', 'informatiq-smart-pricing' ) . '

'; } /** * Render pricing settings section description. */ public function render_pricing_settings_section() { echo '

' . esc_html__( 'Configure pricing rules and safeguards.', 'informatiq-smart-pricing' ) . '

'; } /** * Render automation settings section description. */ public function render_automation_settings_section() { echo '

' . esc_html__( 'Configure automated price updates.', 'informatiq-smart-pricing' ) . '

'; } /** * Render merchant ID field. */ public function render_merchant_id_field() { $value = get_option( 'informatiq_sp_merchant_id' ); ?>



' . esc_html( $this->get_oauth_redirect_uri() ) . '' ); ?>

is_authorized(); $has_credentials = ! empty( $client_id ) && ! empty( $client_secret ); if ( $is_authorized ) { ?>

get_oauth_redirect_uri(), $state ); ?>

%


is_authorized() ) : ?>

logger->get_todays_logs(); ?>

created_at ) ) ); ?> product_name ); ?> old_price ) ); ?> new_price ) ); ?> competitor_price ) ); ?>

informatiq.services', 'informatiq-smart-pricing' ) ), 'https://informatiq.services' ); ?>

__( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) ); } if ( ! $this->is_authorized() ) { wp_send_json_error( array( 'message' => __( 'Please authorize with Google first.', 'informatiq-smart-pricing' ) ) ); } try { $scheduler = new Informatiq_SP_Scheduler( $this->price_updater ); $results = $scheduler->trigger_manual_update(); wp_send_json_success( array( 'message' => sprintf( /* translators: %1$d: updated count, %2$d: processed count */ __( 'Sync completed! Updated %1$d of %2$d products.', 'informatiq-smart-pricing' ), $results['updated'], $results['processed'] ), 'results' => $results, ) ); } catch ( Exception $e ) { wp_send_json_error( array( 'message' => $e->getMessage() ) ); } } /** * Handle test connection AJAX request. */ public function handle_test_connection() { check_ajax_referer( 'informatiq_sp_admin', 'nonce' ); if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) ); } $merchant_id = get_option( 'informatiq_sp_merchant_id' ); $client_id = get_option( 'informatiq_sp_client_id' ); $client_secret = get_option( 'informatiq_sp_client_secret' ); $refresh_token = get_option( 'informatiq_sp_refresh_token' ); if ( empty( $merchant_id ) ) { wp_send_json_error( array( 'message' => __( 'Please enter your Merchant ID.', 'informatiq-smart-pricing' ) ) ); } if ( empty( $refresh_token ) ) { wp_send_json_error( array( 'message' => __( 'Please authorize with Google first.', 'informatiq-smart-pricing' ) ) ); } try { $google_api = new Informatiq_SP_Google_API( $merchant_id, $client_id, $client_secret, $refresh_token, $this->logger ); $success = $google_api->test_connection(); if ( $success ) { wp_send_json_success( array( 'message' => __( 'Connection successful!', 'informatiq-smart-pricing' ) ) ); } else { wp_send_json_error( array( 'message' => __( 'Connection failed. Check your credentials.', 'informatiq-smart-pricing' ) ) ); } } catch ( Exception $e ) { wp_send_json_error( array( 'message' => $e->getMessage() ) ); } } /** * Handle revoke authorization AJAX request. */ public function handle_revoke_auth() { check_ajax_referer( 'informatiq_sp_admin', 'nonce' ); if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) ); } delete_option( 'informatiq_sp_refresh_token' ); $this->logger->info( 'Google OAuth authorization revoked' ); wp_send_json_success( array( 'message' => __( 'Authorization revoked.', 'informatiq-smart-pricing' ) ) ); } /** * Handle product comparison AJAX request. */ public function handle_compare_products() { check_ajax_referer( 'informatiq_sp_admin', 'nonce' ); if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) ); } $merchant_id = get_option( 'informatiq_sp_merchant_id' ); $client_id = get_option( 'informatiq_sp_client_id' ); $client_secret = get_option( 'informatiq_sp_client_secret' ); $refresh_token = get_option( 'informatiq_sp_refresh_token' ); if ( empty( $merchant_id ) || empty( $refresh_token ) ) { wp_send_json_error( array( 'message' => __( 'Please configure settings and authorize first.', 'informatiq-smart-pricing' ) ) ); } try { $google_api = new Informatiq_SP_Google_API( $merchant_id, $client_id, $client_secret, $refresh_token, $this->logger ); // Get price insights from Google (suggested prices + predicted impact). $price_insights = $google_api->get_price_insights(); $this->logger->info( sprintf( 'Loaded %d price insights from Google', count( $price_insights ) ) ); // Get all WooCommerce in-stock products (no limit - load all). $wc_products = wc_get_products( array( 'status' => 'publish', 'stock_status' => 'instock', 'limit' => -1, 'return' => 'objects', 'type' => array( 'simple', 'variable' ), ) ); $comparison = array(); foreach ( $wc_products as $product ) { $sku = $product->get_sku(); if ( empty( $sku ) ) { continue; } $product_id = $product->get_id(); // Get local price (tax-inclusive for comparison with Google). $sale_price = $product->get_sale_price(); $regular_price = $product->get_regular_price(); $base_price = ! empty( $sale_price ) ? $sale_price : $regular_price; $price_type = ! empty( $sale_price ) ? 'sale' : 'regular'; $local_price_incl_tax = null; if ( $base_price ) { $local_price_incl_tax = wc_get_price_including_tax( $product, array( 'price' => $base_price ) ); } // Try to find matching price insight by WooCommerce product ID (which is Google's offerId). $insight = null; if ( isset( $price_insights[ 'offer_' . $product_id ] ) ) { $insight = $price_insights[ 'offer_' . $product_id ]; } // Calculate potential gain and determine if update is beneficial. $suggested_price = $insight['suggested_price'] ?? null; $potential_gain = null; $percent_diff = null; if ( $suggested_price && $local_price_incl_tax ) { $potential_gain = round( $suggested_price - $local_price_incl_tax, 2 ); // Calculate percentage difference. $percent_diff = round( ( ( $suggested_price - $local_price_incl_tax ) / $local_price_incl_tax ) * 100, 1 ); } $comparison[] = array( 'id' => $product_id, 'name' => $product->get_name(), 'sku' => $sku, 'local_price' => $local_price_incl_tax ? round( $local_price_incl_tax, 2 ) : null, 'price_type' => $price_type, 'google_price' => $insight['google_price'] ?? null, 'suggested_price' => $suggested_price, 'percent_diff' => $percent_diff, 'potential_gain' => $potential_gain, 'predicted_impressions_change' => $insight['predicted_impressions_change'] ?? null, 'predicted_clicks_change' => $insight['predicted_clicks_change'] ?? null, 'predicted_conversions_change' => $insight['predicted_conversions_change'] ?? null, 'has_insight' => ! empty( $insight ), ); } // Sort by predicted conversion change (highest first), products without data last. usort( $comparison, function( $a, $b ) { // Products without insight data go to the end. if ( ! $a['has_insight'] && $b['has_insight'] ) { return 1; } if ( $a['has_insight'] && ! $b['has_insight'] ) { return -1; } if ( ! $a['has_insight'] && ! $b['has_insight'] ) { return 0; } // Sort by predicted conversion change (highest first). $conv_a = $a['predicted_conversions_change'] ?? -999; $conv_b = $b['predicted_conversions_change'] ?? -999; return $conv_b <=> $conv_a; } ); wp_send_json_success( array( 'products' => $comparison, 'insights_count' => count( $price_insights ), 'wc_count' => count( $wc_products ), 'currency' => get_woocommerce_currency_symbol(), 'currency_code' => get_woocommerce_currency(), ) ); } catch ( Exception $e ) { wp_send_json_error( array( 'message' => $e->getMessage() ) ); } } /** * Handle individual price update AJAX request. */ public function handle_update_price() { check_ajax_referer( 'informatiq_sp_admin', 'nonce' ); if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) ); } $product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0; $new_price = isset( $_POST['new_price'] ) ? floatval( $_POST['new_price'] ) : 0; if ( ! $product_id || $new_price <= 0 ) { wp_send_json_error( array( 'message' => __( 'Invalid product ID or price.', 'informatiq-smart-pricing' ) ) ); } $product = wc_get_product( $product_id ); if ( ! $product ) { wp_send_json_error( array( 'message' => __( 'Product not found.', 'informatiq-smart-pricing' ) ) ); } // Convert tax-inclusive price to the format WooCommerce expects. $price_to_set = $new_price; if ( ! wc_prices_include_tax() ) { // Store prices exclude tax, so we need to reverse-calculate. $tax_rates = WC_Tax::get_rates( $product->get_tax_class() ); if ( ! empty( $tax_rates ) ) { $tax_rate = 0; foreach ( $tax_rates as $rate ) { $tax_rate += (float) $rate['rate']; } $price_to_set = $new_price / ( 1 + ( $tax_rate / 100 ) ); } } $price_to_set = round( $price_to_set, wc_get_price_decimals() ); // Store old price for logging. $old_price = $product->get_sale_price() ?: $product->get_regular_price(); // Set as sale price (since we're adjusting to be competitive). $product->set_sale_price( $price_to_set ); $product->save(); // Log the update. $this->logger->log_price_update( $product_id, $product->get_name(), $old_price, $price_to_set, $new_price, 'Manual update to Google suggested price' ); wp_send_json_success( array( 'message' => sprintf( __( 'Price updated for %s: %s', 'informatiq-smart-pricing' ), $product->get_name(), wc_price( $price_to_set ) ), 'new_price' => $price_to_set, ) ); } /** * Handle bulk price update AJAX request. */ public function handle_bulk_update_prices() { check_ajax_referer( 'informatiq_sp_admin', 'nonce' ); if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) ); } $updates = isset( $_POST['updates'] ) ? $_POST['updates'] : array(); if ( empty( $updates ) || ! is_array( $updates ) ) { wp_send_json_error( array( 'message' => __( 'No updates provided.', 'informatiq-smart-pricing' ) ) ); } $updated = 0; $errors = 0; foreach ( $updates as $update ) { $product_id = absint( $update['product_id'] ?? 0 ); $new_price = floatval( $update['new_price'] ?? 0 ); if ( ! $product_id || $new_price <= 0 ) { $errors++; continue; } $product = wc_get_product( $product_id ); if ( ! $product ) { $errors++; continue; } // Convert tax-inclusive price to the format WooCommerce expects. $price_to_set = $new_price; if ( ! wc_prices_include_tax() ) { $tax_rates = WC_Tax::get_rates( $product->get_tax_class() ); if ( ! empty( $tax_rates ) ) { $tax_rate = 0; foreach ( $tax_rates as $rate ) { $tax_rate += (float) $rate['rate']; } $price_to_set = $new_price / ( 1 + ( $tax_rate / 100 ) ); } } $price_to_set = round( $price_to_set, wc_get_price_decimals() ); $old_price = $product->get_sale_price() ?: $product->get_regular_price(); $product->set_sale_price( $price_to_set ); $product->save(); $this->logger->log_price_update( $product_id, $product->get_name(), $old_price, $price_to_set, $new_price, 'Bulk update to Google suggested price' ); $updated++; } wp_send_json_success( array( 'message' => sprintf( __( 'Bulk update complete: %d updated, %d errors.', 'informatiq-smart-pricing' ), $updated, $errors ), 'updated' => $updated, 'errors' => $errors, ) ); } /** * Enqueue admin assets. * * @param string $hook Current admin page hook. */ public function enqueue_admin_assets( $hook ) { if ( 'woocommerce_page_informatiq-smart-pricing' !== $hook ) { return; } wp_enqueue_style( 'informatiq-sp-admin', INFORMATIQ_SP_PLUGIN_URL . 'assets/css/admin.css', array(), INFORMATIQ_SP_VERSION ); wp_enqueue_script( 'informatiq-sp-admin', INFORMATIQ_SP_PLUGIN_URL . 'assets/js/admin.js', array( 'jquery' ), INFORMATIQ_SP_VERSION, true ); wp_localize_script( 'informatiq-sp-admin', 'informatiqSP', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'informatiq_sp_admin' ), 'strings' => array( 'syncInProgress' => __( 'Sync in progress...', 'informatiq-smart-pricing' ), 'testInProgress' => __( 'Testing connection...', 'informatiq-smart-pricing' ), 'revokeConfirm' => __( 'Are you sure you want to revoke Google authorization?', 'informatiq-smart-pricing' ), 'revokeInProgress' => __( 'Revoking...', 'informatiq-smart-pricing' ), ), ) ); } }