*/
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';
/**
* The ID of this plugin.
*
* @since 1.0.0
* @access private
* @var string $plugin_name The ID of this plugin.
*/
private $plugin_name;
/**
* 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;
/**
* 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();
$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( '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);
}
/**
* 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
*
*/
public function checkout_route_insurance_fee( $cart ) {
if ( (is_admin() && ! defined( 'DOING_AJAX' )) ) return;
$cart = empty($cart) ? WC()->cart : $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);
$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->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 = '';
}
$output = '' .
$jsPath .
'' .
'
'.$order->get_id().'
'.
'' .
$raw_html .
'
' .
'
';
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);
wp_enqueue_script($this->plugin_name, plugin_dir_url(__FILE__) . 'js/routeapp-public-pbc.js', array('jquery'), $this->version, false);
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 = '' .
'
'.
'
'.
'
'.
'
'.
'
'.
'
'.
'
'.
'
';
//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 .= '
';
}
}
return $return . '
' ;
}
public function wc_routeapp_ajaxurl() {
echo '';
}
/**
* 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 );
$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();
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);
}
}
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 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;
}
/**
* 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);
$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'])) {
update_post_meta( $order->get_id(), '_routeapp_quote', $cached['body'] );
//save to order the configuration selected regarding order statuses
$acceptedOrderStatuses = get_option('routeapp_included_order_statuses') ? get_option('routeapp_included_order_statuses') : [];
update_post_meta( $order->get_id(), '_routeapp_included_order_statuses', json_encode($acceptedOrderStatuses));
$acceptedCanceledStatuses = get_option('routeapp_cancel_order_statuses') ? get_option('routeapp_cancel_order_statuses') : [];
update_post_meta( $order->get_id(), '_routeapp_cancel_order_statuses', json_encode($acceptedCanceledStatuses));
//clear cache!
WC()->session->__unset($main_key);
WC()->session->__unset($key);
}
}
}
}
/**
* 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() {
$response = 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()
);
return rest_ensure_response( $response );
},
'permission_callback' => '__return_true'
)
);
}
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;
}
/**
* Check if product inside order item is virtual, return true if product is not valid
* @param $item
* @return bool
*/
public function isProductVirtual($item) {
$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_subtotal = $cart_item['line_subtotal'];
//support for WooCommerce Product Bundles
if (isset($cart_item['bundled_items'])) {
if ($cart_item['data']->get_data()['price'] > 0) {
//grab item price from parent bundle if exist, return directly
$cart_item_subtotal = $cart_item['data']->get_data()['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];
} else{
//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];
} else {
//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_subtotal = $cart_item['line_subtotal'];
//support for WooCommerce Product Bundles
if (isset($cart_item['bundled_items'])) {
if ($cart_item['data']->get_data()['price'] > 0) {
//grab item price from parent bundle if exist, return directly
$cart_item_subtotal = $cart_item['data']->get_data()['price'] * $cart_item['quantity'];
return ['allowed_bundle_parents' => $allowed_bundle_parents, 'subtotal' => $cart_item_subtotal];
} else{
//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];
} else {
//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)
{
$this->xml_append($item, 'SKU', self::ROUTE_PROTECTION_SKU);
}
}
}
}
}
return $order_xml;
}
/**
* Append XML as cdata
*/
private function xml_append( $append_to, $name, $value, $cdata = true ) {
$data = $append_to->appendChild( $append_to->ownerDocument->createElement( $name ) );
if ( $cdata ) {
$data->appendChild( $append_to->ownerDocument->createCDATASection( $value ) );
} else {
$data->appendChild( $append_to->ownerDocument->createTextNode( $value ) );
}
}
}