# routeapp/2.4.7/public/class-routeapp-public.php

Route ‑ Shipping Protection, version 2.4.7. 2,102 lines.

- Page: https://pluginprobe.com/plugins/routeapp/2.4.7/code/public/class-routeapp-public.php
- Raw: https://pluginprobe.com/plugins/routeapp/2.4.7/raw/public/class-routeapp-public.php
- Modified: 2026-05-04T19:06:34+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/routeapp/2.4.7/code/public/class-routeapp-public.php#L10-L20`.

```php
<?php
/**
 * The public-facing functionality of the plugin.
 *
 * @link       https://route.com/
 * @since      1.0.0
 *
 * @package    Routeapp
 * @subpackage Routeapp/public
 */

use Automattic\WooCommerce\Utilities\OrderUtil;

/**
 * The public-facing functionality of routeapp.
 *
 *
 * @package    Routeapp
 * @subpackage Routeapp/public
 * @author     Route App <support@route.com>
 */

class Routeapp_Public {

    const HTTP_SUCCESS_CODE = 200;
    const HTTP_BAD_REQUEST_CODE = 400;
    const HTTP_CREATED_CODE = 201;
    const HTTP_CONFLICT_CODE = 409;
    const HTTP_NOT_FOUND = 404;
    const HTTP_INTERNAL_SERVER_ERROR_CODE = 500;
    const DEFAULT_MAX_USD_SUBTOTAL_ALLOWED = 5000;
    const DEFAULT_MIN_USD_SUBTOTAL_ALLOWED = 0;
    const ROUTE_LABEL = 'Route Shipping Protection';
    const DEFAULT_CHECKOUT_WEBHOOK =  'woocommerce_checkout_before_order_review';
    const ROUTE_PROTECTION_SKU = 'ROUTEINS';
    const ALLOWED_QUOTE_TYPE = 'paid_by_customer';

	const AMPLITUDE_API_KEY = [
		'production' => '6d4f9158886e0c6d78e3bd97663ba920',
		'stage' => '99a869d62986f6415610c6741057bc92',
		'dev' => '527af176070cf4b775c6a718f0737f0a'
	];

    /**
     * The ID of this plugin.
     *
     * @since    1.0.0
     * @access   private
     * @var      string    $plugin_name    The ID of this plugin.
     */
    private $plugin_name;

	private $route_insurance_data = null;

    /**
     * The version of this plugin.
     *
     * @since    1.0.0
     * @access   private
     * @var      string    $version    The current version of this plugin.
     */
    private $version;

    /**
     * Routeapp API instance
     * @since    1.0.0
     * @access   private
     * @var 	string 		$routeapp_api 	Routeapp API Client
     */
    public $routeapp_api_client;


    /**
     * Route Shipment Tracking Module
     */
    private $routeapp_shipment_tracking;

    /**
     * Last Request
     */
    private $routeapp_last_request;

    /**
     * Route Logger
     */
    private $routeapp_logger;

    /**
     * Route Admin Fee
     */
    private $routeapp_admin_fee;

    /**
     * Route Setup
     */
    private $route_app_setup_helper;

    /**
     * Whether the custom Route checkout block has been rendered on this request.
     */
    private $route_block_rendered = false;

    /**
     * Initialize the class and set its properties.
     *
     * @since    1.0.0
     * @param      string    $plugin_name       The name of the plugin.
     * @param      string    $version    The version of this plugin.
     */
    public function __construct($plugin_name, $version)
    {

        $this->plugin_name = $plugin_name;
        $this->version = $version;
        $this->routeapp_api_client = new Routeapp_API_Client($this->routeapp_get_public_token(), $this->routeapp_get_secret_token());
        $this->routeapp_shipment_tracking = new Routeapp_Shipment_Tracking();
        $this->routeapp_logger = new Routeapp_Logger_Sentry();
        $this->routeapp_admin_fee = new Routeapp_Admin_Route_Fee();
        $this->route_app_setup_helper = new Route_Setup();
        Route_Setup::ensure_store_hash();
        $this->add_actions_and_filters();
    }

    public function add_actions_and_filters() {
        //CHECKOUT HOOK
        $checkout_hook = $this->routeapp_get_checkout_hook();
        add_action($checkout_hook, array($this, 'checkout_route_insurance'), 1, 0);


        add_action('woocommerce_checkout_before_order_review', array($this, 'checkout_route_insurance_fee'), 20, 1);

        add_action('woocommerce_cart_calculate_fees', array($this, 'checkout_route_insurance_fee'), 20, 1);
        add_action('wp_ajax_woo_get_ajax_data', array($this, 'checkout_route_insurance_set_session'));
        add_action('wp_ajax_nopriv_woo_get_ajax_data', array($this, 'checkout_route_insurance_set_session'));

        add_action('wp_ajax_woo_check_widget', array($this, 'check_route_widget'));
        add_action('wp_ajax_nopriv_woo_check_widget', array($this, 'check_route_widget'));

        add_action( 'woocommerce_checkout_create_order_line_item', [ $this, 'routeapp_checkout_create_order_line_item' ], 10, 4 );
        add_action( 'woocommerce_ajax_add_order_item_meta', [$this, 'routeapp_ajax_add_order_item_meta'], 10, 2 );
        add_filter( 'woocommerce_hidden_order_itemmeta', [$this,'routeapp_woocommerce_hidden_order_itemmeta'], 10, 1);

        add_action( 'added_post_meta', array( $this, 'routeapp_update_tracking_order_api' ), 20, 3 );
        add_action( 'updated_post_meta', array( $this, 'routeapp_update_tracking_order_api' ), 20, 3 );

        add_action('woocommerce_after_order_object_save', array($this, 'routeapp_update_tracking_order_api_HPOS'), 20, 2);
        
        add_action( 'added_order_item_meta', array( $this, 'routeapp_update_tracking_order_item_api' ), 20, 3 );
        add_action( 'updated_order_item_meta', array( $this, 'routeapp_update_tracking_order_item_api' ), 20, 3 );
        add_action('woocommerce_order_status_processing', array($this, 'routeapp_mark_order'), 10, 1);
        add_action('woocommerce_order_status_completed', array($this, 'routeapp_mark_order'), 8, 2);
        add_action('woocommerce_new_order', array($this, 'routeapp_save_quote_to_order'), 10, 1);
        add_action('rest_api_init', array($this, 'routeapp_register_status_route'));
        add_action('rest_api_init', array($this, 'routeapp_register_setup_route'));
        add_action('wp_ajax_get_route_checkout', array($this, 'routeapp_register_checkout_route'));
        add_action('wp_ajax_nopriv_get_route_checkout', array($this, 'routeapp_register_checkout_route'));
        add_action('wp_head', array($this, 'wc_routeapp_ajaxurl'));
        add_action('woocommerce_init', array($this, 'register_shortcode'));
        add_action('woocommerce_rest_prepare_order_note', array($this, 'check_order_note'));
        add_action('wp_insert_comment', array($this, 'check_order_note_by_note_id'));

        add_action('wp_ajax_routeapp_add_admin_fee', array($this, 'routeapp_add_admin_fee'));
        add_action('wp_ajax_nopriv_routeapp_add_admin_fee', array($this, 'routeapp_add_admin_fee'));
        add_action('wp_ajax_routeapp_remove_admin_fee', array($this, 'routeapp_remove_admin_fee'));
        add_action('wp_ajax_nopriv_routeapp_remove_admin_fee', array($this, 'routeapp_remove_admin_fee'));
        add_action('wc_avatax_api_fee_line_data', array($this, 'routeapp_edit_avatax_fee'));
        add_action('woocommerce_shipstation_export_order_xml', array($this, 'routeapp_add_sku_to_route_node') );

        add_action('woocommerce_order_details_after_order_table', array($this, 'order_updates_route_tracking'), 1, 1);

        add_action('woocommerce_blocks_checkout_update_order_meta', array($this, 'routeapp_save_order_status_to_order'), 10, 1);
        add_action('woocommerce_store_api_checkout_update_order_meta', array($this, 'routeapp_save_order_status_to_order'), 10, 1);

        // Register callback for WooCommerce blocks checkout cart updates
        add_action('woocommerce_blocks_loaded', array($this, 'routeapp_register_blocks_checkout_callback'));

        // Inject Route widget before order summary in checkout block (default placement)
        add_filter('render_block_woocommerce/checkout-order-summary-block', array($this, 'routeapp_checkout_block_before_order_summary'), 10, 3);

        // Register the Route Protection custom checkout block
        add_action('init', array($this, 'routeapp_register_checkout_block'));

        // Register IntegrationInterface for WooCommerce Blocks
        add_action('woocommerce_blocks_checkout_block_registration', array($this, 'routeapp_register_blocks_integration'));
    }

    /**
     * Hide order line_item meta_data from Route App
     *
     * @param $arr
     * @return mixed
     */
    function routeapp_woocommerce_hidden_order_itemmeta($arr) {
        $arr[] = '_routeapp_source_product_id';
        $arr[] = '_routeapp_image_url';
        $arr[] = '_routeapp_virtual';
        $arr[] = '_routeapp_weight_value';
        $arr[] = '_routeapp_weight_unit';
        $arr[] = '_routeapp_origin_location';
        return $arr;
    }

    /**
     * Fill metadata
     *
     * @param $item WC_Order_Item
     * @param $product WC_Product
     * @param $entity_type enum('order_item','item')
     * @return void
     */
    public static function fillMetaData($item, $product, $entity_type='order_item') {

        $sourceProductId = $product->get_id();
        $productImageUrl = (wp_get_attachment_url($product->get_image_id())) ? wp_get_attachment_url($product->get_image_id()) : null;
        $weightValue = $product->get_weight();

        $originLocation = json_encode([
            'address' => get_option('woocommerce_store_address'),
            'address2' => get_option('woocommerce_store_address_2'),
            'store_city' => get_option('woocommerce_store_city'),
            'store_postcode' => get_option('woocommerce_store_postcode'),
            'default_country' => get_option('woocommerce_default_country'),
        ]);


        switch($entity_type) {
            case 'order_item':
                //add source_product_id as order line_item meta_data
                self::addItemMeta($item, '_routeapp_source_product_id', $sourceProductId);

                //add image_url as order line_item meta_data
                self::addItemMeta($item, '_routeapp_image_url', $productImageUrl);

                //add weight_value as order line_item meta_data
                self::addItemMeta($item, '_routeapp_weight_value', $weightValue);

                //add weight_unit as order line_item meta_data
                self::addItemMeta($item, '_routeapp_weight_unit', get_option('woocommerce_weight_unit'));

                //check if product is virtual
                if ($product->is_virtual()) {
                    //add virtual as order line_item meta_data
                    self::addItemMeta($item, '_routeapp_virtual', 1);
                }
                //add origin location as order line_item meta_data
                self::addItemMeta($item, '_routeapp_origin_location', $originLocation);
                break;
            case 'item':
                //add source_product_id as order line_item meta_data
                self::addOrderItemMeta($item->get_id(), '_routeapp_source_product_id', $sourceProductId);

                //add image_url as order line_item meta_data
                self::addOrderItemMeta($item->get_id(), '_routeapp_image_url', $productImageUrl);

                //add weight_value as order line_item meta_data
                self::addOrderItemMeta($item->get_id(), '_routeapp_weight_value', $weightValue);

                //add weight_unit as order line_item meta_data
                self::addOrderItemMeta($item->get_id(), '_routeapp_weight_unit', get_option('woocommerce_weight_unit'));

                //check if product is virtual
                if ($product->is_virtual()) {
                    //add virtual as order line_item meta_data
                    self::addOrderItemMeta($item->get_id(), '_routeapp_virtual', 1);
                }
                //add origin location as order line_item meta_data
                self::addOrderItemMeta($item->get_id(), '_routeapp_origin_location', $originLocation);
                break;
        }
    }

    /**
     * Add order line_item meta_data on ajax add to cart
     *
     * @param $item_id String
     * @param $item WC_Order_Item
     * @return false|void
     */
    public static function routeapp_ajax_add_order_item_meta( $item_id, $item) {
        $product = $item->get_product(); // Get the WC_Product object
        if ($product instanceof WC_Product) {
            try {
                self::fillMetaData($item, $product, 'item');
            }catch (Exception $exception) {
                return false;
            }
        }
    }

    /**
     * @param $itemId WC_Order_Item ID
     * @param $key String
     * @param $value String
     * @return bool
     */
    private static function addOrderItemMeta($itemId, $key, $value) {
        try {
            $fieldExists = wc_get_order_item_meta( $itemId, $key, true);
            if (!$fieldExists) {
                wc_add_order_item_meta($itemId, $key, $value);
            }
        }catch (Exception $exception) {
            return false;
        }
        return true;
    }

    /**
     * @param $item WC_Order_Item
     * @param $key String
     * @param $value String
     * @return bool
     */
    private static function addItemMeta($item, $key, $value) {
        try {
            $fieldExists =  $item->get_meta($key, true);
            if (!$fieldExists) {
                $item->add_meta_data( $key, $value );
            }
        }catch (Exception $exception) {
            return false;
        }
        return true;
    }

    /**
     * Add order line_item meta_data on checkout add to cart
     *
     * @param $item WC_Order_Item
     * @param $cart_item_key String
     * @param $values Object
     * @param $order WC_Order
     * @return false|void
     */
    public function routeapp_checkout_create_order_line_item( $item, $cart_item_key, $values, $order ) {
        if ( $order instanceof WC_Order ) {
            $product = $item->get_product(); // Get the WC_Product object
            if ($product instanceof WC_Product) {
                try {
                    self::fillMetaData($item, $product, 'order_item');
                }catch (Exception $exception) {
                    return false;
                }
            }
        }
    }

    public function routeapp_add_admin_fee() {
        $this->routeapp_admin_fee->routeapp_add_admin_fee();
    }

    public function routeapp_remove_admin_fee() {
        $this->routeapp_admin_fee->routeapp_remove_admin_fee();
    }

    public function register_shortcode() {
        add_shortcode( 'route', array( $this, 'route_widget_shortcode' ) );
    }

    public function get_allowed_quote_type() {
        return self::ALLOWED_QUOTE_TYPE;
    }

    public function check_route_widget() {
        if (!isset($_POST) || !$_POST['shipping_method']) {
            wp_send_json_error('its missing shipping method on the request');
            return;
        }
        $shipping_method = isset($_POST['shipping_method']) ? $_POST['shipping_method'] : '';
        $response = [];
        $response['show_widget'] =  $this->routeapp_is_shipping_method_allowed($shipping_method);
        wp_send_json_success( $response );
        return;
    }

    /**
     * set routeapp session
     *
     * @since    1.0.0
     */
    public function checkout_route_insurance_set_session() {
        if ( isset($_POST['checkbox']) && $_POST['checkbox'] === 'true' ){
            WC()->session->set('checkbox_checked', true );
        } else {
            WC()->session->set( 'checkbox_checked', false );
        }
    }

    /**
     * Add route insurance fee in checkout and cart
     * @since    1.0.0
     * @param  Object $cart_or_checkout
     * MSS-6261:The issue: checkout_route_insurance_fee is bound to hooks that pass different parameter types:
     * woocommerce_checkout_before_order_review -> WC_Checkout
     * woocommerce_cart_calculate_fees -> WC_Cart
     * At line 388, it assumes a WC_Cart and calls get_cart_hash(), which fails when a WC_Checkout is passed.
     * Fixing by detecting the object type and handling both cases.
    */
    public function checkout_route_insurance_fee( $cart_or_checkout ) {
        if ( (is_admin() && ! defined( 'DOING_AJAX' )) ) return;

        // Determine if we received a WC_Cart or WC_Checkout object
        $is_checkout_object = is_a( $cart_or_checkout, 'WC_Checkout' );
        $cart = $is_checkout_object ? WC()->cart : $cart_or_checkout;
        
        // Use WC()->cart as fallback if not a valid WC_Cart
        if ( is_null( $cart ) || ! is_a( $cart, 'WC_Cart' ) ) {
            $cart = WC()->cart;
        }
        
        $checkbox = WC()->session->get( 'checkbox_checked' );

        $cartRef = $cart->get_cart_hash();
        $cartTotal = round($this->get_cart_subtotal_with_only_shippable_items($cart), 2);
        $currency = get_woocommerce_currency();
        $cartItems = $this->get_cart_shippable_items($cart);
        $route_insurance_quote = $this->routeapp_get_quote_from_api($cartRef, $cartTotal, $currency, $cartItems);
		$this->route_insurance_data = $route_insurance_quote;

        $route_insurance_amount = false;
        //only add fee if the quote is covered by customer and has a valid amount
        if (isset($route_insurance_quote->premium->amount) &&
            (isset($route_insurance_quote->payment_responsible->type)) && $route_insurance_quote->payment_responsible->type==self::ALLOWED_QUOTE_TYPE) {
            $route_insurance_amount = $route_insurance_quote->premium->amount;
        }
       
        if (is_null($checkbox) && 
            isset($route_insurance_quote->payment_responsible) && 
            property_exists($route_insurance_quote->payment_responsible, 'ToggleState')) {
            $checkbox = $route_insurance_quote->payment_responsible->ToggleState;
            WC()->session->set('checkbox_checked', $checkbox);
        }

        Route_Setup::set_setup_check_widget(Route_Setup::SETUP_CHECK_WIDGET_PHP);
        if ( (is_null($checkbox) && is_checkout() || ($checkbox === TRUE || $checkbox === 1)) && $route_insurance_amount > 0 && $this->routeapp_is_shipping_method_allowed()) {
            $cart->add_fee( $this->routeapp_get_insurance_label(),
                $route_insurance_amount,
                $this->routeapp_get_fee_taxable(),
                $this->routeapp_get_taxable_class());
        }
    }

    /**
     * Create checkout
     * @since    1.0.0
     *
     */
    public function checkout_route_insurance() {
        echo do_shortcode('[route]');
    }

    public function order_updates_route_tracking($order) {
        if ($this->routeapp_route_show_order_updates()) {
            echo $this->route_show_thankyoupage_asset($order);
        }
    }

    public function route_show_thankyoupage_asset($order) {
        $output = '';

        $site = parse_url(get_site_url(get_current_blog_id()), PHP_URL_HOST);
        $response = Routeapp_API_Client::getInstance()->asset_settings($site);

        if (is_wp_error($response) ||
            (isset($response['response']['code']) && $response['response']['code'] == 404) ||
            is_null($response['body'])) {
            return $output;
        } else {

            $body = json_decode($response['body']);

            if (!is_null($body) &&
                !is_null($body->asset_settings) &&
                !is_null($body->asset_settings->asset_live) &&
                $body->asset_settings->asset_live) {
                $raw_html = $body->asset_settings->asset_content->raw_html;
                $css = $body->asset_settings->asset_content->css_url;
                $js = isset($body->asset_settings->asset_content->js_url) ? $body->asset_settings->asset_content->js_url : '';
                $jsPath = '';
                if (!empty($js)) {
                    $jsPath = '<script type="text/javascript" src="' . $js . '"></script>';
                }
                $output = '<link rel="stylesheet" href="' . $css . '" type="text/css" />' .
                    $jsPath .
                    '<section class=woocommerce-order-updates>' .
                    '<h2 class="woocommerce-order-updates__title">Order Updates</h2>' .
                    '</section>' .
                    '<div><span class="os-order-number" style="display:none;">'.$order->get_id().'</span></div>'.
                    '<div>' .
                    $raw_html .
                    '</div>' .
                    '<div>&nbsp;</div>';

                wp_enqueue_script($this->plugin_name . '-analytics', 'https://cdn.routeapp.io/route-analytics/route-analytics.js', array(), $this->version, false);
                wp_enqueue_script($this->plugin_name, plugin_dir_url(__FILE__) . 'js/routeapp-analytics.js', array('jquery'), $this->version, false);
            }
        }

        return $output;
    }

    public function route_widget_shortcode()
    {
        if(is_admin() || empty($this->routeapp_get_public_token())) return;

        $custom_env = getenv('ROUTEAPP_ENVIRONMENT_ENDPOINT');
        if (is_null($custom_env) || !$custom_env) {
            $custom_env = isset($_SERVER['ROUTEAPP_ENVIRONMENT_ENDPOINT']) ? $_SERVER['ROUTEAPP_ENVIRONMENT_ENDPOINT'] : '';
        }
        $custom_env = empty($custom_env) ? 'production' : $custom_env;

        wp_enqueue_script($this->plugin_name . '-widget', 'https://protection-widget.route.com/route-protection-widget.js', array(), $this->version, false);

		$amplitude_key = self::AMPLITUDE_API_KEY[$custom_env];

	    // Load Amplitude SDK (standard version, not snippet)
	    wp_enqueue_script('amplitude-js', "https://cdn.amplitude.com/script/$amplitude_key.js", array(), null, false);

	    // Enqueue Amplitude Analytics module (depends on jQuery and Amplitude SDK)
	    wp_enqueue_script($this->plugin_name . '-amplitude-analytics', plugin_dir_url(__FILE__) . 'js/amplitude-analytics.js', array('jquery', 'amplitude-js'), $this->version, false);

	    // Main script depends on amplitude-analytics when available
	    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);

	    /**
	     * Calculate total Route fee
	     * Uses the premium amount from route insurance data if available,
	     * otherwise defaults to 0.98 as a fallback fee amount
	     */
	    $totalRouteFee = (is_object($this->route_insurance_data) && isset($this->route_insurance_data->premium->amount))
		    ? (float) $this->route_insurance_data->premium->amount
		    : 0.98;

	    // Pass cart data to JavaScript - send to amplitude-analytics.js
	    wp_localize_script($this->plugin_name . '-amplitude-analytics', 'cartData', array(
		    'cartId' => WC()->cart->get_cart_hash(),
		    'amplitudeKey' => $amplitude_key,
		    'widgetId' => uniqid('widget-', true),
		    'subtotal' => round($this->get_cart_subtotal_with_only_shippable_items(WC()->cart), 2),
		    'totalRouteFee' => $totalRouteFee,
		    'environment' => $custom_env,
		    'currency' => get_woocommerce_currency(),
		    'merchantId' => $this->routeapp_api_client->get_merchant_id(),
	    ));

        wp_enqueue_style($this->plugin_name . '-widget', plugin_dir_url(__FILE__) . 'css/routeapp-widget.css', array(), $this->version, false);

        global $wp;
        $parsedUrl = parse_url(home_url( $wp->request));

        switch($custom_env) {
            case 'stage':
                $custom_env = 'Route.Environment.Dev';
                break;
            default:
                $custom_env = 'Route.Environment.Production';
                break;
        }
        $checkbox = WC()->session->get( 'checkbox_checked' );
        $checkbox = $checkbox ? 'Route.Coverage.ActiveByDefault' : 'Route.Coverage.InactiveByDefault';

        $divId = is_checkout() ? 'route-checkout-radio' : 'route-cart-radio';

        $return = '<div id="'.$divId.'">' .
            '<input type="hidden" value="'. get_woocommerce_currency() .'" class="routeapp-currency">'.
            '<input type="hidden" value="'. round($this->get_cart_subtotal_with_only_shippable_items(WC()->cart), 2) .'" class="routeapp-subtotal">'.
            '<input type="hidden" value="' . admin_url('admin-ajax.php') . '" class="routeapp-ajax-url">'.
            '<input type="hidden" value="' . $parsedUrl['host'] . '" class="routeapp-store-domain">'.
            '<input type="hidden" value="' . get_option('blogname') . '" class="routeapp-store-name">'.
            '<input type="hidden" value="' . $this->routeapp_api_client->get_merchant_id() . '" class="routeapp-merchant-id">'.
            '<input type="hidden" value="' . $custom_env . '" class="routeapp-env">'.
            '<input type="hidden" value="' . $checkbox .'" class="routeapp-checkbox-cookie">';

        //check if shipping method is allowed
        $cart = WC()->cart;
        $shipping_method = $cart->calculate_shipping();
        if (isset($shipping_method[0]->method_id)) {
            $shipping_method = $shipping_method[0]->method_id;
            $show_widget =  $this->routeapp_is_shipping_method_allowed($shipping_method);
            if (!$show_widget) {
                $return .= '<input type="hidden" value="true" class="routeapp-invalid-shipping-method">';
            }
        }
        return $return . '<div id="RouteWidget" class="route-div"></div></div>' ;
    }


    public function wc_routeapp_ajaxurl() {
        echo '<script type="text/javascript">' .
            'var wc_routeapp_ajaxurl = "' . admin_url('admin-ajax.php') . '"' .
            '</script>';
    }

    /**
     * get amount from RouteApp API
     *
     * @param $cartRef
     * @param $cartTotal
     * @param $currency
     * @param $cartItems
     * @return false|mixed
     */
    public function routeapp_get_quote_from_api($cartRef, $cartTotal, $currency, $cartItems) {
        if (!$cartRef) return false;
        if (!$cartTotal) return false;
        $currency = !$currency ? get_woocommerce_currency() : $currency;

        $response = $this->routeapp_api_client->get_quote($cartRef, $cartTotal, $currency, $cartItems);

        if (is_wp_error($response)) {
            return false;
        } else {
            if (isset($response['response']['code']) && $response['response']['code'] == 401) {
                return false;
            }
            $price_data = json_decode($response['body']);
            Route_Setup::set_setup_check_widget(Route_Setup::SETUP_CHECK_WIDGET_JS);
            return $price_data;
        }
    }

    /**
     * create order data sending for API
     * @since    1.0.0
     * @param  integer $order_id
     */
    public function create_order_data_api($order_id) {

        $order = wc_get_order( $order_id );

        if ( class_exists('Automattic\WooCommerce\Utilities\OrderUtil')
            && OrderUtil::custom_orders_table_usage_is_enabled() ) {
            // HPOS usage is enabled.
            $quote = $order->get_meta('_routeapp_quote');
        } else {
            // Traditional CPT-based orders are in use.
            $quote = get_post_meta($order->get_id(), '_routeapp_quote', true);
        }

        $insurance_selected = false;
        $insurance_amount = false;

        foreach ($order->get_items('fee') as $fee) {
            if ($fee->get_name() === $this->routeapp_get_insurance_label()) {
                $insurance_selected = true;
                $insurance_amount = $fee->get_amount();
                if ( class_exists('Automattic\WooCommerce\Utilities\OrderUtil')
                    && OrderUtil::custom_orders_table_usage_is_enabled() ) {
                    // HPOS usage is enabled.
                    $order->update_meta_data('_routeapp_route_charge', $fee->get_amount() );
                    $order->save();
                } else {
                    // Traditional CPT-based orders are in use.
                    update_post_meta( $order->get_id(), '_routeapp_route_charge', $fee->get_amount() );
                }
            }
        }
        $quoteId = 'Invalid';
        if ($quote) {
            $quoteDecoded = json_decode($quote);
            $quoteId = $quoteDecoded->id;
        }

        $destination =  array(
            "first_name"		=> $order->get_billing_first_name(),
            "last_name"		    => $order->get_billing_last_name(),
            "street_address1"   => $order->get_shipping_address_1(),
            "street_address2"	=> $order->get_shipping_address_2(),
            "city"			    => $order->get_shipping_city(),
            "province"		    => $order->get_shipping_state(),
            "zip"			    => $order->get_shipping_postcode(),
            "country_code"	    => $order->get_shipping_country(),
        );
        $customerDetails = array(
            "first_name"	=> $order->get_billing_first_name(),
            "last_name"		=> $order->get_billing_last_name(),
            "email"			=> $order->get_billing_email(),
            "phone"			=> $order->get_billing_phone()
        );
        $order_data = $order->get_data();
        $is_shipping_method_allowed = $this->routeapp_is_shipping_method_allowed(false, $order);

        $itemdata = array();

        foreach ($order->get_items() as $item) {
            ## Using WC_Order_Item_Product methods ##
            $product = $item->get_product(); // Get the WC_Product object

            if ($product instanceof WC_Product) {

                $isProductVirtual = (bool)$product->is_virtual();

                $arrItem = array(
                    "delivery_method" => $isProductVirtual ? 'digital' :
                        ($is_shipping_method_allowed ? 'ship_to_home' : 'ship_to_store'),
                    "is_insured" => $isProductVirtual ? false :
                        ($is_shipping_method_allowed ? true : false),
                    "source_product_id" => $product->get_id(),
                    "source_id" => strval($item->get_id()),
                    "sku" => $product->get_sku(),
                    "name" => $product->get_title(),
                    "price" => $product->get_price(),
                    "quantity" => $item->get_quantity(),
                    "image_url" =>  (wp_get_attachment_url($product->get_image_id())) ? wp_get_attachment_url($product->get_image_id()) : null,
                    "upc" => ""// WooCommerce doesn’t provide UPC by default
                );

                array_push($itemdata, $arrItem);
            }

        }

        if ( class_exists('Automattic\WooCommerce\Utilities\OrderUtil')
            && OrderUtil::custom_orders_table_usage_is_enabled() ) {
            // HPOS usage is enabled.
            $order->update_meta_data('_routeapp_route_protection', $insurance_selected );
            $order->update_meta_data('_routeapp_version', ROUTEAPP_VERSION );

            $order->save();
        } else {
            // Traditional CPT-based orders are in use.
            update_post_meta( $order->get_id(), '_routeapp_route_protection', $insurance_selected );
            update_post_meta( $order->get_id(), '_routeapp_version', ROUTEAPP_VERSION );
        }

        $orderData = array(
            'quote_id' => $quoteId,
            'source_order_id' => $order->get_id(),
            'source_order_number' => $order->get_id(),
            'subtotal' => $order->get_subtotal(),
            'order_total' => $order->get_total(),
            'shipping_total' => $order->get_shipping_total(),
            'discounts_total' => $order->get_discount_total(),
            'source_created_on' => date(DATE_ATOM, strtotime($order->get_date_created())),
            'source_updated_on' => date(DATE_ATOM, strtotime($order->get_date_modified())),
            'taxes' => $order_data['total_tax'],
            'insurance_selected' => $insurance_selected,
            'customer_details' => $customerDetails,
            'shipping_details' => $destination,
            'line_items' => $itemdata,
            'currency' => $order->get_currency(),
            'amount_covered' => $this->get_order_subtotal($order),
            'origin' => 'woocommerce-module',
        );
        //if customer covers the order, pass the paid to insure with Route Fee value
        if ($insurance_selected && $insurance_amount) {
            $orderData['paid_to_insure'] = $insurance_amount;
        }

        return $orderData;
    }

    /**
     * Update tracking order data sending for API
     * @since    1.0.0
     * @param  integer $item_id
     * @param  object $order
     * @param  string $post
     */
    public function routeapp_update_tracking_order_api($item_id, $order, $post) {
        if (is_null($order) || get_post_type($order) !== 'shop_order' || $post == '_edit_lock') {
            return false;
        }
        $this->routeapp_shipment_tracking->update($order, $this);
    }

    /**
     * Update tracking order data sending when HPOS is enable
     * @param  object $order
     * @param  object $data_store
     */
    public function routeapp_update_tracking_order_api_HPOS($order, $data_store) {
        static $already_executed = false;

        if ($already_executed) {
            return false; // Prevent recursion
        }

        $already_executed = true;

        // Ensure the $order object is valid and of type 'shop_order'
        if (!$order || $order->get_type() !== 'shop_order') {
            return false;
        }

        $this->routeapp_shipment_tracking->update($order->get_id(), $this);
    }

    /**
     * Update tracking order item data sending for API
     * @since    1.0.0
     * @param  integer $order_id
     * @param  object $post
     * @param  boolean $update
     */
    public function routeapp_update_tracking_order_item_api($item_id, $order_item_id, $post) {
        if (is_null($order_item_id) ||
            !is_admin() ||
            $this->_isShippingTrackingCall($post)
        ) {
            return false;
        }
        $this->routeapp_shipment_tracking->update($order_item_id, $this);
    }

    /**
     * Check if the post comes for shipment tracking
     * @param $post
     * @return bool
     */
    private function _isShippingTrackingCall($post) {
        $isNotShippingTracking = $post != '_wc_shipment_tracking_items' &&
            $post != '_bst_tracking_number' &&
            $post != 'mimo_tracking_number' &&
            $post != '_aftership_tracking_number' &&
            $post != 'ywot_tracking_code' &&
            $post != 'ph_shipment_tracking_shipping_service' &&
            $post != 'ph_shipment_tracking_ids' &&
            $post != 'usps_evs_label_details_array' &&
            $post != '_order_trackno' &&
            $post != '_order_trackno1' &&
            $post != '_order_trackno2' &&
            $post != '_order_trackno3' &&
            $post != '_order_trackno4' &&
            $post != '_vi_wot_order_item_tracking_data' &&
            $post != 'wc_connect_labels';

        return apply_filters('routeapp_meta_key_is_not_shipping_related', $isNotShippingTracking, $post);
    }


    public function routeapp_update_merchant_api( $merchant_id, $merchantData ) {
        if ( is_null($merchant_id) ) {
            return false;
        }

        $response = $this->routeapp_api_client->update_merchant( $merchant_id, $merchantData );
        if ( is_wp_error($response) ) {
            return false;
        }

        return true;
    }


    /**
     * Updates Route-related metadata to the order after it is placed in draft status.
     *
     * This method is triggered when an order is created in draft status and is responsible for
     * adding meta fields with the list Route sync status.
     * This prevents incorrect synchronization of draft orders.
     *
     * @param int $order_id The ID of the WooCommerce order.
     */
    public function routeapp_save_order_status_to_order( $order_id ) {
        if ( ! $order_id || ! WC()->session ) {
            return;
        }

        try {
            $order = wc_get_order( $order_id );

            if ( ! $order ) {
                throw new Exception( "Order with ID {$order_id} could not be retrieved." );
            }
	        $is_hpos = class_exists('Automattic\WooCommerce\Utilities\OrderUtil')
	                   && OrderUtil::custom_orders_table_usage_is_enabled();

	        $acceptedOrderStatuses = get_option('routeapp_included_order_statuses') ? get_option('routeapp_included_order_statuses') : [];
	        $acceptedCanceledStatuses = get_option('routeapp_cancel_order_statuses') ? get_option('routeapp_cancel_order_statuses') : [];

	        $updated = false;
	        $updated |= $this->updateRouteOrderMetaData( $order, $is_hpos, '_routeapp_included_order_statuses', $acceptedOrderStatuses );
	        $updated |= $this->updateRouteOrderMetaData( $order, $is_hpos, '_routeapp_cancel_order_statuses', $acceptedCanceledStatuses );

	        if ($updated && $is_hpos) {
		        $order->save();
	        }
        } catch ( Exception $e ) {
            error_log( "An error occurred while updating Route order status meta fields for order ID {$order_id}: " . $e->getMessage() );
        }
    }


    /**
     * Save into order meta_data the quote from the last request made on session
     *
     * @param $order_id
     */
    public function routeapp_save_quote_to_order($order_id )
    {
        if (!$order_id && !WC()->session) {
            return;
        }
        $order = wc_get_order($order_id);
	    if (!$order) {
		    return;
	    }
	    //MSS-6248, Support High-Performance Order Storage (HPOS); fall back to legacy order storage when disabled.
	    $is_hpos = class_exists('Automattic\WooCommerce\Utilities\OrderUtil')
	               && OrderUtil::custom_orders_table_usage_is_enabled();

	    $updated = false;

        if ($is_hpos) {
            $quote = $order->get_meta('_routeapp_quote');
        } else {
            // Traditional CPT-based orders are in use.
            $quote = get_post_meta($order->get_id(), '_routeapp_quote', true);
        }

		if (!$quote) {
            $cartRef = $order->get_cart_hash();
            if (!$cartRef) {
                //create uniq ID for backend orders
                $cartRef = uniqid();
            }
            $main_key = $this->routeapp_api_client->get_cache_api_session_key().'-'.$cartRef;
            $key = $main_key.'-latest';
            if (WC()->session) {
                $cached = WC()->session->get($key);
                if (isset($cached) && is_array($cached) && isset($cached['body'])) {

                    $fees = $order->get_items('fee');
                    if (!empty($fees)) {
                        foreach ($fees as $fee) {
                            if ($fee->get_name() === $this->routeapp_get_insurance_label()) {
                                $insurance_amount = $fee->get_amount();
	                            $updated |= $this->updateRouteOrderMetaData( $order, $is_hpos, '_routeapp_route_charge', $insurance_amount );
	                            $updated |= $this->updateRouteOrderMetaData( $order, $is_hpos, '_routeapp_route_protection', true );
                            }
                        }
                    } else {
                        try {
                            // Check if WC()->cart exists and is accessible
                            if (class_exists('WC_Cart') && isset(WC()->cart) && WC()->cart instanceof WC_Cart) {
                                // Fallback to WC()->cart->get_fees() if no fees are present in the order
                                $cart_fees = WC()->cart->get_fees();
                                if (!empty($cart_fees)) {
                                    foreach ($cart_fees as $cart_fee) {
                                        if ($cart_fee->name === $this->routeapp_get_insurance_label()) {
                                            $insurance_amount = $cart_fee->amount; // Correct method for cart fee amount
	                                        $updated |= $this->updateRouteOrderMetaData( $order, $is_hpos, '_routeapp_route_charge', $insurance_amount );
	                                        $updated |= $this->updateRouteOrderMetaData( $order, $is_hpos, '_routeapp_route_protection', true );
                                        }
                                    }
                                }
                            }
                        } catch (Exception $e) {
                            // Log the exception and ensure the application flow continues
                            error_log("An error occurred while processing cart fees: " . $e->getMessage());
                        }

                    }
	                $updated |= $this->updateRouteOrderMetaData( $order, $is_hpos, '_routeapp_quote', $cached['body'] );

                    //clear cache!
                    WC()->session->__unset($main_key);
                    WC()->session->__unset($key);
                }
            }
        }

		//Add orders status meta data
	    $acceptedOrderStatuses = get_option('routeapp_included_order_statuses') ? get_option('routeapp_included_order_statuses') : [];
	    $acceptedCanceledStatuses = get_option('routeapp_cancel_order_statuses') ? get_option('routeapp_cancel_order_statuses') : [];

	    $updated |= $this->updateRouteOrderMetaData( $order, $is_hpos, '_routeapp_included_order_statuses', $acceptedOrderStatuses );
	    $updated |= $this->updateRouteOrderMetaData( $order, $is_hpos, '_routeapp_cancel_order_statuses', $acceptedCanceledStatuses );

		//MSS-6533: Add ship to store flag and excluded shipping methods meta data to the order
	    $updated |= $this->updateRouteOrderMetaData( $order, $is_hpos,'_routeapp_order_ship_to_store', $this->routeapp_resolve_ship_to_store_flag( $order ) );
	    $updated |= $this->updateRouteOrderMetaData( $order, $is_hpos, '_routeapp_excluded_shipping_methods', $this->routeapp_get_excluded_shipping_methods());

	    // Save order if there is any update
	    if ($updated && $is_hpos) {
		    $order->save();
	    }
    }

    /**
     * Mark order on woocommerce thank you hook
     * @since    1.0.0
     * @param  integer $order_id
     *
     */
    public function routeapp_mark_order($order_id)
    {
        if (!$order_id) {
            return;
        }

        $order = wc_get_order($order_id);

        //order cancel action will be handled by webhook event
        if ($order->get_status() === 'cancelled') {
            return;
        }

        // Init the setup for readiness report
        $this->_setup_readiness_report_check();

        if (WC()->session) {
            WC()->session->__unset('checkbox_checked');
        }
    }

    /**
     * Check order note (look for shipment tracking data on it)
     * @param $noteId id
     * @return mixed
     */
    public function check_order_note_by_note_id($noteId)
    {
        $comment = get_comment($noteId);
        $order = wc_get_order($comment->comment_post_ID);
        if ($order && $order->get_id()) {
            $this->routeapp_shipment_tracking->parse_order_notes($order->get_id(), $this);
        }
    }

    /**
     * Check order note (look for shipment tracking data on it)
     * @param $response WP_REST_Response
     * @return mixed
     */
    public function check_order_note($response)
    {
        $note = $response->get_data();
        $comment = get_comment($note['id']);
        $order = wc_get_order($comment->comment_post_ID);
        if ($order && $order->get_id()) {
            $this->routeapp_shipment_tracking->parse_order_notes($order->get_id(), $this);
        }
        return $response;
    }
    /**
     * Cancel the tracking order sending for API
     * @since    1.0.0
     * @param  integer $order_id
     * @param  integer $tracking_number
     * @param  array $product_ids
     */

    public function routeapp_cancel_tracking_order( $order_id, $tracking_number, $product_ids ) {
        if ( is_null($order_id) ) {
            return false;
        }
        $this->routeapp_shipment_tracking->cancel($order_id, $tracking_number, $product_ids, $this);
    }

    /**
     * Create user at Route
     * @param $data
     * @return array|bool|mixed|object
     */
    public function routeapp_create_user($data){
        $response = $this->routeapp_api_client->create_user($data);

        $this->routeapp_last_request = $response;

        if ( is_wp_error( $response ) ) {
            return false;
        } else {
            $body = json_decode($response['body']);

            if ($response['response']['code'] != self::HTTP_CREATED_CODE) {
                return false;
            }

            return $body;
        }
    }

    /**
     * Create user at Route
     *
     * @param $username
     * @param $password
     *
     * @return array|bool|mixed|object
     */
    public function routeapp_user_login($username, $password){
        $response = $this->routeapp_api_client->login_user($username, $password);

        $this->routeapp_last_request = $response;

        if ( is_wp_error( $response ) ) {
            return false;
        } else {
            $body = json_decode($response['body']);

            if ($response['response']['code'] != self::HTTP_SUCCESS_CODE) {
                return false;
            }

            return $body;
        }
    }

    /**
     * Creates merchant at Route
     *
     * @param $data
     * @return array|bool|mixed|object
     */
    public function routeapp_create_merchant($data){

        $response = $this->routeapp_api_client->create_merchant($data);
        $this->routeapp_last_request = $response;

        if ( is_wp_error( $response ) ) {
            return false;
        } else {
            $body = json_decode($response['body']);

            if ($response['response']['code'] != self::HTTP_CREATED_CODE && $response['response']['code'] != self::HTTP_SUCCESS_CODE) {
                return false;
            }

            return $body;
        }
    }

    /**
     * Creates merchant at Route
     *
     * @param $data
     * @return array|bool|mixed|object
     */
    public function routeapp_get_merchants(){

        $response = $this->routeapp_api_client->get_merchants();
        $this->routeapp_last_request = $response;

        if ( is_wp_error( $response ) ) {
            return false;
        } else {
            $body = json_decode($response['body']);

            if ($response['response']['code'] != self::HTTP_CREATED_CODE && $response['response']['code'] != self::HTTP_SUCCESS_CODE) {
                return false;
            }

            return $body;
        }
    }

    /**
     * Activate user account
     *
     * @param $email
     *
     * @return array|bool|mixed|object
     */
    public function routeapp_activate_account($email){

        $response = $this->routeapp_api_client->activate_account(['email' => $email]);
        $this->routeapp_last_request = $response;

        if ( is_wp_error( $response ) ) {
            return false;
        } else {
            $body = json_decode($response['body']);

            if ($response['response']['code'] != self::HTTP_CREATED_CODE && $response['response']['code'] != self::HTTP_SUCCESS_CODE) {
                return false;
            }

            return $body;
        }
    }

    public function routeapp_is_shipping_method_allowed($shipping_method=false, $order=false) {

        if ($shipping_method || $order) {
            if ($shipping_method) {
                $shipping_method = explode(':', $shipping_method);
                $shipping_method = $shipping_method[0];
            }
            if ($order) {
                $shipping_method = @array_shift($order->get_shipping_methods());
                if(isset($shipping_method['method_id']))
                {
                    $shipping_method = $shipping_method['method_id'];
                } else {
                    return false;
                }
            }
            $excludedShippingMethods = $this->routeapp_get_excluded_shipping_methods();
            if (is_array($excludedShippingMethods) && in_array($shipping_method, $excludedShippingMethods)) {
                return false;
            }
        } else {
            if (WC()->session->get('shipping_for_package_0')) {
                foreach( WC()->session->get('shipping_for_package_0')['rates'] as $method_id => $rate ){
                    if( WC()->session->get('chosen_shipping_methods')[0] == $method_id ){
                        $shippingMethod = explode(':', $rate->id);
                        $shippingMethod = $shippingMethod[0];
                        $excludedShippingMethods = $this->routeapp_get_excluded_shipping_methods();
                        if (in_array($shippingMethod, $excludedShippingMethods)) {
                            return false;
                        }
                    }
                }
            }
        }
        return true;
    }

    public function routeapp_get_insurance_label() {
        return __(self::ROUTE_LABEL, 'routeapp');
    }

    public function routeapp_is_opt_in() {
        $merchant = $this->routeapp_api_client->get_merchant();
        return isset($merchant->merchant_preferences->opt_in) && $merchant->merchant_preferences->opt_in == true;
    }

    public function routeapp_get_public_token() {
        return get_option('routeapp_public_token');
    }

    public static function routeapp_get_taxable_class() {
        return get_option('routeapp_taxable_class');
    }

    public static function routeapp_get_checkout_hook() {
        return get_option('routeapp_checkout_hook') ? get_option('routeapp_checkout_hook') : self::DEFAULT_CHECKOUT_WEBHOOK;
    }

    public static function routeapp_get_checkout_hook_options() {
        return [
            'woocommerce_before_checkout_form' => 'woocommerce_before_checkout_form',
            'woocommerce_checkout_before_customer_details' => 'woocommerce_checkout_before_customer_details',
            'woocommerce_checkout_billing' => 'woocommerce_checkout_billing',
            'woocommerce_before_checkout_billing_form' => 'woocommerce_before_checkout_billing_form',
            'woocommerce_after_checkout_billing_form' => 'woocommerce_after_checkout_billing_form',
            'woocommerce_before_checkout_registration_form' => 'woocommerce_before_checkout_registration_form',
            'woocommerce_after_checkout_registration_form' => 'woocommerce_after_checkout_registration_form',
            'woocommerce_checkout_shipping' => 'woocommerce_checkout_shipping',
            'woocommerce_before_checkout_shipping_form' => 'woocommerce_before_checkout_shipping_form',
            'woocommerce_after_checkout_shipping_form' => 'woocommerce_after_checkout_shipping_form',
            'woocommerce_before_order_notes' => 'woocommerce_before_order_notes',
            'woocommerce_after_order_notes' => 'woocommerce_after_order_notes',
            'woocommerce_checkout_after_customer_details' => 'woocommerce_checkout_after_customer_details',
            'woocommerce_checkout_before_order_review_heading' => 'woocommerce_checkout_before_order_review_heading',
            'woocommerce_checkout_order_review' => 'woocommerce_checkout_order_review',
            'woocommerce_checkout_before_order_review' => 'woocommerce_checkout_before_order_review',
            'woocommerce_review_order_before_cart_contents' => 'woocommerce_review_order_before_cart_contents',
            'woocommerce_review_order_after_cart_contents' => 'woocommerce_review_order_after_cart_contents',
            'woocommerce_review_order_before_shipping' => 'woocommerce_review_order_before_shipping',
            'woocommerce_review_order_after_shipping' => 'woocommerce_review_order_after_shipping',
            'woocommerce_review_order_before_order_total' => 'woocommerce_review_order_before_order_total',
            'woocommerce_review_order_after_order_total' => 'woocommerce_review_order_after_order_total',
            'woocommerce_review_order_before_payment' => 'woocommerce_review_order_before_payment',
            'woocommerce_review_order_before_submit' => 'woocommerce_review_order_before_submit',
            'woocommerce_review_order_after_submit' => 'woocommerce_review_order_after_submit',
            'woocommerce_review_order_after_payment' => 'woocommerce_review_order_after_payment',
            'woocommerce_checkout_after_order_review' => 'woocommerce_checkout_after_order_review',
            'woocommerce_after_checkout_form' => 'woocommerce_after_checkout_form'
        ];
    }

    public static  function routeapp_get_fee_taxable() {
        if (get_option('routeapp_route_fee_taxable') == 'yes') return true;
        return false;
    }

    public function routeapp_get_excluded_shipping_methods() {
        return get_option('routeapp_excluded_shipping_methods') ?
            get_option('routeapp_excluded_shipping_methods') :
            [];
    }

    public function routeapp_get_secret_token() {
        return get_option('routeapp_secret_token');
    }

    public function routeapp_route_enabled_extra_columns() {
        return get_option('routeapp_route_enable_extra_columns') !== "no";
    }

    public function routeapp_get_fresh_installed(){
        return get_option('routeapp_route_fresh_installed');
    }

    public function routeapp_route_show_order_updates() {
        return get_option('routeapp_route_show_order_updates', 'yes') == 'yes';
    }

    private static function routeapp_get_failed_registration(){
        return get_option('routeapp_failed_registration');
    }

    /**
     * If it's installation has failed at user step creation step
     *
     * @return bool
     */
    public function has_user_creation_failed(){
        return self::routeapp_get_failed_registration() == Route_Setup::FAILED_REGISTRATION_STEP_USER;
    }

    /**
     * If it's installation has failed at user step creation step due to conflict
     *
     * @return bool
     */
    public function has_user_creation_conflicted(){
        return !Route_Setup::has_valid_merchant_tokens() && (self::routeapp_get_failed_registration() == Route_Setup::FAILED_REGISTRATION_STEP_USER_DUPLICATED || self::routeapp_get_failed_registration() == Route_Setup::FAILED_REGISTRATION_STEP_USER_LOGIN_FAILED);
    }

    /**
     * If it's installation has failed at Merchant creation step
     *
     * @return bool
     */
    public function has_merchant_creation_failed(){
        return self::routeapp_get_failed_registration() == Route_Setup::FAILED_REGISTRATION_STEP_MERCHANT  && !Route_Setup::has_valid_merchant_tokens();
    }
    /**
     * If it's installation has failed at Merchant creation step
     *
     * @return bool
     */
    public function has_merchant_creation_conflicted(){
        return self::routeapp_get_failed_registration() == Route_Setup::FAILED_REGISTRATION_STEP_MERCHANT_DUPLICATED && !Route_Setup::has_valid_merchant_tokens();
    }

    /**
     * If it's installation has failed at Merchant creation step
     *
     * @return bool
     */
    public function successful_registration(){
        return self::routeapp_get_failed_registration() === Route_Setup::REGISTRATION_FIXED || self::routeapp_get_failed_registration() === Route_Setup::REGISTRATION_STEP_USER_LOGIN_SUCCESS;
    }

    /**
     * Get registration step at setup flow
     * @return mixed
     */
    private static function registration_step(){
        return get_option(Route_Setup::REGISTRATION_STEP);
    }

    /**
     * Check if at the merchant create flow it was already redirected
     * @return bool
     */
    public static function was_redirected(){
        return self::registration_step() == Route_Setup::REDIRECTED;
    }

    /**
     * Set it as redirected to Route after registration is complete
     * @return void
     */
    public function set_as_redirected(){
        update_option(Route_Setup::REGISTRATION_STEP, Route_Setup::REDIRECTED);
    }

    public function routeapp_register_status_route() {
        register_rest_route( 'route', 'status', array(
                'methods' => 'GET',
                'callback' => function() {
                    return rest_ensure_response( $this->routeapp_build_status_response() );
                },
                'permission_callback' => '__return_true'
            )
        );
    }

    /**
     * Build the associative array returned by GET /wp-json/route/status.
     *
     * Kept separate from the REST callback so tests can exercise the response
     * shape directly without REST dispatch. Never returns raw credentials —
     * `routeapp_user_token` and `routeapp_user_id` are intentionally excluded;
     * `public_token`, `secret_key`, and `routeapp_store_hash` are masked via
     * {@see Routeapp_Public::_encodeToken()}. `routeapp_merchant_id` is
     * exposed raw — it is the merchant identifier Route support matches
     * against, and already appears in outbound webhook URLs.
     *
     * @return array Flat associative array keyed by setting name.
     */
    private function routeapp_build_status_response() {
        $store_hash = Route_Setup::get_store_hash();
        $widget_install_ts = get_option( Route_Setup::SETUP_CHECK_WIDGET_INSTALLATION_DATE, null );
        $widget_install_human = null;
        if ( ! empty( $widget_install_ts ) && is_numeric( $widget_install_ts ) ) {
            $widget_install_ts = (int) $widget_install_ts;
            $widget_install_human = wp_date( 'Y-m-d H:i:s', $widget_install_ts );
        } else {
            $widget_install_ts = null;
        }

        return array(
            'version' => ROUTEAPP_VERSION,
            'public_token' => !empty($this->routeapp_get_public_token()) ?
                $this->_encodeToken($this->routeapp_get_public_token()) :
                false,
            'secret_key' => !empty($this->routeapp_get_secret_token()) ?
                $this->_encodeToken($this->routeapp_get_secret_token()) :
                false,
            'route_insurance_label' => $this->routeapp_get_insurance_label(),
            'extra_columns_enabled' => $this->routeapp_route_enabled_extra_columns(),
            'failed_registration' => $this->routeapp_get_failed_registration(),
            'base_currency' => get_woocommerce_currency(),
            'date' => array(
                'date' => date('Y-m-d H:i:s'),
                'timezone_type' => date('P'),
                'timezone' => date_default_timezone_get()
            ),
            'routeapp_route_fee_taxable' => $this->routeapp_get_fee_taxable(),
            'routeapp_taxable_class' => $this->routeapp_get_taxable_class(),
            'routeapp_excluded_shipping_methods' => $this->routeapp_get_excluded_shipping_methods(),
            'routeapp_route_show_order_updates' => $this->routeapp_route_show_order_updates(),
            'routeapp_checkout_hook' => $this->routeapp_get_checkout_hook(),
            'routeapp_have_webhook_secret' => $this->_routeapp_have_webhook_secret(),
            'routeapp_included_order_statuses' => get_option( 'routeapp_included_order_statuses', array() ),
            'routeapp_cancel_order_statuses' => get_option( 'routeapp_cancel_order_statuses', array() ),
            'routeapp_external_tracking_providers' => get_option( 'routeapp_external_tracking_providers', '' ),
            'routeapp_order_recover_from' => get_option( 'routeapp_order_recover_from', '' ),
            'routeapp_order_recover_to' => get_option( 'routeapp_order_recover_to', '' ),
            'routeapp_order_recover_reconcile_backend' => get_option( 'routeapp_order_recover_reconcile_backend', 'no' ),
            'routeapp_merchant_id' => get_option( Routeapp_API_Client::ROUTEAPP_MERCHANT_ID, '' ),
            'routeapp_store_hash' => ! empty( $store_hash ) ? $this->_encodeToken( $store_hash ) : false,
            'routeapp_last_install_date' => get_option( '_routeapp_last_install_date', '' ),
            'routeapp_webhooks_created' => (bool) get_option( '_routeapp_webhooks_created', false ),
            'routeapp_route_registration_step' => get_option( Route_Setup::REGISTRATION_STEP, '' ),
            'routeapp_user_conflict_auto_redirect_done' => (bool) get_option( Route_Setup::USER_CONFLICT_AUTO_REDIRECT_DONE_OPTION, false ),
            'routeapp_route_fresh_installed' => $this->routeapp_get_fresh_installed(),
            'route_setup_check_widget_installation_date' => $widget_install_ts,
            'route_setup_check_widget_installation_date_human' => $widget_install_human,
            'route_setup_check_widget_php' => (bool) get_option( Route_Setup::SETUP_CHECK_WIDGET_PHP, false ),
            'route_setup_check_widget_js' => (bool) get_option( Route_Setup::SETUP_CHECK_WIDGET_JS, false ),
            'route_setup_check_widget_api_call' => (bool) get_option( Route_Setup::SETUP_CHECK_WIDGET_API_CALL, false ),
        );
    }

    private function _routeapp_have_webhook_secret() {
        return get_option('_routeapp_webhooks_secret') ? 'Yes' : 'No';
    }

    private function _encodeToken($token) {
        return substr($token, 0, 5) . '...' . substr($token, -5);
    }

    public function routeapp_register_checkout_route(){

        if (!isset(WC()->session) || !isset($_POST) || !isset($_POST['shipping_method'])) {
            return rest_ensure_response( [] );
        }

        $shipping_method = $_POST['shipping_method'];

        //check first if shipping method is allowed
        if ($shipping_method) {
            $show_widget =  $this->routeapp_is_shipping_method_allowed($shipping_method);
            if (!$show_widget) {
                wp_send_json(['hide-widget' => true]);
                return;
            }
        }

        $cart = WC()->cart;
        $this->checkout_route_insurance_fee($cart);
        $response= [
            'routeapp-subtotal' => round($this->get_cart_subtotal_with_only_shippable_items($cart), 2),
        ];

        wp_send_json( $response );
    }

    /**
     * Add route to recreate User and Merchant Accounts
     * if it has failed at the first try
     */
    public function routeapp_register_setup_route () {
        register_rest_route( 'route', 'recreate_user', array(
            'methods' => 'GET',
            'callback' => [Route_Setup::class, 'retry_user'],
            'permission_callback' => '__return_true'
        ) );
        register_rest_route( 'route', 'recreate_merchant', array(
            'methods' => 'GET',
            'callback' => [Route_Setup::class, 'retry_merchant'],
            'permission_callback' => '__return_true'
        ) );
        register_rest_route( 'route', 'user_login', array(
            'methods' => 'POST',
            'callback' => [Route_Setup::class, 'user_login'],
            'permission_callback' => '__return_true'
        ) );
    }

    public function routeapp_last_request_has_failed(){
        if ( is_wp_error( $this->routeapp_last_request ) || !isset($this->routeapp_last_request)) {
            return true;
        } else {
            if ($this->routeapp_last_request['response']['code'] != self::HTTP_CREATED_CODE && $this->routeapp_last_request['response']['code'] != self::HTTP_SUCCESS_CODE) {
                return true;
            }

            return false;
        }
    }

    public function routeapp_last_request_has_success() {
        if ( is_wp_error( $this->routeapp_last_request ) || !isset($this->routeapp_last_request)) {
            return false;
        }
        return $this->routeapp_last_request['response']['code'] === self::HTTP_SUCCESS_CODE;
    }

    public function routeapp_last_request_has_conflicted(){
        if ( is_wp_error( $this->routeapp_last_request ) || !isset($this->routeapp_last_request)) {
            return false;
        } else {
            if ($this->routeapp_last_request['response']['code'] == self::HTTP_CONFLICT_CODE) {
                return true;
            }

            return false;
        }
    }

    public function routeapp_log($event, $extraData = false) {
        $this->routeapp_logger->sentry_log($event, $extraData);
    }

    private function _setup_readiness_report_check()
    {
        if ($this->route_app_setup_helper->can_send_compatibility_report()) {
            /**
             * API CALL for incompatibility test
             */
            $api = new Routeapp_API_Compatibility();
            $api->send($this->route_app_setup_helper->prepare_compatibility_data());
        }
    }

    public function get_order_subtotal($order)
    {
        $subtotal = 0;

        if (is_a($order, 'WC_Order')) {
            $order_items = $order->get_items();

            foreach ($order_items as $order_item_id => $order_item) {
                if ($this->isProductVirtual($order_item)) {
                    //if the product is not found or virtual, skip it
                    continue;
                }
                $subtotal += $order_item->get_subtotal();
            }
        }
        return $subtotal;
    }

    /**
     * Normalize cart item (array) or order item (WC_Order_Item_Product) to a common array format.
     * Ensures compatibility with WooCommerce Product Bundles when used in order context.
     *
     * @param array|WC_Order_Item_Product $item Cart item array or order item object.
     * @return array Normalized item with keys: data, line_subtotal, quantity, product_id, variation_id, key, bundled_items, bundled_by.
     */
    private function normalize_item_for_bundle_calc($item) {
        if (is_object($item) && method_exists($item, 'get_product')) {
            /** @var WC_Order_Item_Product $item */
            $product = $item->get_product();
            $bundled_items = $item->get_meta('_bundled_items', true);
            $bundled_by = $item->get_meta('_bundled_by', true);
            // Use _bundle_cart_key so parent matches child's _bundled_by (Product Bundles uses same hash).
            $key = $item->get_meta('_bundle_cart_key', true) ?: $item->get_id();
            return [
                'data' => $product,
                'line_subtotal' => (float) $item->get_subtotal(),
                'quantity' => (int) $item->get_quantity(),
                'product_id' => $item->get_product_id(),
                'variation_id' => $item->get_variation_id(),
                'key' => $key,
                'bundled_items' => !empty($bundled_items) ? $bundled_items : null,
                'bundled_by' => !empty($bundled_by) ? $bundled_by : null,
            ];
        }
        return $item;
    }

    /**
     * Check if product inside order item is virtual, return true if product is not valid
     * @param array|WC_Order_Item_Product $item Cart item array or order item object.
     * @return bool
     */
    public function isProductVirtual($item) {
        if (is_object($item) && method_exists($item, 'get_product_id')) {
            $productId = $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id();
        } else {
            $productId = isset($item['variation_id']) && !empty($item['variation_id']) ?
                $item['variation_id'] :
                $item['product_id'];
        }

        $product = wc_get_product($productId);
        if ($product) {
            return $product->is_virtual();
        }
        return true;
    }

    /**
     * Process cart item
     *
     * @param $cart_item
     * @param $allowed_bundle_parents
     * @return array
     */
    public function processCartItem($cart_item, $allowed_bundle_parents) {

        $cart_item = $this->normalize_item_for_bundle_calc($cart_item);

        $cart_item_subtotal = $cart_item['line_subtotal'];

        //support for WooCommerce Product Bundles (only when product data is available; order items may have null)
        if (isset($cart_item['bundled_items'])) {
            if (isset($cart_item['data']) && $cart_item['data'] instanceof \WC_Product) {
                $price = $cart_item['data']->get_data()['price'];
                if ($price > 0) {
                    //grab item price from parent bundle if exist, return directly
                    $cart_item_subtotal = $price * $cart_item['quantity'];
                    $data = [
                        'id' => strval($cart_item['product_id']),
                        'quantity' => intval($cart_item['quantity']),
                        'unit_price' => strval(round($cart_item_subtotal / $cart_item['quantity'], 2))
                    ];
                    return ['allowed_bundle_parents' => $allowed_bundle_parents, 'data' => $data];
                }
            }
            //add bundle parent key to allowed array
            $allowed_bundle_parents[] = $cart_item['key'];
        }

        if ($this->isProductVirtual($cart_item)) {
            //if the product is not found or virtual, skip it
            return ['allowed_bundle_parents' => $allowed_bundle_parents];
        }

        if (isset($cart_item['bundled_by'])) {
            if (!in_array($cart_item['bundled_by'], $allowed_bundle_parents)) {
                //skip bundle item children (price is on parent)
                return ['allowed_bundle_parents' => $allowed_bundle_parents];
            }
            if (isset($cart_item['data']) && $cart_item['data'] instanceof \WC_Product) {
                //grab subtotal from bundle child
                $cart_item_subtotal = $cart_item['data']->get_data()['price'] * $cart_item['quantity'];
            }
        }

        return  [
            'allowed_bundle_parents' => $allowed_bundle_parents,
            'data' => [
                'id' => strval($cart_item['product_id']),
                'quantity' => intval($cart_item['quantity']),
                'unit_price' => strval(round($cart_item_subtotal / $cart_item['quantity'], 2))
            ]
        ];
    }

    /**
     * Get cart item subtotal
     *
     * @param $cart_item
     * @param $allowed_bundle_parents
     * @return array
     */
    public function getCartItemSubtotal($cart_item, $allowed_bundle_parents) {

        $cart_item = $this->normalize_item_for_bundle_calc($cart_item);

        $cart_item_subtotal = $cart_item['line_subtotal'];

        //support for WooCommerce Product Bundles (only when product data is available; order items may have null)
        if (isset($cart_item['bundled_items'])) {
            if (isset($cart_item['data']) && $cart_item['data'] instanceof \WC_Product) {
                $price = $cart_item['data']->get_data()['price'];
                if ($price > 0) {
                    //grab item price from parent bundle if exist, return directly
                    $cart_item_subtotal = $price * $cart_item['quantity'];
                    return ['allowed_bundle_parents' => $allowed_bundle_parents, 'subtotal' => $cart_item_subtotal];
                }
            }
            //add bundle parent key to allowed array
            $allowed_bundle_parents[] = $cart_item['key'];
        }

        if ($this->isProductVirtual($cart_item)) {
            //if the product is not found or virtual, skip it
            return ['allowed_bundle_parents' => $allowed_bundle_parents];
        }

        if (isset($cart_item['bundled_by'])) {
            if (!in_array($cart_item['bundled_by'], $allowed_bundle_parents)) {
                //skip bundle item children (price is on parent)
                return ['allowed_bundle_parents' => $allowed_bundle_parents];
            }
            if (isset($cart_item['data']) && $cart_item['data'] instanceof \WC_Product) {
                //grab subtotal from bundle child
                $cart_item_subtotal = $cart_item['data']->get_data()['price'] * $cart_item['quantity'];
            }
        }
        return ['allowed_bundle_parents' => $allowed_bundle_parents, 'subtotal' => $cart_item_subtotal];
    }

    /**
     * get cart shippable items to grab quote on RouteApp
     *
     * @param $cart
     * @param string $object
     * @return array
     */
    public function get_cart_shippable_items($cart, $object='cart')
    {
        $cartItems = [];
        if (!$cart) {
            return $cartItems;
        }
        $allowed_bundle_parents = [];
        if ($object=='cart') {
            foreach ( $cart->get_cart() as $cart_item ) {
                $cartItemData = $this->processCartItem($cart_item, $allowed_bundle_parents);
                $allowed_bundle_parents = $cartItemData['allowed_bundle_parents'];
                if (isset($cartItemData['data'])) {
                    $cartItems[] = $cartItemData['data'];
                }
            }
        } else {
            foreach ( $cart->get_items() as $cart_item ) {
                $cartItemData = $this->processCartItem($cart_item, $allowed_bundle_parents);
                $allowed_bundle_parents = $cartItemData['allowed_bundle_parents'];
                if (isset($cartItemData['data'])) {
                    $cartItems[] = $cartItemData['data'];
                }
            }
        }

        return $cartItems;
    }

    /**
     * get subtotal for shippable items only
     *
     * @param $cart
     * @param string $object
     * @return float|int|mixed
     */
    public function get_cart_subtotal_with_only_shippable_items($cart, $object='cart')
    {
        $subtotal = 0;
        if (!$cart) {
            return $subtotal;
        }
        $allowed_bundle_parents = [];

        if ($object=='cart') {
            foreach ( $cart->get_cart() as $cart_item ) {
                $itemSubtotal = $this->getCartItemSubtotal($cart_item, $allowed_bundle_parents);
                $allowed_bundle_parents = $itemSubtotal['allowed_bundle_parents'];
                if (isset($itemSubtotal['subtotal'])) {
                    $subtotal += $itemSubtotal['subtotal'];
                }
            }
        } else {
            foreach ( $cart->get_items() as $cart_item ) {
                $itemSubtotal = $this->getCartItemSubtotal($cart_item, $allowed_bundle_parents);
                $allowed_bundle_parents = $itemSubtotal['allowed_bundle_parents'];
                if (isset($itemSubtotal['subtotal'])) {
                    $subtotal += $itemSubtotal['subtotal'];
                }
            }
        }
        return $subtotal;
    }

    /**
     * Edit Avatax fee line (send tax class name instead item_id)
     */
    public function routeapp_edit_avatax_fee($fee_line, $fee = null)
    {
        if (isset($fee_line['description']) && $fee_line['description'] == $this->routeapp_get_insurance_label()) {
            $fee_line['itemCode'] = $this->routeapp_get_taxable_class();
        }

        return $fee_line;
    }


    /**
     * This method adds to xml export an SKU tag in order to identify
     * Route Protection to ShipStation integration
     *
     * @param $order_xml
     * @return mixed
     */
    public function routeapp_add_sku_to_route_node($order_xml){

        if ($order_xml instanceof DOMElement){

            $items = $order_xml->getElementsByTagName('Items');

            foreach ($items as $itemNode) {
                foreach ($itemNode->getElementsByTagName('Item') as $item) {
                    foreach ($item->getElementsByTagName('Name') as $name) {
                        if (trim($name->nodeValue) === self::ROUTE_LABEL)
                        {
                            $itemNode->removeChild($item);
                        }
                    }
                }
            }

        }

        return $order_xml;
    }

	/**
	 * Update order meta only if changed (HPOS & CPT safe)
	 *
	 * @return bool True if meta changed
	 */
	public function updateRouteOrderMetaData(
		WC_Order $order,
		bool $is_hpos,
		string $meta_key,
		$meta_value
	): bool {
		try {
			if ( ! $order || empty( $meta_key ) ) {
				return false;
			}

			$normalized = is_scalar( $meta_value )
				? (string) $meta_value
				: wp_json_encode( $meta_value );

			if ( $is_hpos ) {
				$current = $order->get_meta( $meta_key, true );
				$current = is_scalar( $current ) ? (string) $current : wp_json_encode( $current );


				if ( (string) $current === (string) $normalized ) {
					return false;
				}

				$order->update_meta_data( $meta_key, $normalized );
				return true;

			}

			$order_id = $order->get_id();
			$current = get_post_meta( $order_id, $meta_key, true );
			$current = is_scalar( $current ) ? (string) $current : wp_json_encode( $current );


			if ( (string) $current === (string) $normalized ) {
				return false;
			}

			update_post_meta( $order_id, $meta_key, $normalized );
			return true;

		} catch ( Exception $e ) {
			error_log(
				'RouteApp: error updating order meta (' . $meta_key . '): ' . $e->getMessage()
			);
			return false;
		}
	}

	//MSS-6533
	/**
	 * Resolve the local pickup flag in a FAIL-SAFE way.
	 *
	 * Priority:
	 * 1. Order shipping items
	 * 3. Checkout session (fallback only)
	 *
	 * @param WC_Order|null $order
	 *
	 * @return int 1 = local pickup, 0 = not local pickup
	 */
	public function routeapp_resolve_ship_to_store_flag( WC_Order $order = null): int {
		$excluded_methods = $this->routeapp_get_excluded_shipping_methods();
		if ( empty( $excluded_methods ) || ! is_array( $excluded_methods ) ) {
			return 0;
		}

		// Order-based (most reliable).
		if ( $order instanceof WC_Order ) {
			$shipping_items = $order->get_shipping_methods();
			if ( ! empty( $shipping_items ) ) {
				foreach ( $shipping_items as $shipping_item ) {
					if ( ! $shipping_item instanceof WC_Order_Item_Shipping ) {
						continue;
					}
					$method_id = $shipping_item->get_method_id();
					if ( empty( $method_id ) ) {
						continue;
					}
					$method_id = explode( ':', $method_id )[0];
					if ( in_array( $method_id, $excluded_methods, true ) ) {
						return 1;
					}
				}
				return 0;
			}
		}

		// Session fallback (checkout only).
		if ( ! function_exists( 'WC' ) || ! WC()->session ) {
			return 0;
		}
		$chosen = WC()->session->get( 'chosen_shipping_methods' );
		$packages = WC()->session->get( 'shipping_for_package_0' );
		if ( empty( $chosen[0] ) || empty( $packages['rates'] ) ) {
			return 0;
		}
		$chosen_id = $chosen[0];
		if ( ! isset( $packages['rates'][ $chosen_id ] ) ) {
			return 0;
		}
		$method_id = explode( ':', $packages['rates'][ $chosen_id ]->id )[0];
		if ( in_array( $method_id, $excluded_methods, true ) ) {
			return 1;
		}

		return 0;
	}

	/**
	 * Register callback for WooCommerce blocks checkout cart updates
	 * This ensures the Route widget toggle properly updates the cart in blocks checkout
	 *
	 * @return void
	 */
	public function routeapp_register_blocks_checkout_callback() {
		if ( function_exists( 'woocommerce_store_api_register_update_callback' ) ) {
			woocommerce_store_api_register_update_callback(
				array(
					'namespace' => 'route-widget-integration',
					'callback'  => function( $data ) {
						// Update session if checkbox value is provided
						if ( isset( $data['checkbox'] ) && WC()->session ) {
                            $checkboxValue = filter_var( $data['checkbox'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );

                            // Treat invalid values as false to keep session state deterministic.
                            WC()->session->set( 'checkbox_checked', true === $checkboxValue );
						}

						// The cart will be recalculated automatically, triggering woocommerce_cart_calculate_fees
						return true;
					},
				)
			);
		}
	}

	/**
	 * Inject Route widget before the checkout block order summary (default placement for blocks checkout).
	 *
	 * @param string $block_content The block content.
	 * @param array  $block         The full block, including name and attributes.
	 * @param object $instance      The block instance.
	 * @return string Modified block content with Route widget prepended when applicable.
	 */
	public function routeapp_checkout_block_before_order_summary( $blockContent, $block, $instance ) {
		if ( is_admin() && ! wp_doing_ajax() ) {
			return $blockContent;
		}

		if ( $this->route_block_rendered || $this->is_route_block_in_checkout_page() ) {
			return $blockContent;
		}

		if ( ! function_exists( 'WC' ) || ! WC()->cart || ! WC()->session ) {
			return $blockContent;
		}
		if ( empty( $this->routeapp_get_public_token() ) ) {
			return $blockContent;
		}

		WC()->cart->calculate_totals();

		$routeHtml = do_shortcode( '[route]' );
		if ( empty( $routeHtml ) ) {
			return $blockContent;
		}

		return $routeHtml . $blockContent;
	}

	/**
	 * Register the Route Protection custom checkout block.
	 * Hooked to 'init'.
	 */
	public function routeapp_register_checkout_block() {
		if ( ! function_exists( 'register_block_type' ) ) {
			return;
		}

		$block_dir = plugin_dir_path( dirname( __FILE__ ) ) . 'blocks/route-protection';
		if ( ! file_exists( $block_dir . '/block.json' ) ) {
			return;
		}

		wp_register_script(
			'route-protection-editor',
			plugins_url( 'blocks/route-protection/index.js', dirname( __FILE__ ) ),
			array( 'wp-blocks', 'wp-element', 'wp-block-editor', 'wc-blocks-checkout' ),
			defined( 'ROUTEAPP_VERSION' ) ? ROUTEAPP_VERSION : '1.0.0',
			true
		);

		register_block_type(
			$block_dir,
			array(
				'editor_script'   => 'route-protection-editor',
				'render_callback' => array( $this, 'render_route_protection_block' ),
			)
		);
	}

	/**
	 * Server-side render callback for the route/route-protection block.
	 *
	 * @param array  $attributes Block attributes.
	 * @param string $content    Block inner content.
	 * @return string
	 */
	public function render_route_protection_block( $attributes, $content ) {
		if ( is_admin() && ! wp_doing_ajax() ) {
			return '';
		}
		if ( ! function_exists( 'WC' ) || ! WC()->cart || ! WC()->session ) {
			return '';
		}
		if ( empty( $this->routeapp_get_public_token() ) ) {
			return '';
		}

		WC()->cart->calculate_totals();

		$routeHtml = do_shortcode( '[route]' );

		$this->route_block_rendered = true;

		return $routeHtml;
	}

	/**
	 * Register the Route Protection IntegrationInterface with WooCommerce Blocks.
	 *
	 * @param \Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry $integration_registry
	 */
	public function routeapp_register_blocks_integration( $integration_registry ) {
		$file = plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-routeapp-blocks-integration.php';
		if ( ! class_exists( 'Routeapp_Blocks_Integration' ) && file_exists( $file ) ) {
			require_once $file;
		}
		if ( class_exists( 'Routeapp_Blocks_Integration' ) ) {
			$integration_registry->register( new Routeapp_Blocks_Integration() );
		}
	}

	/**
	 * Check whether the checkout page contains the route/route-protection block.
	 *
	 * @return bool
	 */
	private function is_route_block_in_checkout_page() {
		if ( ! function_exists( 'wc_get_page_id' ) || ! function_exists( 'has_block' ) ) {
			return false;
		}

		$checkout_page_id = wc_get_page_id( 'checkout' );
		if ( $checkout_page_id <= 0 ) {
			return false;
		}

		$post = get_post( $checkout_page_id );
		if ( ! $post ) {
			return false;
		}

		return has_block( 'route/route-protection', $post );
	}

}


```
