api_url, [ 'method' => 'POST', 'headers' => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_token, 'X-Widget-Id' => $widget_id, 'Origin' => $origin, 'Referer' => $referer, 'X-Origin' => $origin, 'Accept' => 'application/json', 'X-Requested-With' => 'XMLHttpRequest' ], 'body' => wp_json_encode($payload), 'timeout' => $this->timeout, 'sslverify' => true ] ); if (is_wp_error($response)) { return $response; } $status_code = (int) wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); if ($status_code < 200 || $status_code >= 300) { $message = $this->extract_error_message_from_body($body); if (empty($message)) { $message = sprintf('Chat service returned HTTP %d', $status_code); } return new WP_Error('http_error', $message, ['status_code' => $status_code]); } if (empty($body)) { return new WP_Error('empty_response', 'Chat service returned an empty response'); } $data = json_decode($body, true); if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) { return new WP_Error('invalid_response', 'Invalid response from chat service'); } // Support both Shopify format (status: success) and other formats if (isset($data['status']) && $data['status'] !== 'success') { return new WP_Error('api_status_error', $this->extract_error_message_from_body($body) ?: 'Request failed'); } return $data; } /** * Fetch AI-generated shopping suggestions for the current context. * * @param array $payload Context payload (page, product_id, cart, etc.) * @param string $api_token JWT token for authentication * @param string $widget_id Widget ID * * @return array|WP_Error */ public function get_suggestions($payload, $api_token, $widget_id) { $api_token = trim((string) $api_token); if (empty($api_token)) { return new WP_Error('no_token', 'API token not configured'); } $payload['channel_id'] = 'wordpress'; $origin = ''; if (!empty($payload['origin']) && is_string($payload['origin'])) { $origin = esc_url_raw($payload['origin']); } if (empty($origin)) { $origin = home_url(); $payload['origin'] = $origin; } $referer = ''; if (!empty($payload['referer']) && is_string($payload['referer'])) { $referer = esc_url_raw($payload['referer']); } if (empty($referer)) { $referer = $origin; } $response = wp_remote_post( $this->suggestions_api_url, [ 'method' => 'POST', 'headers' => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_token, 'X-Widget-Id' => $widget_id, 'Origin' => $origin, 'Referer' => $referer, 'X-Origin' => $origin, 'Accept' => 'application/json', 'X-Requested-With' => 'XMLHttpRequest', ], 'body' => wp_json_encode($payload), 'timeout' => 30, 'sslverify' => true, ] ); if (is_wp_error($response)) { return $response; } $status_code = (int) wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); if ($status_code < 200 || $status_code >= 300) { $message = $this->extract_error_message_from_body($body); if (empty($message)) { $message = sprintf('Suggestion service returned HTTP %d', $status_code); } return new WP_Error('http_error', $message, ['status_code' => $status_code]); } if (empty($body)) { return new WP_Error('empty_response', 'Suggestion service returned an empty response'); } $data = json_decode($body, true); if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) { return new WP_Error('invalid_response', 'Invalid response from suggestion service'); } return $data; } /** * Default smart suggestion settings. * * @return array */ public function get_default_suggestion_settings() { return [ 'enable_smart_suggestions' => 1, 'max_suggestion_chips' => 5, 'suggest_on_widget_closed' => 1, 'auto_refresh_suggestions' => 1, 'followup_suggestions' => 1, 'product_recommendations' => 1, 'cross_sell_suggestions' => 1, 'upsell_suggestions' => 1, 'personalize_suggestions' => 1, 'suggestion_delay_seconds' => 3, ]; } /** * Extracts a meaningful error message from API response body. * * @param string $body * * @return string */ private function extract_error_message_from_body($body) { if (empty($body)) { return ''; } $decoded = json_decode($body, true); if (!is_array($decoded)) { return ''; } foreach (['message', 'error', 'detail', 'response'] as $key) { if (!empty($decoded[$key]) && is_string($decoded[$key])) { return sanitize_text_field($decoded[$key]); } } if (!empty($decoded['data']) && is_array($decoded['data'])) { foreach (['message', 'error', 'detail'] as $nested_key) { if (!empty($decoded['data'][$nested_key]) && is_string($decoded['data'][$nested_key])) { return sanitize_text_field($decoded['data'][$nested_key]); } } } return ''; } /** * Get API configuration */ public function get_config() { $opts = get_option($this->option_key, []); $suggestion_defaults = $this->get_default_suggestion_settings(); return array_merge( [ 'api_token' => $opts['api_token'] ?? '', 'widget_id' => $opts['widget_id'] ?? '', 'agent_id' => $opts['agent_id'] ?? '', 'user_id' => $opts['user_id'] ?? '', 'agent_name' => $opts['agent_name'] ?? 'AI Assistant', 'auto_insert' => $opts['auto_insert'] ?? 0, ], [ 'enable_smart_suggestions' => !isset($opts['enable_smart_suggestions']) || !empty($opts['enable_smart_suggestions']) ? 1 : 0, 'max_suggestion_chips' => max(3, min(8, (int) ($opts['max_suggestion_chips'] ?? $suggestion_defaults['max_suggestion_chips']))), 'suggest_on_widget_closed' => !isset($opts['suggest_on_widget_closed']) || !empty($opts['suggest_on_widget_closed']) ? 1 : 0, 'auto_refresh_suggestions' => !isset($opts['auto_refresh_suggestions']) || !empty($opts['auto_refresh_suggestions']) ? 1 : 0, 'followup_suggestions' => !isset($opts['followup_suggestions']) || !empty($opts['followup_suggestions']) ? 1 : 0, 'product_recommendations' => !isset($opts['product_recommendations']) || !empty($opts['product_recommendations']) ? 1 : 0, 'cross_sell_suggestions' => !isset($opts['cross_sell_suggestions']) || !empty($opts['cross_sell_suggestions']) ? 1 : 0, 'upsell_suggestions' => !isset($opts['upsell_suggestions']) || !empty($opts['upsell_suggestions']) ? 1 : 0, 'personalize_suggestions' => !isset($opts['personalize_suggestions']) || !empty($opts['personalize_suggestions']) ? 1 : 0, 'suggestion_delay_seconds' => max(0, min(15, (int) ($opts['suggestion_delay_seconds'] ?? $suggestion_defaults['suggestion_delay_seconds']))), ] ); } /** * Check if plugin is configured */ public function is_configured() { $config = $this->get_config(); return !empty($config['widget_id']) && !empty($config['agent_id']) && !empty($config['user_id']); } }