Changeset 3433074
- Timestamp:
- 01/05/2026 09:12:17 PM (7 weeks ago)
- Location:
- routeapp/trunk
- Files:
-
- 5 edited
-
includes/class-routeapp-api-client.php (modified) (4 diffs)
-
public/class-routeapp-public.php (modified) (5 diffs)
-
public/js/routeapp-public-pbc.js (modified) (4 diffs)
-
readme.txt (modified) (2 diffs)
-
routeapp.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
routeapp/trunk/includes/class-routeapp-api-client.php
r3092267 r3433074 178 178 /** 179 179 * Get current quote price based on subtotal 180 * Optimized to store only essential data in session 180 181 * @param $cartRef 181 182 * @param $cartTotal … … 203 204 //if creation date is more than 30 minutes, we unset it 204 205 WC()->session->__unset($key); 206 $lastCalledMade = $key . '-latest'; 207 WC()->session->__unset($lastCalledMade); 208 // Clean up all old entries when we find an expired one 209 $this->_cleanup_old_quote_sessions(); 205 210 } else { 206 211 return $cached['result']; … … 208 213 } 209 214 210 $cached['createdAt'] = time();211 $ cached['result']= $this->_make_private_api_call('quotes', array(215 // Make API call 216 $api_response = $this->_make_private_api_call('quotes', array( 212 217 'merchant_id' => $merchant_id, 213 218 'cart' => [ … … 220 225 ], 221 226 ), 'POST', 'v2'); 227 228 // Extract only essential data from API response to minimize session storage 229 $essential_data = $this->_extract_essential_quote_data($api_response); 230 231 // Store only essential data in session (not the full HTTP response) 222 232 if (WC()->session) { 233 // Clean up old entries BEFORE adding new one to enforce limit 234 // This ensures we don't exceed max_entries even when adding a new quote 235 $this->_cleanup_old_quote_sessions(); 236 237 $created_at = time(); 238 $cached = array( 239 'createdAt' => $created_at, 240 'result' => $essential_data 241 ); 223 242 WC()->session->set($key, $cached); 224 $lastCalledMade = $key. '-latest'; 225 WC()->session->set($lastCalledMade, $cached['result']); 226 } 227 return $cached['result']; 243 244 // Store latest quote data (also minimal) 245 // Format: array with 'body' key for compatibility with routeapp_save_quote_to_order 246 $lastCalledMade = $key . '-latest'; 247 WC()->session->set($lastCalledMade, $essential_data); 248 249 // Track this key for cleanup and limit total entries 250 $this->_track_quote_session_key($key, $created_at); 251 252 // Clean up again after adding to ensure limit is strictly enforced 253 // This handles the case where we had exactly max_entries before adding 254 $this->_cleanup_old_quote_sessions(); 255 } 256 257 return $essential_data; 258 } 259 260 /** 261 * Extract only essential data from API response 262 * Prevents storing full HTTP response objects in session 263 * Stores only: id, premium.amount, premium.currency, payment_responsible.type, payment_responsible.ToggleState 264 * 265 * @param array|WP_Error $api_response The full API response from wp_remote_request 266 * @return array Minimal quote data in expected format (compatible with existing code) 267 */ 268 private function _extract_essential_quote_data($api_response) 269 { 270 // Handle errors - return in expected format 271 if (is_wp_error($api_response)) { 272 return array( 273 'response' => array('code' => 500), 274 'body' => json_encode(array('premium' => array('amount' => '0'))) 275 ); 276 } 277 278 // Extract body from response 279 $response_code = wp_remote_retrieve_response_code($api_response); 280 $response_body = wp_remote_retrieve_body($api_response); 281 282 // If API call failed, return original format for error handling 283 if ($response_code !== 200 && $response_code !== 201) { 284 return $api_response; // Return original for error handling 285 } 286 287 // Parse JSON body 288 $body_data = json_decode($response_body, true); 289 290 if (!$body_data || empty($body_data)) { 291 return array( 292 'response' => array('code' => $response_code), 293 'body' => json_encode(array('premium' => array('amount' => '0'))) 294 ); 295 } 296 297 $essential_quote = array(); 298 299 // Extract essential fields 300 if (isset($body_data['id'])) { 301 $essential_quote['id'] = $body_data['id']; 302 } 303 304 if (isset($body_data['premium'])) { 305 $essential_quote['premium'] = array( 306 'currency' => isset($body_data['premium']['currency']) ? $body_data['premium']['currency'] : 'USD', 307 'amount' => isset($body_data['premium']['amount']) ? $body_data['premium']['amount'] : '0' 308 ); 309 } 310 311 if (isset($body_data['payment_responsible'])) { 312 $payment = $body_data['payment_responsible']; 313 $essential_quote['payment_responsible'] = array( 314 'type' => isset($payment['type']) ? $payment['type'] : 'paid_by_merchant' 315 ); 316 317 // Convert to boolean ToggleState for compatibility with existing code 318 if (array_key_exists('toggle_state', $payment)) { 319 $toggle_state_value = $payment['toggle_state']; 320 // Convert "checked" -> true, "unchecked" -> false 321 $essential_quote['payment_responsible']['ToggleState'] = ($toggle_state_value === 'checked' || $toggle_state_value === true || $toggle_state_value === 1); 322 } elseif (array_key_exists('ToggleState', $payment)) { 323 // Fallback for camelCase format (if API changes) 324 $essential_quote['payment_responsible']['ToggleState'] = $payment['ToggleState']; 325 } 326 } 327 328 // Return in expected format (compatible with routeapp_get_quote_from_api) 329 return array( 330 'response' => array('code' => $response_code), 331 'body' => json_encode($essential_quote, JSON_FORCE_OBJECT) 332 ); 333 } 334 335 /** 336 * Track quote session keys for cleanup management 337 * 338 * @param string $key Session key 339 * @param int $created_at Timestamp 340 */ 341 private function _track_quote_session_key($key, $created_at) 342 { 343 if (!WC()->session) { 344 return; 345 } 346 347 $tracker_key = $this->get_cache_api_session_key() . '_keys'; 348 $tracked_keys = WC()->session->get($tracker_key); 349 350 if (!is_array($tracked_keys)) { 351 $tracked_keys = array(); 352 } 353 354 // Add current key to tracker (avoid duplicates) 355 $key_exists = false; 356 foreach ($tracked_keys as $index => $tracked) { 357 if (isset($tracked['key']) && $tracked['key'] === $key) { 358 $tracked_keys[$index]['time'] = $created_at; // Update timestamp 359 $key_exists = true; 360 break; 361 } 362 } 363 364 if (!$key_exists) { 365 $tracked_keys[] = array('key' => $key, 'time' => $created_at); 366 } 367 368 WC()->session->set($tracker_key, $tracked_keys); 369 } 370 371 /** 372 * Clean up old quote session entries to prevent session bloat 373 * Removes entries older than 30 minutes and limits total entries per session 374 * This prevents accumulation of hundreds of quote entries 375 */ 376 private function _cleanup_old_quote_sessions() 377 { 378 if (!WC()->session) { 379 return; 380 } 381 382 $cache_prefix = $this->get_cache_api_session_key(); 383 $current_time = time(); 384 $max_age = 1800; // 30 minutes 385 $max_entries = 5; // Maximum number of quote entries per session (reduced from potential hundreds) 386 387 // Get tracked keys 388 $tracker_key = $cache_prefix . '_keys'; 389 $tracked_keys = WC()->session->get($tracker_key); 390 391 if (!is_array($tracked_keys) || empty($tracked_keys)) { 392 return; 393 } 394 395 // Clean up old entries (expired) and verify they still exist in session 396 $valid_keys = array(); 397 foreach ($tracked_keys as $key_with_timestamp) { 398 if (!is_array($key_with_timestamp) || !isset($key_with_timestamp['key']) || !isset($key_with_timestamp['time'])) { 399 continue; 400 } 401 402 $key = $key_with_timestamp['key']; 403 $created_at = $key_with_timestamp['time']; 404 $age = $current_time - $created_at; 405 406 // Check if entry still exists in session (might have been manually removed) 407 $session_entry = WC()->session->get($key); 408 409 if ($age > $max_age || !$session_entry) { 410 // Remove expired or missing entry 411 WC()->session->__unset($key); 412 WC()->session->__unset($key . '-latest'); 413 } else { 414 // Keep valid entry 415 $valid_keys[] = $key_with_timestamp; 416 } 417 } 418 419 // Always sort by time (newest first) to prepare for limiting 420 usort($valid_keys, function($a, $b) { 421 return $b['time'] - $a['time']; 422 }); 423 424 // Limit total entries (keep most recent) - STRICTLY enforce max_entries 425 if (count($valid_keys) > $max_entries) { 426 // Keep only max_entries (most recent) 427 $keys_to_keep = array_slice($valid_keys, 0, $max_entries); 428 429 // Remove excess entries (older ones beyond limit) 430 $keys_to_remove = array_slice($valid_keys, $max_entries); 431 foreach ($keys_to_remove as $excess_entry) { 432 if (isset($excess_entry['key'])) { 433 $excess_key = $excess_entry['key']; 434 WC()->session->__unset($excess_key); 435 WC()->session->__unset($excess_key . '-latest'); 436 } 437 } 438 439 // Update tracker with only kept keys (maintain sorted order) 440 $valid_keys = $keys_to_keep; 441 } 442 443 // Always update tracker to maintain correct order and remove any orphaned entries 444 WC()->session->set($tracker_key, $valid_keys); 228 445 } 229 446 -
routeapp/trunk/public/class-routeapp-public.php
r3433065 r3433074 36 36 const ALLOWED_QUOTE_TYPE = 'paid_by_customer'; 37 37 38 const AMPLITUDE_API_KEY = [ 39 'production' => '6d4f9158886e0c6d78e3bd97663ba920', 40 'stage' => '99a869d62986f6415610c6741057bc92', 41 'dev' => '527af176070cf4b775c6a718f0737f0a' 42 ]; 43 38 44 /** 39 45 * The ID of this plugin. … … 44 50 */ 45 51 private $plugin_name; 52 53 private $route_insurance_data = null; 46 54 47 55 /** … … 390 398 $cartItems = $this->get_cart_shippable_items($cart); 391 399 $route_insurance_quote = $this->routeapp_get_quote_from_api($cartRef, $cartTotal, $currency, $cartItems); 400 $this->route_insurance_data = $route_insurance_quote; 392 401 393 402 $route_insurance_amount = false; … … 397 406 $route_insurance_amount = $route_insurance_quote->premium->amount; 398 407 } 399 if (is_null($checkbox) && isset($route_insurance_quote->payment_responsible->ToggleState)) { 408 409 if (is_null($checkbox) && 410 isset($route_insurance_quote->payment_responsible) && 411 property_exists($route_insurance_quote->payment_responsible, 'ToggleState')) { 400 412 $checkbox = $route_insurance_quote->payment_responsible->ToggleState; 401 413 WC()->session->set('checkbox_checked', $checkbox); … … 482 494 wp_enqueue_script($this->plugin_name . '-widget', 'https://protection-widget.route.com/route-protection-widget.js', array(), $this->version, false); 483 495 484 wp_enqueue_script($this->plugin_name, plugin_dir_url(__FILE__) . 'js/routeapp-public-pbc.js', array('jquery'), $this->version, false); 496 $amplitude_key = self::AMPLITUDE_API_KEY[$custom_env]; 497 498 // Load Amplitude SDK (standard version, not snippet) 499 wp_enqueue_script('amplitude-js', "https://cdn.amplitude.com/script/$amplitude_key.js", array(), null, false); 500 501 // Enqueue Amplitude Analytics module (depends on jQuery and Amplitude SDK) 502 wp_enqueue_script($this->plugin_name . '-amplitude-analytics', plugin_dir_url(__FILE__) . 'js/amplitude-analytics.js', array('jquery', 'amplitude-js'), $this->version, false); 503 504 // Main script depends on amplitude-analytics when available 505 wp_enqueue_script($this->plugin_name, plugin_dir_url(__FILE__) . 'js/routeapp-public-pbc.js', array('jquery', $this->plugin_name . '-amplitude-analytics'), $this->version, false); 506 507 /** 508 * Calculate total Route fee 509 * Uses the premium amount from route insurance data if available, 510 * otherwise defaults to 0.98 as a fallback fee amount 511 */ 512 $totalRouteFee = (is_object($this->route_insurance_data) && isset($this->route_insurance_data->premium->amount)) 513 ? (float) $this->route_insurance_data->premium->amount 514 : 0.98; 515 516 // Pass cart data to JavaScript - send to amplitude-analytics.js 517 wp_localize_script($this->plugin_name . '-amplitude-analytics', 'cartData', array( 518 'cartId' => WC()->cart->get_cart_hash(), 519 'amplitudeKey' => $amplitude_key, 520 'widgetId' => uniqid('widget-', true), 521 'subtotal' => round($this->get_cart_subtotal_with_only_shippable_items(WC()->cart), 2), 522 'totalRouteFee' => $totalRouteFee, 523 'environment' => $custom_env, 524 'currency' => get_woocommerce_currency(), 525 'merchantId' => $this->routeapp_api_client->get_merchant_id(), 526 )); 485 527 486 528 wp_enqueue_style($this->plugin_name . '-widget', plugin_dir_url(__FILE__) . 'css/routeapp-widget.css', array(), $this->version, false); -
routeapp/trunk/public/js/routeapp-public-pbc.js
r3433065 r3433074 23 23 is_cart_page: window.location.pathname === "/cart/", 24 24 invalid_shipping_method: '.routeapp-invalid-shipping-method', 25 checkbox: $(PROTECTION_COOKIE).length ? parseCheckboxValue() : Route.Coverage.ActiveByDefault ,25 checkbox: $(PROTECTION_COOKIE).length ? parseCheckboxValue() : Route.Coverage.ActiveByDefault 26 26 }; 27 27 … … 63 63 success: function () { 64 64 RouteConfig.is_cart_page ? triggerCartUpdate() : triggerCheckoutUpdate(); 65 66 // Track protection change using RouteAmplitudeAnalytics 67 if (window.RouteAmplitudeAnalytics) { 68 RouteAmplitudeAnalytics.trackProtectChange(checkbox); 69 } 65 70 } 66 71 }); … … 108 113 $(ROUTE_WIDGET_ID).show(); 109 114 renderWidget(result['routeapp-subtotal']); 115 // Track widget render using RouteAmplitudeAnalytics 116 if (window.RouteAmplitudeAnalytics) { 117 RouteAmplitudeAnalytics.trackWidgetRender(); 118 } 110 119 } else { 111 120 $(ROUTE_WIDGET_ID).hide(); … … 119 128 $(document.body).on( UPDATED_CHECKOUT_EVENT, checkWidgetShow); 120 129 130 // Initialize Amplitude analytics using RouteAmplitudeAnalytics 131 if (window.RouteAmplitudeAnalytics) { 132 RouteAmplitudeAnalytics.initialize(); 133 } 134 135 // Set up event listeners for info modal tracking 136 // Listen for clicks on Route widget info buttons 137 $(document).on('click', '#RouteWidget .pw-info-icon', function() { 138 if (window.RouteAmplitudeAnalytics) { 139 RouteAmplitudeAnalytics.trackInfoChange('opened'); 140 } 141 }); 142 143 // Listen for modal close events 144 $(document).on('click', '#RouteWidget .route-modal__close', function() { 145 if (window.RouteAmplitudeAnalytics) { 146 RouteAmplitudeAnalytics.trackInfoChange('closed'); 147 } 148 }); 149 121 150 if (!$(RouteConfig.invalid_shipping_method).length) { 122 151 renderWidget($(RouteConfig.subtotal).val()); -
routeapp/trunk/readme.txt
r3433065 r3433074 6 6 Requires at least: 4.0 7 7 Tested up to: 6.7.1 8 Stable tag: 2.2. 298 Stable tag: 2.2.32 9 9 Requires PHP: 5.6 10 10 License: GPLv2 or later … … 106 106 107 107 == Changelog == 108 109 = 2.2.32 = 110 * Update plugin version 111 112 = 2.2.31 = 113 * Fix session bloat issue by optimizing quote data storage 114 115 = 2.2.30 = 116 * Add Amplitude tracking for Route widget interactions 108 117 109 118 = 2.2.29 = -
routeapp/trunk/routeapp.php
r3433065 r3433074 10 10 * Plugin URI: https://route.com/for-merchants/ 11 11 * Description: Route allows shoppers to insure their orders with one-click during checkout, adding a layer of 3rd party trust while improving the customer shopping experience. 12 * Version: 2.2. 2912 * Version: 2.2.32 13 13 * Author: Route 14 14 * Author URI: https://route.com/ … … 26 26 * Currently plugin version. 27 27 */ 28 define( 'ROUTEAPP_VERSION', '2.2. 29' );28 define( 'ROUTEAPP_VERSION', '2.2.32' ); 29 29 30 30 /**
Note: See TracChangeset
for help on using the changeset viewer.