Plugin Directory

Changeset 3433074


Ignore:
Timestamp:
01/05/2026 09:12:17 PM (7 weeks ago)
Author:
routedev
Message:

version bump

Location:
routeapp/trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • routeapp/trunk/includes/class-routeapp-api-client.php

    r3092267 r3433074  
    178178    /**
    179179     * Get current quote price based on subtotal
     180     * Optimized to store only essential data in session
    180181     * @param $cartRef
    181182     * @param $cartTotal
     
    203204                //if creation date is more than 30 minutes, we unset it
    204205                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();
    205210            } else {
    206211                return $cached['result'];
     
    208213        }
    209214
    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(
    212217            'merchant_id' => $merchant_id,
    213218            'cart' => [
     
    220225            ],
    221226        ), '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)
    222232        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            );
    223242            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);
    228445    }
    229446
  • routeapp/trunk/public/class-routeapp-public.php

    r3433065 r3433074  
    3636    const ALLOWED_QUOTE_TYPE = 'paid_by_customer';
    3737
     38    const AMPLITUDE_API_KEY = [
     39        'production' => '6d4f9158886e0c6d78e3bd97663ba920',
     40        'stage' => '99a869d62986f6415610c6741057bc92',
     41        'dev' => '527af176070cf4b775c6a718f0737f0a'
     42    ];
     43
    3844    /**
    3945     * The ID of this plugin.
     
    4450     */
    4551    private $plugin_name;
     52
     53    private $route_insurance_data = null;
    4654
    4755    /**
     
    390398        $cartItems = $this->get_cart_shippable_items($cart);
    391399        $route_insurance_quote = $this->routeapp_get_quote_from_api($cartRef, $cartTotal, $currency, $cartItems);
     400        $this->route_insurance_data = $route_insurance_quote;
    392401
    393402        $route_insurance_amount = false;
     
    397406            $route_insurance_amount = $route_insurance_quote->premium->amount;
    398407        }
    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')) {
    400412            $checkbox = $route_insurance_quote->payment_responsible->ToggleState;
    401413            WC()->session->set('checkbox_checked', $checkbox);
     
    482494        wp_enqueue_script($this->plugin_name . '-widget', 'https://protection-widget.route.com/route-protection-widget.js', array(), $this->version, false);
    483495
    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        ));
    485527
    486528        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  
    2323            is_cart_page: window.location.pathname === "/cart/",
    2424            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
    2626        };
    2727
     
    6363                success: function () {
    6464                    RouteConfig.is_cart_page ? triggerCartUpdate() : triggerCheckoutUpdate();
     65
     66                    // Track protection change using RouteAmplitudeAnalytics
     67                    if (window.RouteAmplitudeAnalytics) {
     68                        RouteAmplitudeAnalytics.trackProtectChange(checkbox);
     69                    }
    6570                }
    6671            });
     
    108113                        $(ROUTE_WIDGET_ID).show();
    109114                        renderWidget(result['routeapp-subtotal']);
     115                        // Track widget render using RouteAmplitudeAnalytics
     116                        if (window.RouteAmplitudeAnalytics) {
     117                            RouteAmplitudeAnalytics.trackWidgetRender();
     118                        }
    110119                    } else {
    111120                        $(ROUTE_WIDGET_ID).hide();
     
    119128        $(document.body).on( UPDATED_CHECKOUT_EVENT, checkWidgetShow);
    120129
     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
    121150        if (!$(RouteConfig.invalid_shipping_method).length) {
    122151            renderWidget($(RouteConfig.subtotal).val());
  • routeapp/trunk/readme.txt

    r3433065 r3433074  
    66Requires at least: 4.0
    77Tested up to: 6.7.1
    8 Stable tag: 2.2.29
     8Stable tag: 2.2.32
    99Requires PHP: 5.6
    1010License: GPLv2 or later
     
    106106
    107107== 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
    108117
    109118= 2.2.29 =
  • routeapp/trunk/routeapp.php

    r3433065 r3433074  
    1010 * Plugin URI:        https://route.com/for-merchants/
    1111 * 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.29
     12 * Version:           2.2.32
    1313 * Author:            Route
    1414 * Author URI:        https://route.com/
     
    2626 * Currently plugin version.
    2727 */
    28 define( 'ROUTEAPP_VERSION', '2.2.29' );
     28define( 'ROUTEAPP_VERSION', '2.2.32' );
    2929
    3030/**
Note: See TracChangeset for help on using the changeset viewer.