PluginProbe
Route ‑ Shipping Protection / 2.2.4
Route ‑ Shipping Protection v2.2.4
2.4.19 2.4.18 2.4.17 2.4.16 2.4.15 2.4.14 2.4.13 2.4.12 2.4.11 2.4.10 2.4.9 2.4.8 2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.2.4 2.2.5 2.2.6 2.2.8 2.2.9 2.3.0 2.3.1 All 153 releases
routeapp / public / class-routeapp-public.php

class-routeapp-public.php in Route ‑ Shipping Protection 2.2.4, at public/class-routeapp-public.php

1,528 lines 57.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The public-facing functionality of the plugin.
4 *
5 * @link https://route.com/
6 * @since 1.0.0
7 *
8 * @package Routeapp
9 * @subpackage Routeapp/public
10 */
11
12 /**
13 * The public-facing functionality of routeapp.
14 *
15 *
16 * @package Routeapp
17 * @subpackage Routeapp/public
18 * @author Route App <[email protected]>
19 */
20
21 class Routeapp_Public {
22
23 const HTTP_SUCCESS_CODE = 200;
24 const HTTP_BAD_REQUEST_CODE = 400;
25 const HTTP_CREATED_CODE = 201;
26 const HTTP_CONFLICT_CODE = 409;
27 const HTTP_NOT_FOUND = 404;
28 const HTTP_INTERNAL_SERVER_ERROR_CODE = 500;
29 const DEFAULT_MAX_USD_SUBTOTAL_ALLOWED = 5000;
30 const DEFAULT_MIN_USD_SUBTOTAL_ALLOWED = 0;
31 const ROUTE_LABEL = 'Route Shipping Protection';
32 const DEFAULT_CHECKOUT_WEBHOOK = 'woocommerce_checkout_before_order_review';
33 const ROUTE_PROTECTION_SKU = 'ROUTEINS';
34 const ALLOWED_QUOTE_TYPE = 'paid_by_customer';
35
36 /**
37 * The ID of this plugin.
38 *
39 * @since 1.0.0
40 * @access private
41 * @var string $plugin_name The ID of this plugin.
42 */
43 private $plugin_name;
44
45 /**
46 * The version of this plugin.
47 *
48 * @since 1.0.0
49 * @access private
50 * @var string $version The current version of this plugin.
51 */
52 private $version;
53
54 /**
55 * Routeapp API instance
56 * @since 1.0.0
57 * @access private
58 * @var string $routeapp_api Routeapp API Client
59 */
60 public $routeapp_api_client;
61
62
63 /**
64 * Route Shipment Tracking Module
65 */
66 private $routeapp_shipment_tracking;
67
68 /**
69 * Last Request
70 */
71 private $routeapp_last_request;
72
73 /**
74 * Route Logger
75 */
76 private $routeapp_logger;
77
78 /**
79 * Route Admin Fee
80 */
81 private $routeapp_admin_fee;
82
83 /**
84 * Route Setup
85 */
86 private $route_app_setup_helper;
87
88 /**
89 * Initialize the class and set its properties.
90 *
91 * @since 1.0.0
92 * @param string $plugin_name The name of the plugin.
93 * @param string $version The version of this plugin.
94 */
95 public function __construct($plugin_name, $version)
96 {
97
98 $this->plugin_name = $plugin_name;
99 $this->version = $version;
100 $this->routeapp_api_client = new Routeapp_API_Client($this->routeapp_get_public_token(), $this->routeapp_get_secret_token());
101 $this->routeapp_shipment_tracking = new Routeapp_Shipment_Tracking();
102 $this->routeapp_logger = new Routeapp_Logger_Sentry();
103 $this->routeapp_admin_fee = new Routeapp_Admin_Route_Fee();
104 $this->route_app_setup_helper = new Route_Setup();
105 $this->add_actions_and_filters();
106 }
107
108 public function add_actions_and_filters() {
109 //CHECKOUT HOOK
110 $checkout_hook = $this->routeapp_get_checkout_hook();
111 add_action($checkout_hook, array($this, 'checkout_route_insurance'), 1, 0);
112
113
114 add_action('woocommerce_checkout_before_order_review', array($this, 'checkout_route_insurance_fee'), 20, 1);
115
116 add_action('woocommerce_cart_calculate_fees', array($this, 'checkout_route_insurance_fee'), 20, 1);
117 add_action('wp_ajax_woo_get_ajax_data', array($this, 'checkout_route_insurance_set_session'));
118 add_action('wp_ajax_nopriv_woo_get_ajax_data', array($this, 'checkout_route_insurance_set_session'));
119
120 add_action('wp_ajax_woo_check_widget', array($this, 'check_route_widget'));
121 add_action('wp_ajax_nopriv_woo_check_widget', array($this, 'check_route_widget'));
122
123 add_action( 'woocommerce_checkout_create_order_line_item', [ $this, 'routeapp_checkout_create_order_line_item' ], 10, 4 );
124 add_action( 'woocommerce_ajax_add_order_item_meta', [$this, 'routeapp_ajax_add_order_item_meta'], 10, 2 );
125 add_filter( 'woocommerce_hidden_order_itemmeta', [$this,'routeapp_woocommerce_hidden_order_itemmeta'], 10, 1);
126
127 add_action( 'added_post_meta', array( $this, 'routeapp_update_tracking_order_api' ), 20, 3 );
128 add_action( 'updated_post_meta', array( $this, 'routeapp_update_tracking_order_api' ), 20, 3 );
129 add_action( 'added_order_item_meta', array( $this, 'routeapp_update_tracking_order_item_api' ), 20, 3 );
130 add_action( 'updated_order_item_meta', array( $this, 'routeapp_update_tracking_order_item_api' ), 20, 3 );
131 add_action('woocommerce_order_status_processing', array($this, 'routeapp_mark_order'), 10, 1);
132 add_action('woocommerce_order_status_completed', array($this, 'routeapp_mark_order'), 8, 2);
133 add_action('woocommerce_new_order', array($this, 'routeapp_save_quote_to_order'), 10, 1);
134 add_action('rest_api_init', array($this, 'routeapp_register_status_route'));
135 add_action('rest_api_init', array($this, 'routeapp_register_setup_route'));
136 add_action('wp_ajax_get_route_checkout', array($this, 'routeapp_register_checkout_route'));
137 add_action('wp_ajax_nopriv_get_route_checkout', array($this, 'routeapp_register_checkout_route'));
138 add_action('wp_head', array($this, 'wc_routeapp_ajaxurl'));
139 add_action('woocommerce_init', array($this, 'register_shortcode'));
140 add_action('woocommerce_rest_prepare_order_note', array($this, 'check_order_note'));
141 add_action('wp_insert_comment', array($this, 'check_order_note_by_note_id'));
142
143 add_action('wp_ajax_routeapp_add_admin_fee', array($this, 'routeapp_add_admin_fee'));
144 add_action('wp_ajax_nopriv_routeapp_add_admin_fee', array($this, 'routeapp_add_admin_fee'));
145 add_action('wp_ajax_routeapp_remove_admin_fee', array($this, 'routeapp_remove_admin_fee'));
146 add_action('wp_ajax_nopriv_routeapp_remove_admin_fee', array($this, 'routeapp_remove_admin_fee'));
147 add_action('wc_avatax_api_fee_line_data', array($this, 'routeapp_edit_avatax_fee'));
148 add_action('woocommerce_shipstation_export_order_xml', array($this, 'routeapp_add_sku_to_route_node') );
149
150 add_action('woocommerce_order_details_after_order_table', array($this, 'order_updates_route_tracking'), 1, 1);
151 }
152
153 /**
154 * Hide order line_item meta_data from Route App
155 *
156 * @param $arr
157 * @return mixed
158 */
159 function routeapp_woocommerce_hidden_order_itemmeta($arr) {
160 $arr[] = '_routeapp_source_product_id';
161 $arr[] = '_routeapp_image_url';
162 $arr[] = '_routeapp_virtual';
163 $arr[] = '_routeapp_weight_value';
164 $arr[] = '_routeapp_weight_unit';
165 $arr[] = '_routeapp_origin_location';
166 return $arr;
167 }
168
169 /**
170 * Fill metadata
171 *
172 * @param $item WC_Order_Item
173 * @param $product WC_Product
174 * @param $entity_type enum('order_item','item')
175 * @return void
176 */
177 public static function fillMetaData($item, $product, $entity_type='order_item') {
178
179 $sourceProductId = $product->get_id();
180 $productImageUrl = (wp_get_attachment_url($product->get_image_id())) ? wp_get_attachment_url($product->get_image_id()) : null;
181 $weightValue = $product->get_weight();
182
183 $originLocation = json_encode([
184 'address' => get_option('woocommerce_store_address'),
185 'address2' => get_option('woocommerce_store_address_2'),
186 'store_city' => get_option('woocommerce_store_city'),
187 'store_postcode' => get_option('woocommerce_store_postcode'),
188 'default_country' => get_option('woocommerce_default_country'),
189 ]);
190
191
192 switch($entity_type) {
193 case 'order_item':
194 //add source_product_id as order line_item meta_data
195 self::addItemMeta($item, '_routeapp_source_product_id', $sourceProductId);
196
197 //add image_url as order line_item meta_data
198 self::addItemMeta($item, '_routeapp_image_url', $productImageUrl);
199
200 //add weight_value as order line_item meta_data
201 self::addItemMeta($item, '_routeapp_weight_value', $weightValue);
202
203 //add weight_unit as order line_item meta_data
204 self::addItemMeta($item, '_routeapp_weight_unit', get_option('woocommerce_weight_unit'));
205
206 //check if product is virtual
207 if ($product->is_virtual()) {
208 //add virtual as order line_item meta_data
209 self::addItemMeta($item, '_routeapp_virtual', 1);
210 }
211 //add origin location as order line_item meta_data
212 self::addItemMeta($item, '_routeapp_origin_location', $originLocation);
213 break;
214 case 'item':
215 //add source_product_id as order line_item meta_data
216 self::addOrderItemMeta($item->get_id(), '_routeapp_source_product_id', $sourceProductId);
217
218 //add image_url as order line_item meta_data
219 self::addOrderItemMeta($item->get_id(), '_routeapp_image_url', $productImageUrl);
220
221 //add weight_value as order line_item meta_data
222 self::addOrderItemMeta($item->get_id(), '_routeapp_weight_value', $weightValue);
223
224 //add weight_unit as order line_item meta_data
225 self::addOrderItemMeta($item->get_id(), '_routeapp_weight_unit', get_option('woocommerce_weight_unit'));
226
227 //check if product is virtual
228 if ($product->is_virtual()) {
229 //add virtual as order line_item meta_data
230 self::addOrderItemMeta($item->get_id(), '_routeapp_virtual', 1);
231 }
232 //add origin location as order line_item meta_data
233 self::addOrderItemMeta($item->get_id(), '_routeapp_origin_location', $originLocation);
234 break;
235 }
236 }
237
238 /**
239 * Add order line_item meta_data on ajax add to cart
240 *
241 * @param $item_id String
242 * @param $item WC_Order_Item
243 * @return false|void
244 */
245 public static function routeapp_ajax_add_order_item_meta( $item_id, $item) {
246 $product = $item->get_product(); // Get the WC_Product object
247 if ($product instanceof WC_Product) {
248 try {
249 self::fillMetaData($item, $product, 'item');
250 }catch (Exception $exception) {
251 return false;
252 }
253 }
254 }
255
256 /**
257 * @param $itemId WC_Order_Item ID
258 * @param $key String
259 * @param $value String
260 * @return bool
261 */
262 private static function addOrderItemMeta($itemId, $key, $value) {
263 try {
264 $fieldExists = wc_get_order_item_meta( $itemId, $key, true);
265 if (!$fieldExists) {
266 wc_add_order_item_meta($itemId, $key, $value);
267 }
268 }catch (Exception $exception) {
269 return false;
270 }
271 return true;
272 }
273
274 /**
275 * @param $item WC_Order_Item
276 * @param $key String
277 * @param $value String
278 * @return bool
279 */
280 private static function addItemMeta($item, $key, $value) {
281 try {
282 $fieldExists = $item->get_meta($key, true);
283 if (!$fieldExists) {
284 $item->add_meta_data( $key, $value );
285 }
286 }catch (Exception $exception) {
287 return false;
288 }
289 return true;
290 }
291
292 /**
293 * Add order line_item meta_data on checkout add to cart
294 *
295 * @param $item WC_Order_Item
296 * @param $cart_item_key String
297 * @param $values Object
298 * @param $order WC_Order
299 * @return false|void
300 */
301 public function routeapp_checkout_create_order_line_item( $item, $cart_item_key, $values, $order ) {
302 if ( $order instanceof WC_Order ) {
303 $product = $item->get_product(); // Get the WC_Product object
304 if ($product instanceof WC_Product) {
305 try {
306 self::fillMetaData($item, $product, 'order_item');
307 }catch (Exception $exception) {
308 return false;
309 }
310 }
311 }
312 }
313
314 public function routeapp_add_admin_fee() {
315 $this->routeapp_admin_fee->routeapp_add_admin_fee();
316 }
317
318 public function routeapp_remove_admin_fee() {
319 $this->routeapp_admin_fee->routeapp_remove_admin_fee();
320 }
321
322 public function register_shortcode() {
323 add_shortcode( 'route', array( $this, 'route_widget_shortcode' ) );
324 }
325
326 public function get_allowed_quote_type() {
327 return self::ALLOWED_QUOTE_TYPE;
328 }
329
330 public function check_route_widget() {
331 if (!isset($_POST) || !$_POST['shipping_method']) {
332 wp_send_json_error('its missing shipping method on the request');
333 return;
334 }
335 $shipping_method = isset($_POST['shipping_method']) ? $_POST['shipping_method'] : '';
336 $response = [];
337 $response['show_widget'] = $this->routeapp_is_shipping_method_allowed($shipping_method);
338 wp_send_json_success( $response );
339 return;
340 }
341
342 /**
343 * set routeapp session
344 *
345 * @since 1.0.0
346 */
347 public function checkout_route_insurance_set_session() {
348 if ( isset($_POST['checkbox']) && $_POST['checkbox'] === 'true' ){
349 WC()->session->set('checkbox_checked', true );
350 } else {
351 WC()->session->set( 'checkbox_checked', false );
352 }
353 }
354
355 /**
356 * Add route insurance fee in checkout and cart
357 * @since 1.0.0
358 * @param Object $cart
359 *
360 */
361 public function checkout_route_insurance_fee( $cart ) {
362 if ( (is_admin() && ! defined( 'DOING_AJAX' )) ) return;
363
364 $cart = empty($cart) ? WC()->cart : $cart;
365 $checkbox = WC()->session->get( 'checkbox_checked' );
366
367 $cartRef = $cart->get_cart_hash();
368 $cartTotal = round($this->get_cart_subtotal_with_only_shippable_items($cart), 2);
369 $currency = get_woocommerce_currency();
370 $cartItems = $this->get_cart_shippable_items($cart);
371 $route_insurance_quote = $this->routeapp_get_quote_from_api($cartRef, $cartTotal, $currency, $cartItems);
372
373 $route_insurance_amount = false;
374 //only add fee if the quote is covered by customer and has a valid amount
375 if (isset($route_insurance_quote->premium->amount) &&
376 (isset($route_insurance_quote->payment_responsible->type)) && $route_insurance_quote->payment_responsible->type==self::ALLOWED_QUOTE_TYPE) {
377 $route_insurance_amount = $route_insurance_quote->premium->amount;
378 }
379 if (is_null($checkbox) && isset($route_insurance_quote->payment_responsible->ToggleState)) {
380 $checkbox = $route_insurance_quote->payment_responsible->ToggleState;
381 WC()->session->set('checkbox_checked', $checkbox);
382 }
383
384 Route_Setup::set_setup_check_widget(Route_Setup::SETUP_CHECK_WIDGET_PHP);
385 if ( (is_null($checkbox) && is_checkout() || ($checkbox === TRUE || $checkbox === 1)) && $route_insurance_amount > 0 && $this->routeapp_is_shipping_method_allowed()) {
386 $cart->add_fee( $this->routeapp_get_insurance_label(),
387 $route_insurance_amount,
388 $this->routeapp_get_fee_taxable(),
389 $this->routeapp_get_taxable_class());
390 }
391 }
392
393 /**
394 * Create checkout
395 * @since 1.0.0
396 *
397 */
398 public function checkout_route_insurance() {
399 echo do_shortcode('[route]');
400 }
401
402 public function order_updates_route_tracking($order) {
403 if ($this->routeapp_route_show_order_updates()) {
404 echo $this->route_show_thankyoupage_asset($order);
405 }
406 }
407
408 public function route_show_thankyoupage_asset($order) {
409 $output = '';
410
411 $site = parse_url(get_site_url(get_current_blog_id()), PHP_URL_HOST);
412 $response = Routeapp_API_Client::getInstance()->asset_settings($site);
413
414 if (is_wp_error($response) ||
415 (isset($response['response']['code']) && $response['response']['code'] == 404) ||
416 is_null($response['body'])) {
417 return $output;
418 } else {
419
420 $body = json_decode($response['body']);
421
422 if (!is_null($body) &&
423 !is_null($body->asset_settings) &&
424 !is_null($body->asset_settings->asset_live) &&
425 $body->asset_settings->asset_live) {
426 $raw_html = $body->asset_settings->asset_content->raw_html;
427 $css = $body->asset_settings->asset_content->css_url;
428 $js = isset($body->asset_settings->asset_content->js_url) ? $body->asset_settings->asset_content->js_url : '';
429 $jsPath = '';
430 if (!empty($js)) {
431 $jsPath = '<script type="text/javascript" src="' . $js . '"></script>';
432 }
433 $output = '<link rel="stylesheet" href="' . $css . '" type="text/css" />' .
434 $jsPath .
435 '<section class=woocommerce-order-updates>' .
436 '<h2 class="woocommerce-order-updates__title">Order Updates</h2>' .
437 '</section>' .
438 '<div><span class="os-order-number" style="display:none;">'.$order->get_id().'</span></div>'.
439 '<div>' .
440 $raw_html .
441 '</div>' .
442 '<div>&nbsp;</div>';
443
444 wp_enqueue_script($this->plugin_name . '-analytics', 'https://cdn.routeapp.io/route-analytics/route-analytics.js', array(), $this->version, false);
445 wp_enqueue_script($this->plugin_name, plugin_dir_url(__FILE__) . 'js/routeapp-analytics.js', array('jquery'), $this->version, false);
446 }
447 }
448
449 return $output;
450 }
451
452 public function route_widget_shortcode()
453 {
454 if(is_admin() || empty($this->routeapp_get_public_token())) return;
455
456 $custom_env = getenv('ROUTEAPP_ENVIRONMENT_ENDPOINT');
457 if (is_null($custom_env) || !$custom_env) {
458 $custom_env = isset($_SERVER['ROUTEAPP_ENVIRONMENT_ENDPOINT']) ? $_SERVER['ROUTEAPP_ENVIRONMENT_ENDPOINT'] : '';
459 }
460 $custom_env = empty($custom_env) ? 'production' : $custom_env;
461
462 wp_enqueue_script($this->plugin_name . '-widget', 'https://protection-widget.route.com/route-protection-widget.js', array(), $this->version, false);
463
464 wp_enqueue_script($this->plugin_name, plugin_dir_url(__FILE__) . 'js/routeapp-public-pbc.js', array('jquery'), $this->version, false);
465
466 wp_enqueue_style($this->plugin_name . '-widget', plugin_dir_url(__FILE__) . 'css/routeapp-widget.css', array(), $this->version, false);
467
468 global $wp;
469 $parsedUrl = parse_url(home_url( $wp->request));
470
471 switch($custom_env) {
472 case 'stage':
473 $custom_env = 'Route.Environment.Dev';
474 break;
475 default:
476 $custom_env = 'Route.Environment.Production';
477 break;
478 }
479 $checkbox = WC()->session->get( 'checkbox_checked' );
480 $checkbox = $checkbox ? 'Route.Coverage.ActiveByDefault' : 'Route.Coverage.InactiveByDefault';
481
482 $divId = is_checkout() ? 'route-checkout-radio' : 'route-cart-radio';
483
484 $return = '<div id="'.$divId.'">' .
485 '<input type="hidden" value="'. get_woocommerce_currency() .'" class="routeapp-currency">'.
486 '<input type="hidden" value="'. round($this->get_cart_subtotal_with_only_shippable_items(WC()->cart), 2) .'" class="routeapp-subtotal">'.
487 '<input type="hidden" value="' . admin_url('admin-ajax.php') . '" class="routeapp-ajax-url">'.
488 '<input type="hidden" value="' . $parsedUrl['host'] . '" class="routeapp-store-domain">'.
489 '<input type="hidden" value="' . get_option('blogname') . '" class="routeapp-store-name">'.
490 '<input type="hidden" value="' . $this->routeapp_api_client->get_merchant_id() . '" class="routeapp-merchant-id">'.
491 '<input type="hidden" value="' . $custom_env . '" class="routeapp-env">'.
492 '<input type="hidden" value="' . $checkbox .'" class="routeapp-checkbox-cookie">';
493
494 //check if shipping method is allowed
495 $cart = WC()->cart;
496 $shipping_method = $cart->calculate_shipping();
497 if (isset($shipping_method[0]->method_id)) {
498 $shipping_method = $shipping_method[0]->method_id;
499 $show_widget = $this->routeapp_is_shipping_method_allowed($shipping_method);
500 if (!$show_widget) {
501 $return .= '<input type="hidden" value="true" class="routeapp-invalid-shipping-method">';
502 }
503 }
504 return $return . '<div id="RouteWidget" class="route-div"></div></div>' ;
505 }
506
507
508 public function wc_routeapp_ajaxurl() {
509 echo '<script type="text/javascript">' .
510 'var wc_routeapp_ajaxurl = "' . admin_url('admin-ajax.php') . '"' .
511 '</script>';
512 }
513
514 /**
515 * get amount from RouteApp API
516 *
517 * @param $cartRef
518 * @param $cartTotal
519 * @param $currency
520 * @param $cartItems
521 * @return false|mixed
522 */
523 public function routeapp_get_quote_from_api($cartRef, $cartTotal, $currency, $cartItems) {
524 if (!$cartRef) return false;
525 if (!$cartTotal) return false;
526 $currency = !$currency ? get_woocommerce_currency() : $currency;
527
528 $response = $this->routeapp_api_client->get_quote($cartRef, $cartTotal, $currency, $cartItems);
529
530 if (is_wp_error($response)) {
531 return false;
532 } else {
533 if (isset($response['response']['code']) && $response['response']['code'] == 401) {
534 return false;
535 }
536 $price_data = json_decode($response['body']);
537 Route_Setup::set_setup_check_widget(Route_Setup::SETUP_CHECK_WIDGET_JS);
538 return $price_data;
539 }
540 }
541
542 /**
543 * create order data sending for API
544 * @since 1.0.0
545 * @param integer $order_id
546 */
547 public function create_order_data_api($order_id) {
548
549 $order = wc_get_order( $order_id );
550
551 $quote = get_post_meta($order->get_id(), '_routeapp_quote', true);
552 $insurance_selected = false;
553 $insurance_amount = false;
554
555 foreach ($order->get_items('fee') as $fee) {
556 if ($fee->get_name() === $this->routeapp_get_insurance_label()) {
557 $insurance_selected = true;
558 $insurance_amount = $fee->get_amount();
559 update_post_meta( $order->get_id(), '_routeapp_route_charge', $fee->get_amount() );
560 }
561 }
562 $quoteId = 'Invalid';
563 if ($quote) {
564 $quoteDecoded = json_decode($quote);
565 $quoteId = $quoteDecoded->id;
566 }
567
568 $destination = array(
569 "first_name" => $order->get_billing_first_name(),
570 "last_name" => $order->get_billing_last_name(),
571 "street_address1" => $order->get_shipping_address_1(),
572 "street_address2" => $order->get_shipping_address_2(),
573 "city" => $order->get_shipping_city(),
574 "province" => $order->get_shipping_state(),
575 "zip" => $order->get_shipping_postcode(),
576 "country_code" => $order->get_shipping_country(),
577 );
578 $customerDetails = array(
579 "first_name" => $order->get_billing_first_name(),
580 "last_name" => $order->get_billing_last_name(),
581 "email" => $order->get_billing_email(),
582 "phone" => $order->get_billing_phone()
583 );
584 $order_data = $order->get_data();
585 $is_shipping_method_allowed = $this->routeapp_is_shipping_method_allowed(false, $order);
586
587 $itemdata = array();
588
589 foreach ($order->get_items() as $item) {
590 ## Using WC_Order_Item_Product methods ##
591 $product = $item->get_product(); // Get the WC_Product object
592
593 if ($product instanceof WC_Product) {
594
595 $isProductVirtual = (bool)$product->is_virtual();
596
597 $arrItem = array(
598 "delivery_method" => $isProductVirtual ? 'digital' :
599 ($is_shipping_method_allowed ? 'ship_to_home' : 'ship_to_store'),
600 "is_insured" => $isProductVirtual ? false :
601 ($is_shipping_method_allowed ? true : false),
602 "source_product_id" => $product->get_id(),
603 "source_id" => strval($item->get_id()),
604 "sku" => $product->get_sku(),
605 "name" => $product->get_title(),
606 "price" => $product->get_price(),
607 "quantity" => $item->get_quantity(),
608 "image_url" => (wp_get_attachment_url($product->get_image_id())) ? wp_get_attachment_url($product->get_image_id()) : null,
609 "upc" => ""// WooCommerce doesn’t provide UPC by default
610 );
611
612 array_push($itemdata, $arrItem);
613 }
614
615 }
616
617 update_post_meta( $order->get_id(), '_routeapp_route_protection', $insurance_selected );
618 update_post_meta( $order->get_id(), '_routeapp_version', ROUTEAPP_VERSION );
619
620 $orderData = array(
621 'quote_id' => $quoteId,
622 'source_order_id' => $order->get_id(),
623 'source_order_number' => $order->get_id(),
624 'subtotal' => $order->get_subtotal(),
625 'order_total' => $order->get_total(),
626 'shipping_total' => $order->get_shipping_total(),
627 'discounts_total' => $order->get_discount_total(),
628 'source_created_on' => date(DATE_ATOM, strtotime($order->get_date_created())),
629 'source_updated_on' => date(DATE_ATOM, strtotime($order->get_date_modified())),
630 'taxes' => $order_data['total_tax'],
631 'insurance_selected' => $insurance_selected,
632 'customer_details' => $customerDetails,
633 'shipping_details' => $destination,
634 'line_items' => $itemdata,
635 'currency' => $order->get_currency(),
636 'amount_covered' => $this->get_order_subtotal($order),
637 'origin' => 'woocommerce-module',
638 );
639 //if customer covers the order, pass the paid to insure with Route Fee value
640 if ($insurance_selected && $insurance_amount) {
641 $orderData['paid_to_insure'] = $insurance_amount;
642 }
643
644 return $orderData;
645 }
646
647 /**
648 * Update tracking order data sending for API
649 * @since 1.0.0
650 * @param integer $item_id
651 * @param object $order
652 * @param string $post
653 */
654 public function routeapp_update_tracking_order_api($item_id, $order, $post) {
655 if (is_null($order) || get_post_type($order) !== 'shop_order' || $post == '_edit_lock') {
656 return false;
657 }
658 $this->routeapp_shipment_tracking->update($order, $this);
659 }
660
661 /**
662 * Update tracking order item data sending for API
663 * @since 1.0.0
664 * @param integer $order_id
665 * @param object $post
666 * @param boolean $update
667 */
668 public function routeapp_update_tracking_order_item_api($item_id, $order_item_id, $post) {
669 if (is_null($order_item_id) ||
670 !is_admin() ||
671 $this->_isShippingTrackingCall($post)
672 ) {
673 return false;
674 }
675 $this->routeapp_shipment_tracking->update($order_item_id, $this);
676 }
677
678 /**
679 * Check if the post comes for shipment tracking
680 * @param $post
681 * @return bool
682 */
683 private function _isShippingTrackingCall($post) {
684 $isNotShippingTracking = $post != '_wc_shipment_tracking_items' &&
685 $post != '_bst_tracking_number' &&
686 $post != 'mimo_tracking_number' &&
687 $post != '_aftership_tracking_number' &&
688 $post != 'ywot_tracking_code' &&
689 $post != 'ph_shipment_tracking_shipping_service' &&
690 $post != 'ph_shipment_tracking_ids' &&
691 $post != 'usps_evs_label_details_array' &&
692 $post != '_order_trackno' &&
693 $post != '_order_trackno1' &&
694 $post != '_order_trackno2' &&
695 $post != '_order_trackno3' &&
696 $post != '_order_trackno4' &&
697 $post != '_vi_wot_order_item_tracking_data' &&
698 $post != 'wc_connect_labels';
699
700 return apply_filters('routeapp_meta_key_is_not_shipping_related', $isNotShippingTracking, $post);
701 }
702
703
704 public function routeapp_update_merchant_api( $merchant_id, $merchantData ) {
705 if ( is_null($merchant_id) ) {
706 return false;
707 }
708
709 $response = $this->routeapp_api_client->update_merchant( $merchant_id, $merchantData );
710 if ( is_wp_error($response) ) {
711 return false;
712 }
713
714 return true;
715 }
716
717 /**
718 * Save into order meta_data the quote from the last request made on session
719 *
720 * @param $order_id
721 */
722 public function routeapp_save_quote_to_order($order_id )
723 {
724 if (!$order_id && !WC()->session) {
725 return;
726 }
727 $order = wc_get_order($order_id);
728 $quote = get_post_meta($order->get_id(), '_routeapp_quote', true);
729 if (!$quote) {
730 $cartRef = $order->get_cart_hash();
731 if (!$cartRef) {
732 //create uniq ID for backend orders
733 $cartRef = uniqid();
734 }
735 $main_key = $this->routeapp_api_client->get_cache_api_session_key().'-'.$cartRef;
736 $key = $main_key.'-latest';
737 if (WC()->session) {
738 $cached = WC()->session->get($key);
739 if (isset($cached) && is_array($cached) && isset($cached['body'])) {
740 update_post_meta( $order->get_id(), '_routeapp_quote', $cached['body'] );
741 //save to order the configuration selected regarding order statuses
742 $acceptedOrderStatuses = get_option('routeapp_included_order_statuses') ? get_option('routeapp_included_order_statuses') : [];
743 update_post_meta( $order->get_id(), '_routeapp_included_order_statuses', json_encode($acceptedOrderStatuses));
744 $acceptedCanceledStatuses = get_option('routeapp_cancel_order_statuses') ? get_option('routeapp_cancel_order_statuses') : [];
745 update_post_meta( $order->get_id(), '_routeapp_cancel_order_statuses', json_encode($acceptedCanceledStatuses));
746
747 //clear cache!
748 WC()->session->__unset($main_key);
749 WC()->session->__unset($key);
750 }
751 }
752 }
753 }
754
755 /**
756 * Mark order on woocommerce thank you hook
757 * @since 1.0.0
758 * @param integer $order_id
759 *
760 */
761 public function routeapp_mark_order($order_id)
762 {
763 if (!$order_id) {
764 return;
765 }
766
767 $order = wc_get_order($order_id);
768
769 //order cancel action will be handled by webhook event
770 if ($order->get_status() === 'cancelled') {
771 return;
772 }
773
774 // Init the setup for readiness report
775 $this->_setup_readiness_report_check();
776
777 if (WC()->session) {
778 WC()->session->__unset('checkbox_checked');
779 }
780 }
781
782 /**
783 * Check order note (look for shipment tracking data on it)
784 * @param $noteId id
785 * @return mixed
786 */
787 public function check_order_note_by_note_id($noteId)
788 {
789 $comment = get_comment($noteId);
790 $order = wc_get_order($comment->comment_post_ID);
791 if ($order && $order->get_id()) {
792 $this->routeapp_shipment_tracking->parse_order_notes($order->get_id(), $this);
793 }
794 }
795
796 /**
797 * Check order note (look for shipment tracking data on it)
798 * @param $response WP_REST_Response
799 * @return mixed
800 */
801 public function check_order_note($response)
802 {
803 $note = $response->get_data();
804 $comment = get_comment($note['id']);
805 $order = wc_get_order($comment->comment_post_ID);
806 if ($order && $order->get_id()) {
807 $this->routeapp_shipment_tracking->parse_order_notes($order->get_id(), $this);
808 }
809 return $response;
810 }
811 /**
812 * Cancel the tracking order sending for API
813 * @since 1.0.0
814 * @param integer $order_id
815 * @param integer $tracking_number
816 * @param array $product_ids
817 */
818
819 public function routeapp_cancel_tracking_order( $order_id, $tracking_number, $product_ids ) {
820 if ( is_null($order_id) ) {
821 return false;
822 }
823 $this->routeapp_shipment_tracking->cancel($order_id, $tracking_number, $product_ids, $this);
824 }
825
826 /**
827 * Create user at Route
828 * @param $data
829 * @return array|bool|mixed|object
830 */
831 public function routeapp_create_user($data){
832 $response = $this->routeapp_api_client->create_user($data);
833
834 $this->routeapp_last_request = $response;
835
836 if ( is_wp_error( $response ) ) {
837 return false;
838 } else {
839 $body = json_decode($response['body']);
840
841 if ($response['response']['code'] != self::HTTP_CREATED_CODE) {
842 return false;
843 }
844
845 return $body;
846 }
847 }
848
849 /**
850 * Create user at Route
851 *
852 * @param $username
853 * @param $password
854 *
855 * @return array|bool|mixed|object
856 */
857 public function routeapp_user_login($username, $password){
858 $response = $this->routeapp_api_client->login_user($username, $password);
859
860 $this->routeapp_last_request = $response;
861
862 if ( is_wp_error( $response ) ) {
863 return false;
864 } else {
865 $body = json_decode($response['body']);
866
867 if ($response['response']['code'] != self::HTTP_SUCCESS_CODE) {
868 return false;
869 }
870
871 return $body;
872 }
873 }
874
875 /**
876 * Creates merchant at Route
877 *
878 * @param $data
879 * @return array|bool|mixed|object
880 */
881 public function routeapp_create_merchant($data){
882
883 $response = $this->routeapp_api_client->create_merchant($data);
884 $this->routeapp_last_request = $response;
885
886 if ( is_wp_error( $response ) ) {
887 return false;
888 } else {
889 $body = json_decode($response['body']);
890
891 if ($response['response']['code'] != self::HTTP_CREATED_CODE && $response['response']['code'] != self::HTTP_SUCCESS_CODE) {
892 return false;
893 }
894
895 return $body;
896 }
897 }
898
899 /**
900 * Creates merchant at Route
901 *
902 * @param $data
903 * @return array|bool|mixed|object
904 */
905 public function routeapp_get_merchants(){
906
907 $response = $this->routeapp_api_client->get_merchants();
908 $this->routeapp_last_request = $response;
909
910 if ( is_wp_error( $response ) ) {
911 return false;
912 } else {
913 $body = json_decode($response['body']);
914
915 if ($response['response']['code'] != self::HTTP_CREATED_CODE && $response['response']['code'] != self::HTTP_SUCCESS_CODE) {
916 return false;
917 }
918
919 return $body;
920 }
921 }
922
923 /**
924 * Activate user account
925 *
926 * @param $email
927 *
928 * @return array|bool|mixed|object
929 */
930 public function routeapp_activate_account($email){
931
932 $response = $this->routeapp_api_client->activate_account(['email' => $email]);
933 $this->routeapp_last_request = $response;
934
935 if ( is_wp_error( $response ) ) {
936 return false;
937 } else {
938 $body = json_decode($response['body']);
939
940 if ($response['response']['code'] != self::HTTP_CREATED_CODE && $response['response']['code'] != self::HTTP_SUCCESS_CODE) {
941 return false;
942 }
943
944 return $body;
945 }
946 }
947
948 public function routeapp_is_shipping_method_allowed($shipping_method=false, $order=false) {
949
950 if ($shipping_method || $order) {
951 if ($shipping_method) {
952 $shipping_method = explode(':', $shipping_method);
953 $shipping_method = $shipping_method[0];
954 }
955 if ($order) {
956 $shipping_method = @array_shift($order->get_shipping_methods());
957 if(isset($shipping_method['method_id']))
958 {
959 $shipping_method = $shipping_method['method_id'];
960 } else {
961 return false;
962 }
963 }
964 $excludedShippingMethods = $this->routeapp_get_excluded_shipping_methods();
965 if (is_array($excludedShippingMethods) && in_array($shipping_method, $excludedShippingMethods)) {
966 return false;
967 }
968 } else {
969 if (WC()->session->get('shipping_for_package_0')) {
970 foreach( WC()->session->get('shipping_for_package_0')['rates'] as $method_id => $rate ){
971 if( WC()->session->get('chosen_shipping_methods')[0] == $method_id ){
972 $shippingMethod = explode(':', $rate->id);
973 $shippingMethod = $shippingMethod[0];
974 $excludedShippingMethods = $this->routeapp_get_excluded_shipping_methods();
975 if (in_array($shippingMethod, $excludedShippingMethods)) {
976 return false;
977 }
978 }
979 }
980 }
981 }
982 return true;
983 }
984
985 public function routeapp_get_insurance_label() {
986 return __(self::ROUTE_LABEL, 'routeapp');
987 }
988
989 public function routeapp_is_opt_in() {
990 $merchant = $this->routeapp_api_client->get_merchant();
991 return isset($merchant->merchant_preferences->opt_in) && $merchant->merchant_preferences->opt_in == true;
992 }
993
994 public function routeapp_get_public_token() {
995 return get_option('routeapp_public_token');
996 }
997
998 public static function routeapp_get_taxable_class() {
999 return get_option('routeapp_taxable_class');
1000 }
1001
1002 public static function routeapp_get_checkout_hook() {
1003 return get_option('routeapp_checkout_hook') ? get_option('routeapp_checkout_hook') : self::DEFAULT_CHECKOUT_WEBHOOK;
1004 }
1005
1006 public static function routeapp_get_checkout_hook_options() {
1007 return [
1008 'woocommerce_before_checkout_form' => 'woocommerce_before_checkout_form',
1009 'woocommerce_checkout_before_customer_details' => 'woocommerce_checkout_before_customer_details',
1010 'woocommerce_checkout_billing' => 'woocommerce_checkout_billing',
1011 'woocommerce_before_checkout_billing_form' => 'woocommerce_before_checkout_billing_form',
1012 'woocommerce_after_checkout_billing_form' => 'woocommerce_after_checkout_billing_form',
1013 'woocommerce_before_checkout_registration_form' => 'woocommerce_before_checkout_registration_form',
1014 'woocommerce_after_checkout_registration_form' => 'woocommerce_after_checkout_registration_form',
1015 'woocommerce_checkout_shipping' => 'woocommerce_checkout_shipping',
1016 'woocommerce_before_checkout_shipping_form' => 'woocommerce_before_checkout_shipping_form',
1017 'woocommerce_after_checkout_shipping_form' => 'woocommerce_after_checkout_shipping_form',
1018 'woocommerce_before_order_notes' => 'woocommerce_before_order_notes',
1019 'woocommerce_after_order_notes' => 'woocommerce_after_order_notes',
1020 'woocommerce_checkout_after_customer_details' => 'woocommerce_checkout_after_customer_details',
1021 'woocommerce_checkout_before_order_review_heading' => 'woocommerce_checkout_before_order_review_heading',
1022 'woocommerce_checkout_order_review' => 'woocommerce_checkout_order_review',
1023 'woocommerce_checkout_before_order_review' => 'woocommerce_checkout_before_order_review',
1024 'woocommerce_review_order_before_cart_contents' => 'woocommerce_review_order_before_cart_contents',
1025 'woocommerce_review_order_after_cart_contents' => 'woocommerce_review_order_after_cart_contents',
1026 'woocommerce_review_order_before_shipping' => 'woocommerce_review_order_before_shipping',
1027 'woocommerce_review_order_after_shipping' => 'woocommerce_review_order_after_shipping',
1028 'woocommerce_review_order_before_order_total' => 'woocommerce_review_order_before_order_total',
1029 'woocommerce_review_order_after_order_total' => 'woocommerce_review_order_after_order_total',
1030 'woocommerce_review_order_before_payment' => 'woocommerce_review_order_before_payment',
1031 'woocommerce_review_order_before_submit' => 'woocommerce_review_order_before_submit',
1032 'woocommerce_review_order_after_submit' => 'woocommerce_review_order_after_submit',
1033 'woocommerce_review_order_after_payment' => 'woocommerce_review_order_after_payment',
1034 'woocommerce_checkout_after_order_review' => 'woocommerce_checkout_after_order_review',
1035 'woocommerce_after_checkout_form' => 'woocommerce_after_checkout_form'
1036 ];
1037 }
1038
1039 public static function routeapp_get_fee_taxable() {
1040 if (get_option('routeapp_route_fee_taxable') == 'yes') return true;
1041 return false;
1042 }
1043
1044 public function routeapp_get_excluded_shipping_methods() {
1045 return get_option('routeapp_excluded_shipping_methods') ?
1046 get_option('routeapp_excluded_shipping_methods') :
1047 [];
1048 }
1049
1050 public function routeapp_get_secret_token() {
1051 return get_option('routeapp_secret_token');
1052 }
1053
1054 public function routeapp_route_enabled_extra_columns() {
1055 return get_option('routeapp_route_enable_extra_columns') !== "no";
1056 }
1057
1058 public function routeapp_get_fresh_installed(){
1059 return get_option('routeapp_route_fresh_installed');
1060 }
1061
1062 public function routeapp_route_show_order_updates() {
1063 return get_option('routeapp_route_show_order_updates', 'yes') == 'yes';
1064 }
1065
1066 private static function routeapp_get_failed_registration(){
1067 return get_option('routeapp_failed_registration');
1068 }
1069
1070 /**
1071 * If it's installation has failed at user step creation step
1072 *
1073 * @return bool
1074 */
1075 public function has_user_creation_failed(){
1076 return self::routeapp_get_failed_registration() == Route_Setup::FAILED_REGISTRATION_STEP_USER;
1077 }
1078
1079 /**
1080 * If it's installation has failed at user step creation step due to conflict
1081 *
1082 * @return bool
1083 */
1084 public function has_user_creation_conflicted(){
1085 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);
1086 }
1087
1088 /**
1089 * If it's installation has failed at Merchant creation step
1090 *
1091 * @return bool
1092 */
1093 public function has_merchant_creation_failed(){
1094 return self::routeapp_get_failed_registration() == Route_Setup::FAILED_REGISTRATION_STEP_MERCHANT && !Route_Setup::has_valid_merchant_tokens();
1095 }
1096 /**
1097 * If it's installation has failed at Merchant creation step
1098 *
1099 * @return bool
1100 */
1101 public function has_merchant_creation_conflicted(){
1102 return self::routeapp_get_failed_registration() == Route_Setup::FAILED_REGISTRATION_STEP_MERCHANT_DUPLICATED && !Route_Setup::has_valid_merchant_tokens();
1103 }
1104
1105 /**
1106 * If it's installation has failed at Merchant creation step
1107 *
1108 * @return bool
1109 */
1110 public function successful_registration(){
1111 return self::routeapp_get_failed_registration() === Route_Setup::REGISTRATION_FIXED || self::routeapp_get_failed_registration() === Route_Setup::REGISTRATION_STEP_USER_LOGIN_SUCCESS;
1112 }
1113
1114 /**
1115 * Get registration step at setup flow
1116 * @return mixed
1117 */
1118 private static function registration_step(){
1119 return get_option(Route_Setup::REGISTRATION_STEP);
1120 }
1121
1122 /**
1123 * Check if at the merchant create flow it was already redirected
1124 * @return bool
1125 */
1126 public static function was_redirected(){
1127 return self::registration_step() == Route_Setup::REDIRECTED;
1128 }
1129
1130 /**
1131 * Set it as redirected to Route after registration is complete
1132 * @return void
1133 */
1134 public function set_as_redirected(){
1135 update_option(Route_Setup::REGISTRATION_STEP, Route_Setup::REDIRECTED);
1136 }
1137
1138 public function routeapp_register_status_route() {
1139 register_rest_route( 'route', 'status', array(
1140 'methods' => 'GET',
1141 'callback' => function() {
1142 $response = array(
1143 'version' => ROUTEAPP_VERSION,
1144 'public_token' => !empty($this->routeapp_get_public_token()) ?
1145 $this->_encodeToken($this->routeapp_get_public_token()) :
1146 false,
1147 'secret_key' => !empty($this->routeapp_get_secret_token()) ?
1148 $this->_encodeToken($this->routeapp_get_secret_token()) :
1149 false,
1150 'route_insurance_label' => $this->routeapp_get_insurance_label(),
1151 'extra_columns_enabled' => $this->routeapp_route_enabled_extra_columns(),
1152 'failed_registration' => $this->routeapp_get_failed_registration(),
1153 'base_currency' => get_woocommerce_currency(),
1154 'date' => array(
1155 'date' => date('Y-m-d H:i:s'),
1156 'timezone_type' => date('P'),
1157 'timezone' => date_default_timezone_get()
1158 ),
1159 'routeapp_route_fee_taxable' => $this->routeapp_get_fee_taxable(),
1160 'routeapp_taxable_class' => $this->routeapp_get_taxable_class(),
1161 'routeapp_excluded_shipping_methods' => $this->routeapp_get_excluded_shipping_methods(),
1162 'routeapp_route_show_order_updates' => $this->routeapp_route_show_order_updates(),
1163 'routeapp_checkout_hook' => $this->routeapp_get_checkout_hook(),
1164 'routeapp_have_webhook_secret' => $this->_routeapp_have_webhook_secret()
1165 );
1166 return rest_ensure_response( $response );
1167 },
1168 'permission_callback' => '__return_true'
1169 )
1170 );
1171 }
1172
1173 private function _routeapp_have_webhook_secret() {
1174 return get_option('_routeapp_webhooks_secret') ? 'Yes' : 'No';
1175 }
1176
1177 private function _encodeToken($token) {
1178 return substr($token, 0, 5) . '...' . substr($token, -5);
1179 }
1180
1181 public function routeapp_register_checkout_route(){
1182
1183 if (!isset(WC()->session) || !isset($_POST) || !isset($_POST['shipping_method'])) {
1184 return rest_ensure_response( [] );
1185 }
1186
1187 $shipping_method = $_POST['shipping_method'];
1188
1189 //check first if shipping method is allowed
1190 if ($shipping_method) {
1191 $show_widget = $this->routeapp_is_shipping_method_allowed($shipping_method);
1192 if (!$show_widget) {
1193 wp_send_json(['hide-widget' => true]);
1194 return;
1195 }
1196 }
1197
1198 $cart = WC()->cart;
1199 $this->checkout_route_insurance_fee($cart);
1200 $response= [
1201 'routeapp-subtotal' => round($this->get_cart_subtotal_with_only_shippable_items($cart), 2),
1202 ];
1203
1204 wp_send_json( $response );
1205 }
1206
1207 /**
1208 * Add route to recreate User and Merchant Accounts
1209 * if it has failed at the first try
1210 */
1211 public function routeapp_register_setup_route () {
1212 register_rest_route( 'route', 'recreate_user', array(
1213 'methods' => 'GET',
1214 'callback' => [Route_Setup::class, 'retry_user'],
1215 'permission_callback' => '__return_true'
1216 ) );
1217 register_rest_route( 'route', 'recreate_merchant', array(
1218 'methods' => 'GET',
1219 'callback' => [Route_Setup::class, 'retry_merchant'],
1220 'permission_callback' => '__return_true'
1221 ) );
1222 register_rest_route( 'route', 'user_login', array(
1223 'methods' => 'POST',
1224 'callback' => [Route_Setup::class, 'user_login'],
1225 'permission_callback' => '__return_true'
1226 ) );
1227 }
1228
1229 public function routeapp_last_request_has_failed(){
1230 if ( is_wp_error( $this->routeapp_last_request ) || !isset($this->routeapp_last_request)) {
1231 return true;
1232 } else {
1233 if ($this->routeapp_last_request['response']['code'] != self::HTTP_CREATED_CODE && $this->routeapp_last_request['response']['code'] != self::HTTP_SUCCESS_CODE) {
1234 return true;
1235 }
1236
1237 return false;
1238 }
1239 }
1240
1241 public function routeapp_last_request_has_success() {
1242 if ( is_wp_error( $this->routeapp_last_request ) || !isset($this->routeapp_last_request)) {
1243 return false;
1244 }
1245 return $this->routeapp_last_request['response']['code'] === self::HTTP_SUCCESS_CODE;
1246 }
1247
1248 public function routeapp_last_request_has_conflicted(){
1249 if ( is_wp_error( $this->routeapp_last_request ) || !isset($this->routeapp_last_request)) {
1250 return false;
1251 } else {
1252 if ($this->routeapp_last_request['response']['code'] == self::HTTP_CONFLICT_CODE) {
1253 return true;
1254 }
1255
1256 return false;
1257 }
1258 }
1259
1260 public function routeapp_log($event, $extraData = false) {
1261 $this->routeapp_logger->sentry_log($event, $extraData);
1262 }
1263
1264 private function _setup_readiness_report_check()
1265 {
1266 if ($this->route_app_setup_helper->can_send_compatibility_report()) {
1267 /**
1268 * API CALL for incompatibility test
1269 */
1270 $api = new Routeapp_API_Compatibility();
1271 $api->send($this->route_app_setup_helper->prepare_compatibility_data());
1272 }
1273 }
1274
1275 public function get_order_subtotal($order)
1276 {
1277 $subtotal = 0;
1278
1279 if (is_a($order, 'WC_Order')) {
1280 $order_items = $order->get_items();
1281
1282 foreach ($order_items as $order_item_id => $order_item) {
1283 if ($this->isProductVirtual($order_item)) {
1284 //if the product is not found or virtual, skip it
1285 continue;
1286 }
1287 $subtotal += $order_item->get_subtotal();
1288 }
1289 }
1290 return $subtotal;
1291 }
1292
1293 /**
1294 * Check if product inside order item is virtual, return true if product is not valid
1295 * @param $item
1296 * @return bool
1297 */
1298 public function isProductVirtual($item) {
1299 $productId = isset($item['variation_id']) && !empty($item['variation_id']) ?
1300 $item['variation_id'] :
1301 $item['product_id'];
1302
1303 $product = wc_get_product($productId);
1304 if ($product) {
1305 return $product->is_virtual();
1306 }
1307 return true;
1308 }
1309
1310 /**
1311 * Process cart item
1312 *
1313 * @param $cart_item
1314 * @param $allowed_bundle_parents
1315 * @return array
1316 */
1317 public function processCartItem($cart_item, $allowed_bundle_parents) {
1318
1319 $cart_item_subtotal = $cart_item['line_subtotal'];
1320
1321 //support for WooCommerce Product Bundles
1322 if (isset($cart_item['bundled_items'])) {
1323 if ($cart_item['data']->get_data()['price'] > 0) {
1324 //grab item price from parent bundle if exist, return directly
1325 $cart_item_subtotal = $cart_item['data']->get_data()['price'] * $cart_item['quantity'];
1326 $data = [
1327 'id' => strval($cart_item['product_id']),
1328 'quantity' => intval($cart_item['quantity']),
1329 'unit_price' => strval(round($cart_item_subtotal / $cart_item['quantity'], 2))
1330 ];
1331 return ['allowed_bundle_parents' => $allowed_bundle_parents, 'data' => $data];
1332 } else{
1333 //add bundle parent key to allowed array
1334 $allowed_bundle_parents[] = $cart_item['key'];
1335 }
1336 }
1337
1338 if ($this->isProductVirtual($cart_item)) {
1339 //if the product is not found or virtual, skip it
1340 return ['allowed_bundle_parents' => $allowed_bundle_parents];
1341 }
1342
1343 if (isset($cart_item['bundled_by'])) {
1344 if (!in_array($cart_item['bundled_by'], $allowed_bundle_parents)) {
1345 //skip bundle item children (price is on parent)
1346 return ['allowed_bundle_parents' => $allowed_bundle_parents];
1347 } else {
1348 //grab subtotal from bundle child
1349 $cart_item_subtotal = $cart_item['data']->get_data()['price'] * $cart_item['quantity'];
1350 }
1351 }
1352
1353 return [
1354 'allowed_bundle_parents' => $allowed_bundle_parents,
1355 'data' => [
1356 'id' => strval($cart_item['product_id']),
1357 'quantity' => intval($cart_item['quantity']),
1358 'unit_price' => strval(round($cart_item_subtotal / $cart_item['quantity'], 2))
1359 ]
1360 ];
1361 }
1362
1363 /**
1364 * Get cart item subtotal
1365 *
1366 * @param $cart_item
1367 * @param $allowed_bundle_parents
1368 * @return array
1369 */
1370 public function getCartItemSubtotal($cart_item, $allowed_bundle_parents) {
1371
1372 $cart_item_subtotal = $cart_item['line_subtotal'];
1373
1374 //support for WooCommerce Product Bundles
1375 if (isset($cart_item['bundled_items'])) {
1376 if ($cart_item['data']->get_data()['price'] > 0) {
1377 //grab item price from parent bundle if exist, return directly
1378 $cart_item_subtotal = $cart_item['data']->get_data()['price'] * $cart_item['quantity'];
1379 return ['allowed_bundle_parents' => $allowed_bundle_parents, 'subtotal' => $cart_item_subtotal];
1380 } else{
1381 //add bundle parent key to allowed array
1382 $allowed_bundle_parents[] = $cart_item['key'];
1383 }
1384 }
1385
1386 if ($this->isProductVirtual($cart_item)) {
1387 //if the product is not found or virtual, skip it
1388 return ['allowed_bundle_parents' => $allowed_bundle_parents];
1389 }
1390
1391 if (isset($cart_item['bundled_by'])) {
1392 if (!in_array($cart_item['bundled_by'], $allowed_bundle_parents)) {
1393 //skip bundle item children (price is on parent)
1394 return ['allowed_bundle_parents' => $allowed_bundle_parents];
1395 } else {
1396 //grab subtotal from bundle child
1397 $cart_item_subtotal = $cart_item['data']->get_data()['price'] * $cart_item['quantity'];
1398 }
1399 }
1400 return ['allowed_bundle_parents' => $allowed_bundle_parents, 'subtotal' => $cart_item_subtotal];
1401 }
1402
1403 /**
1404 * get cart shippable items to grab quote on RouteApp
1405 *
1406 * @param $cart
1407 * @param string $object
1408 * @return array
1409 */
1410 public function get_cart_shippable_items($cart, $object='cart')
1411 {
1412 $cartItems = [];
1413 if (!$cart) {
1414 return $cartItems;
1415 }
1416 $allowed_bundle_parents = [];
1417 if ($object=='cart') {
1418 foreach ( $cart->get_cart() as $cart_item ) {
1419 $cartItemData = $this->processCartItem($cart_item, $allowed_bundle_parents);
1420 $allowed_bundle_parents = $cartItemData['allowed_bundle_parents'];
1421 if (isset($cartItemData['data'])) {
1422 $cartItems[] = $cartItemData['data'];
1423 }
1424 }
1425 } else {
1426 foreach ( $cart->get_items() as $cart_item ) {
1427 $cartItemData = $this->processCartItem($cart_item, $allowed_bundle_parents);
1428 $allowed_bundle_parents = $cartItemData['allowed_bundle_parents'];
1429 if (isset($cartItemData['data'])) {
1430 $cartItems[] = $cartItemData['data'];
1431 }
1432 }
1433 }
1434
1435 return $cartItems;
1436 }
1437
1438 /**
1439 * get subtotal for shippable items only
1440 *
1441 * @param $cart
1442 * @param string $object
1443 * @return float|int|mixed
1444 */
1445 public function get_cart_subtotal_with_only_shippable_items($cart, $object='cart')
1446 {
1447 $subtotal = 0;
1448 if (!$cart) {
1449 return $subtotal;
1450 }
1451 $allowed_bundle_parents = [];
1452
1453 if ($object=='cart') {
1454 foreach ( $cart->get_cart() as $cart_item ) {
1455 $itemSubtotal = $this->getCartItemSubtotal($cart_item, $allowed_bundle_parents);
1456 $allowed_bundle_parents = $itemSubtotal['allowed_bundle_parents'];
1457 if (isset($itemSubtotal['subtotal'])) {
1458 $subtotal += $itemSubtotal['subtotal'];
1459 }
1460 }
1461 } else {
1462 foreach ( $cart->get_items() as $cart_item ) {
1463 $itemSubtotal = $this->getCartItemSubtotal($cart_item, $allowed_bundle_parents);
1464 $allowed_bundle_parents = $itemSubtotal['allowed_bundle_parents'];
1465 if (isset($itemSubtotal['subtotal'])) {
1466 $subtotal += $itemSubtotal['subtotal'];
1467 }
1468 }
1469 }
1470 return $subtotal;
1471 }
1472
1473 /**
1474 * Edit Avatax fee line (send tax class name instead item_id)
1475 */
1476 public function routeapp_edit_avatax_fee($fee_line, $fee = null)
1477 {
1478 if (isset($fee_line['description']) && $fee_line['description'] == $this->routeapp_get_insurance_label()) {
1479 $fee_line['itemCode'] = $this->routeapp_get_taxable_class();
1480 }
1481
1482 return $fee_line;
1483 }
1484
1485
1486 /**
1487 * This method adds to xml export an SKU tag in order to identify
1488 * Route Protection to ShipStation integration
1489 *
1490 * @param $order_xml
1491 * @return mixed
1492 */
1493 public function routeapp_add_sku_to_route_node($order_xml){
1494
1495 if ($order_xml instanceof DOMElement){
1496
1497 $items = $order_xml->getElementsByTagName('Items');
1498
1499 foreach ($items as $itemNode) {
1500 foreach ($itemNode->getElementsByTagName('Item') as $item) {
1501 foreach ($item->getElementsByTagName('Name') as $name) {
1502 if (trim($name->nodeValue) === self::ROUTE_LABEL)
1503 {
1504 $this->xml_append($item, 'SKU', self::ROUTE_PROTECTION_SKU);
1505 }
1506 }
1507 }
1508 }
1509
1510 }
1511
1512 return $order_xml;
1513 }
1514
1515 /**
1516 * Append XML as cdata
1517 */
1518 private function xml_append( $append_to, $name, $value, $cdata = true ) {
1519 $data = $append_to->appendChild( $append_to->ownerDocument->createElement( $name ) );
1520 if ( $cdata ) {
1521 $data->appendChild( $append_to->ownerDocument->createCDATASection( $value ) );
1522 } else {
1523 $data->appendChild( $append_to->ownerDocument->createTextNode( $value ) );
1524 }
1525 }
1526 }
1527
1528