PluginProbe
Route ‑ Shipping Protection / 2.4.16
Route ‑ Shipping Protection v2.4.16
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 / includes / class-routeapp-cron-schedules.php

class-routeapp-cron-schedules.php in Route ‑ Shipping Protection 2.4.16, at includes/class-routeapp-cron-schedules.php

798 lines 28.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * WooCommerce Routeapp Cron Schedules
5 *
6 * @link https://route.com/
7 * @since 1.0.0
8 *
9 * @package Routeapp
10 * @subpackage Routeapp/includes
11 */
12
13 use Automattic\WooCommerce\Utilities\OrderUtil;
14
15 class Routeapp_Cron_Schedules
16 {
17
18 const RECONCILE_BATCH_SIZE = 100;
19 const RECONCILE_MAX_AGE_DAYS = 30;
20 const RECONCILE_LOCK_TTL = 900;
21 const RECONCILE_MAX_RUNTIME = 20;
22 const RECONCILE_MAX_BATCHES = 50;
23 const RECONCILE_CONTINUE_DELAY = 60;
24 const MISSING_ORDERS_LOCK_KEY = 'routeapp_missing_orders_lock';
25 const MISSING_SHIPMENTS_LOCK_KEY = 'routeapp_missing_shipments_lock';
26 const MISSING_ORDERS_CONTINUE_HOOK = 'routeapp_check_for_missing_orders_continue';
27 const MISSING_SHIPMENTS_CONTINUE_HOOK = 'routeapp_check_for_missing_shipments_continue';
28
29 private $routeapp_webhooks;
30
31 /**
32 * Initialize the class and set its properties.
33 *
34 * @since 1.0.0
35 */
36 public function __construct() {
37 $this->routeapp_webhooks = new Routeapp_Webhooks();
38 add_filter('cron_schedules', array($this, 'wpcron_schedule'));
39 add_action('routeapp_check_for_missing_orders', array($this, 'routeapp_validation_worker'));
40 add_action('routeapp_check_for_missing_shipments', array($this, 'routeapp_missing_shipments_worker'));
41 add_action('routeapp_check_for_invalid_webhooks', array($this, 'routeapp_webhooks_validator'));
42 add_action(self::MISSING_ORDERS_CONTINUE_HOOK, array($this, 'routeapp_validation_worker'));
43 add_action(self::MISSING_SHIPMENTS_CONTINUE_HOOK, array($this, 'routeapp_missing_shipments_worker'));
44 $this->init();
45 }
46
47 /**
48 * Add wp_cron Minutely and Biminutely Interval Schedules if not is scheduled
49 *
50 * @since 1.0.3
51 * @return void
52 */
53 public function init() {
54 $this->set_schedule_event('routeapp_daily', 'routeapp_check_for_missing_orders');
55 $this->set_schedule_event('routeapp_5_hours', 'routeapp_check_for_missing_shipments');
56 $this->set_schedule_event('routeapp_5_hours', 'routeapp_check_for_invalid_webhooks');
57 }
58
59 public function set_schedule_event($event, $action) {
60 if (!wp_next_scheduled($action)) {
61 wp_schedule_event(time(), $event, $action);
62 };
63 }
64
65 /**
66 * Orders processed per cron run. Capped to avoid unbounded load.
67 *
68 * @return int
69 */
70 public static function get_reconcile_batch_size() {
71 $size = (int) apply_filters('routeapp_reconcile_batch_size', self::RECONCILE_BATCH_SIZE);
72 if ($size < 1) {
73 return 1;
74 }
75 return min(self::RECONCILE_BATCH_SIZE, $size);
76 }
77
78 /**
79 * Seconds of work allowed per worker invocation.
80 *
81 * @return int
82 */
83 public static function get_reconcile_max_runtime() {
84 $seconds = (int) apply_filters('routeapp_reconcile_max_runtime', self::RECONCILE_MAX_RUNTIME);
85 if ( $seconds < 5 ) {
86 return 5;
87 }
88 return min(55, $seconds);
89 }
90
91 /**
92 * Max 100-order pages per invocation.
93 *
94 * @return int
95 */
96 public static function get_reconcile_max_batches() {
97 $batches = (int) apply_filters('routeapp_reconcile_max_batches', self::RECONCILE_MAX_BATCHES);
98 return $batches < 1 ? 1 : $batches;
99 }
100
101 /**
102 * Queue another worker run soon without replacing the daily/5-hour schedule.
103 *
104 * @param string $hook One-shot continue hook.
105 * @return void
106 */
107 public static function schedule_reconcile_continue($hook) {
108 if ( function_exists('wp_next_scheduled') && wp_next_scheduled($hook) ) {
109 return;
110 }
111 if ( function_exists('wp_schedule_single_event') ) {
112 wp_schedule_single_event(time() + self::RECONCILE_CONTINUE_DELAY, $hook);
113 }
114 }
115
116 /**
117 * Rolling window in days for reconciliation queries.
118 *
119 * @return int
120 */
121 public static function get_reconcile_max_age_days() {
122 $days = (int) apply_filters('routeapp_reconcile_max_age_days', self::RECONCILE_MAX_AGE_DAYS);
123 return $days < 1 ? self::RECONCILE_MAX_AGE_DAYS : $days;
124 }
125
126 /**
127 * Lower bound for reconcile queries: the later of plugin install date and the rolling window.
128 *
129 * @param int|null $now Unix timestamp to treat as now. Defaults to time().
130 * @return string Y-m-d
131 */
132 public static function get_reconcile_date_after( $now = null ) {
133 $now_ts = ( null === $now ) ? time() : (int) $now;
134 $window_ts = strtotime( '-' . self::get_reconcile_max_age_days() . ' days', $now_ts );
135 $window_start = self::format_site_date( $window_ts );
136
137 $install_ts = self::parse_reconcile_date_timestamp( get_option( '_routeapp_last_install_date' ) );
138 if ( ! $install_ts ) {
139 return $window_start;
140 }
141
142 return $install_ts > $window_ts
143 ? self::format_site_date( $install_ts )
144 : $window_start;
145 }
146
147 /**
148 * Format a unix timestamp as Y-m-d in the site timezone.
149 *
150 * @param int $timestamp Unix timestamp.
151 * @return string
152 */
153 public static function format_site_date( $timestamp ) {
154 if ( function_exists( 'wp_date' ) ) {
155 return wp_date( 'Y-m-d', $timestamp );
156 }
157 if ( function_exists( 'date_i18n' ) ) {
158 return date_i18n( 'Y-m-d', $timestamp );
159 }
160 return date( 'Y-m-d', $timestamp );
161 }
162
163 /**
164 * Parse an install-date option (Y-m-d or datetime) to a unix timestamp.
165 *
166 * @param mixed $value Option value.
167 * @return int
168 */
169 public static function parse_reconcile_date_timestamp( $value ) {
170 if ( ! is_string( $value ) || $value === '' ) {
171 return 0;
172 }
173 $timestamp = strtotime( $value );
174 return $timestamp ? (int) $timestamp : 0;
175 }
176
177 /**
178 * Persist order meta without firing order.updated / webhook delivery.
179 *
180 * update_meta_data() is in-memory only. save_meta_data() writes via the
181 * data store (add_meta/update_meta) and does not call data_store->update(),
182 * so it does not fire woocommerce_update_order. $order->save() does, and
183 * that is what queues Action Scheduler webhook jobs.
184 *
185 * @param \WC_Order $order Order object.
186 * @return void
187 */
188 public static function save_order_meta_only($order) {
189 if (!$order || !is_callable(array($order, 'save_meta_data'))) {
190 return;
191 }
192 $order->save_meta_data();
193 }
194
195 /**
196 * @return bool
197 */
198 public static function is_hpos_enabled() {
199 return class_exists('Automattic\WooCommerce\Utilities\OrderUtil')
200 && OrderUtil::custom_orders_table_usage_is_enabled();
201 }
202
203 /**
204 * Acquire a cron lock with an atomic INSERT on wp_options (unique option_name).
205 * Expired locks are stolen only if CAST(option_value) < now (compare-and-swap).
206 *
207 * @param string $key Option name.
208 * @return bool
209 */
210 public static function acquire_cron_lock($key) {
211 global $wpdb;
212
213 if ( ! isset( $wpdb ) || ! is_object( $wpdb ) ) {
214 return false;
215 }
216
217 $now = time();
218 $expires = $now + self::RECONCILE_LOCK_TTL;
219
220 $result = $wpdb->query(
221 $wpdb->prepare(
222 "INSERT INTO {$wpdb->options} (option_name, option_value, autoload) VALUES (%s, %s, 'no')
223 ON DUPLICATE KEY UPDATE option_value = IF(CAST(option_value AS UNSIGNED) < %d, VALUES(option_value), option_value)",
224 $key,
225 (string) $expires,
226 $now
227 )
228 );
229
230 if ( false === $result ) {
231 self::log_cron_lock( 'skipped cron run, lock query failed: ' . $key );
232 return false;
233 }
234
235 self::clear_cron_lock_option_cache( $key );
236
237 if ( (int) $wpdb->rows_affected < 1 ) {
238 self::log_cron_lock( 'skipped cron run, lock still held: ' . $key . ' (TTL ' . self::RECONCILE_LOCK_TTL . 's)' );
239 return false;
240 }
241
242 self::register_cron_lock_fatal_release($key);
243 return true;
244 }
245
246 /**
247 * @param string $key Option name.
248 * @return void
249 */
250 public static function release_cron_lock($key) {
251 delete_option($key);
252 }
253
254 /**
255 * Release the lock if a fatal error skips the worker's finally block.
256 *
257 * @param string $key Option name.
258 * @return void
259 */
260 public static function register_cron_lock_fatal_release($key) {
261 register_shutdown_function(function () use ($key) {
262 $error = error_get_last();
263 if ( ! $error || ! in_array( $error['type'], array( E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR ), true ) ) {
264 return;
265 }
266 delete_option($key);
267 self::log_cron_lock( 'released cron lock after fatal: ' . $key . ' — ' . $error['message'] );
268 });
269 }
270
271 /**
272 * @param string $key Option name.
273 * @return void
274 */
275 public static function clear_cron_lock_option_cache($key) {
276 if ( ! function_exists( 'wp_cache_delete' ) ) {
277 return;
278 }
279 wp_cache_delete( $key, 'options' );
280 $notoptions = wp_cache_get( 'notoptions', 'options' );
281 if ( is_array( $notoptions ) && isset( $notoptions[ $key ] ) ) {
282 unset( $notoptions[ $key ] );
283 wp_cache_set( 'notoptions', $notoptions, 'options' );
284 }
285 }
286
287 /**
288 * @param string $message Log message.
289 * @return void
290 */
291 public static function log_cron_lock($message) {
292 if ( function_exists( 'error_log' ) ) {
293 error_log( '[routeapp] ' . $message );
294 }
295 }
296
297 /**
298 * @param $routeapp_client
299 * @param array $trackingPayload
300 * @param $order
301 * @param $params
302 * @return bool
303 */
304 public static function resend_shipment(array $trackingPayload)
305 {
306 $routeapp_client = Routeapp_API_Client::getInstance();
307
308 try {
309 $response = $routeapp_client->create_shipment($trackingPayload['tracking_number'], $trackingPayload);
310
311 if (!is_wp_error($response) &&
312 (wp_remote_retrieve_response_code($response) == 409 ||
313 wp_remote_retrieve_response_code($response) == 201)
314 ) {
315 return true;
316 }
317
318 return false;
319
320 } catch (Exception $exception) {
321 self::get_route_public_instance()->routeapp_log($exception, [
322 'endpoint' => 'shipments',
323 'params' => $trackingPayload,
324 'method' => 'POST'
325 ]);
326 return false;
327 }
328 }
329
330
331 /**
332 * Add wp_cron Daily, Daily Interval Schedules
333 *
334 * @since 1.0.3
335 * @param array $schedules
336 * @return array $schedules
337 */
338
339 public function wpcron_schedule($schedules) {
340
341 $schedules['routeapp_daily'] = array(
342 'interval' => 60 * 60 * 24,
343 'display' => __('Once Daily', 'routeapp')
344 );
345
346 $schedules['routeapp_5_hours'] = array(
347 'interval' => 60 * 60 * 5,
348 'display' => __('Every 5 hours', 'routeapp')
349 );
350
351 return $schedules;
352 }
353
354 /**
355 *
356 * Check if the order is created on Route API.
357 *
358 */
359 public static function has_route_order_id( $order_id ) {
360 return self::get_meta_data( $order_id, '_routeapp_order_id');
361 }
362
363 /**
364 * get meta data if custom order table is enabled
365 * @param $order_id
366 * @param $post_meta
367 * @return array|mixed|string
368 */
369 public static function get_meta_data( $order_id, $post_meta ) {
370 if ( class_exists('Automattic\WooCommerce\Utilities\OrderUtil')
371 && OrderUtil::custom_orders_table_usage_is_enabled() ) {
372 $order = wc_get_order($order_id);
373 return $order->get_meta($post_meta);
374 } else {
375 // Traditional CPT-based orders are in use.
376 return get_post_meta($order_id, $post_meta, true);
377 }
378 }
379
380
381 /**
382 *
383 * Get the Route Public class instance
384 *
385 */
386 public static function get_route_public_instance() {
387 global $routeapp_public;
388 return $routeapp_public;
389 }
390
391 /**
392 *
393 * Set the 'routeapp_shipment_cron_api_called' as a flag to avoid multiple requests to Route
394 *
395 */
396 public static function has_reconcile_shipping_triggered( $order_id ) {
397 return self::get_meta_data( $order_id, 'routeapp_shipment_cron_api_called') == 'success';
398 }
399
400 /**
401 *
402 * Get the 'routeapp_shipment_cron_api_called' as a flag see if the order has already be sent to Route
403 *
404 */
405 public static function set_reconcile_shipping_triggered( $order_id ) {
406 self::update_post_meta_value( $order_id, 'routeapp_shipment_cron_api_called', 'success' );
407 }
408
409 /**
410 * Update post meta value
411 *
412 * @param $order_id
413 * @param $post_meta
414 * @param $value
415 */
416 public static function update_post_meta_value( $order_id, $post_meta, $value) {
417 if ( self::is_hpos_enabled() ) {
418 $order = wc_get_order($order_id);
419 if ( ! $order ) {
420 return;
421 }
422 $order->update_meta_data($post_meta, $value );
423 self::save_order_meta_only( $order );
424 } else {
425 // Traditional CPT-based orders are in use.
426 update_post_meta( $order_id, $post_meta, $value );
427 }
428 }
429
430 /**
431 * Periodic Worker Triggering Missing Shippings on Route
432 *
433 * @return void
434 */
435 public static function routeapp_reconcile_shippings($orders)
436 {
437 $routeapp_client = Routeapp_API_Client::getInstance();
438 if (count($orders) > 0) {
439 foreach ($orders as $order) {
440
441 if (!$order || self::has_reconcile_shipping_triggered($order->get_id()) || !self::has_route_order_id($order->get_id())) {
442 continue;
443 }
444
445 try {
446
447 $trackingPayloads = self::format_shipping_data_api($order->get_id());
448 $tracking_numbers = array();
449 foreach ($trackingPayloads as $trackingPayload){
450 $shipmentResponse = $routeapp_client->get_shipment($trackingPayload['tracking_number'], $order->get_id());
451 $tracking_numbers[] = $trackingPayload['tracking_number'];
452 if (is_wp_error($shipmentResponse) || (isset($shipmentResponse['response']['code']) && $shipmentResponse['response']['code'] == 200)) {
453 continue;
454 }
455
456 self::resend_shipment($trackingPayload);
457 }
458
459 //save on post_meta the tracking ids
460 if (!empty($tracking_numbers)) {
461 $tracking_numbers = implode(Routeapp_WooCommerce_Tracking_Provider::SEPARATOR_PIPE, $tracking_numbers);
462 self::update_post_meta_value($order->get_id(), 'routeapp_shipment_tracking_number', $tracking_numbers);
463 }
464
465 self::set_reconcile_shipping_triggered($order->get_id());
466
467 } catch (Exception $exception) {
468 continue;
469 }
470 }
471 }
472 }
473
474
475 /**
476 * create shipping data sending for API
477 * @since 1.0.0
478 * @param integer $order_id
479 */
480 public static function format_shipping_data_api($order_id)
481 {
482 $order = wc_get_order($order_id);
483
484 if (!$order) return [];
485
486 $tracking_number = self::get_meta_data( $order->get_id(), 'routeapp_shipment_tracking_number');
487 $routeapp_shipment_tracking = new Routeapp_Shipment_Tracking();
488 $courier_id = $routeapp_shipment_tracking->get_shipping_provider_name($order_id);
489 // Same ID resolution as shipment create / webhook order-create.
490 $product_ids = Routeapp_WooCommerce_Common_Tracking_Provider::get_order_products_from_order( $order );
491
492 if (strpos($tracking_number, Routeapp_WooCommerce_Tracking_Provider::SEPARATOR_PIPE) > 0) {
493 $tracking_numbers = explode(Routeapp_WooCommerce_Tracking_Provider::SEPARATOR_PIPE, $tracking_number);
494 } elseif (strpos($tracking_number, Routeapp_WooCommerce_Tracking_Provider::SEPARATOR_COMMA) > 0) {
495 $tracking_numbers = explode(Routeapp_WooCommerce_Tracking_Provider::SEPARATOR_COMMA, $tracking_number);
496 }else{
497 $tracking_numbers = [$tracking_number];
498 }
499
500 $trackings = [];
501
502 foreach ($tracking_numbers as $tracking){
503 if ($tracking!='') {
504 $trackings[] = [
505 'source_order_id' => $order->get_id(),
506 'source_product_ids' => $product_ids,
507 'courier_id' => is_string($courier_id) ? $courier_id : $courier_id[$tracking],
508 'tracking_number' => $tracking,
509 ];
510 }
511 }
512 if(empty($trackings[0])) {
513 //we don't have shipping on our post_meta yet, try to get the shipping data from third party module
514 $trackings = $routeapp_shipment_tracking->get_shipping_info($order_id);
515 }
516
517 return $trackings;
518 }
519
520
521 /**
522 * Periodic Worker Triggering Transaction Validation Jobs on Route
523 *
524 * @return void
525 */
526 function routeapp_validation_worker() {
527 if ( ! self::acquire_cron_lock( self::MISSING_ORDERS_LOCK_KEY ) ) {
528 return;
529 }
530
531 try {
532 $acceptedOrderStatuses = get_option('routeapp_included_order_statuses') ? get_option('routeapp_included_order_statuses') : [];
533 if ( ! is_array( $acceptedOrderStatuses ) || empty( $acceptedOrderStatuses ) ) {
534 return;
535 }
536
537 $deadline = time() + self::get_reconcile_max_runtime();
538 $max_batches = self::get_reconcile_max_batches();
539 $batch_size = self::get_reconcile_batch_size();
540 $processed_batches = 0;
541 $more = false;
542
543 do {
544 $args = array(
545 'type' => 'shop_order',
546 'limit' => $batch_size,
547 'status' => $acceptedOrderStatuses,
548 'date_created' => '>=' . self::get_reconcile_date_after(),
549 'orderby' => 'date',
550 'order' => 'DESC',
551 'return' => 'ids',
552 'meta_query' => array(
553 array(
554 'key' => '_routeapp_order_id',
555 'compare' => 'NOT EXISTS'
556 ),
557 )
558 );
559
560 $order_ids = wc_get_orders($args);
561 if ( ! is_array( $order_ids ) || empty( $order_ids ) ) {
562 $more = false;
563 break;
564 }
565
566 self::routeapp_run_order_reconcile( self::hydrate_orders( $order_ids ) );
567 $processed_batches++;
568 $more = count( $order_ids ) >= $batch_size;
569 } while ( $more && $processed_batches < $max_batches && time() < $deadline );
570
571 if ( $more ) {
572 self::schedule_reconcile_continue( self::MISSING_ORDERS_CONTINUE_HOOK );
573 }
574 } finally {
575 self::release_cron_lock( self::MISSING_ORDERS_LOCK_KEY );
576 }
577 }
578
579 /**
580 * Load WC_Order objects from IDs (or pass through existing objects).
581 *
582 * @param array $order_ids Order IDs or order objects.
583 * @return array
584 */
585 public static function hydrate_orders( $order_ids ) {
586 $orders = array();
587 if ( ! is_array( $order_ids ) ) {
588 return $orders;
589 }
590 foreach ( $order_ids as $order_id ) {
591 if ( is_object( $order_id ) && is_callable( array( $order_id, 'get_id' ) ) ) {
592 $orders[] = $order_id;
593 continue;
594 }
595 $order = wc_get_order( $order_id );
596 if ( $order ) {
597 $orders[] = $order;
598 }
599 }
600 return $orders;
601 }
602
603 /**
604 * Update order status meta (_routeapp_included_order_statuses, _routeapp_cancel_order_statuses)
605 * only if they don't exist or are different from current options.
606 *
607 * @param \WC_Order $order The order to update.
608 * @param bool $auto_save If true (default), persists meta when HPOS and meta changed
609 * without firing order.updated. Set to false when the caller
610 * will persist (e.g. persist_route_order_meta, massiveOrderSave).
611 * @return void
612 */
613 public static function updateOrderStatusMetaIfNeeded( $order, $auto_save = true ) {
614 if ( ! $order ) {
615 return;
616 }
617 $routeapp_public = self::get_route_public_instance();
618 if ( ! $routeapp_public ) {
619 return;
620 }
621 $is_hpos = self::is_hpos_enabled();
622 $acceptedOrderStatuses = get_option( 'routeapp_included_order_statuses' ) ? get_option( 'routeapp_included_order_statuses' ) : array();
623 $acceptedCanceledStatuses = get_option( 'routeapp_cancel_order_statuses' ) ? get_option( 'routeapp_cancel_order_statuses' ) : array();
624
625 $updated = false;
626 $updated |= $routeapp_public->updateRouteOrderMetaData( $order, $is_hpos, '_routeapp_included_order_statuses', $acceptedOrderStatuses );
627 $updated |= $routeapp_public->updateRouteOrderMetaData( $order, $is_hpos, '_routeapp_cancel_order_statuses', $acceptedCanceledStatuses );
628
629 if ( $updated && $is_hpos && $auto_save ) {
630 self::save_order_meta_only( $order );
631 }
632 }
633
634 /**
635 * Update order post_meta with route order data
636 *
637 * @param $order
638 * @param $routeOrder
639 * @return void
640 */
641 public static function updateOrderPostMeta($order, $routeOrder) {
642 //order was found, save route order id in db
643 $routeOrder = json_decode($routeOrder['body'], true);
644 $routeCharge = $routeOrder['insured_status'] == 'insured_selected' ? $routeOrder['paid_to_insure'] : '';
645 $protected = !empty($routeCharge) ? 1 : 0;
646 self::persist_route_order_meta( $order, $routeOrder['id'], $routeCharge, $protected );
647 }
648
649 /**
650 * Write Route order metadata without firing order.updated webhooks.
651 *
652 * @param \WC_Order $order Order object.
653 * @param mixed $route_order_id Route order id.
654 * @param mixed $route_charge Charge amount or empty.
655 * @param mixed $protected Protection flag.
656 * @return void
657 */
658 public static function persist_route_order_meta( $order, $route_order_id, $route_charge, $protected ) {
659 if ( ! $order ) {
660 return;
661 }
662 if ( self::is_hpos_enabled() ) {
663 $order->update_meta_data( '_routeapp_order_id', $route_order_id );
664 $order->update_meta_data( '_routeapp_route_charge', $route_charge );
665 $order->update_meta_data( '_routeapp_route_protection', $protected );
666 self::updateOrderStatusMetaIfNeeded( $order, false );
667 self::save_order_meta_only( $order );
668 return;
669 }
670 update_post_meta( $order->get_id(), '_routeapp_order_id', $route_order_id );
671 update_post_meta( $order->get_id(), '_routeapp_route_charge', $route_charge );
672 update_post_meta( $order->get_id(), '_routeapp_route_protection', $protected );
673 self::updateOrderStatusMetaIfNeeded( $order );
674 }
675
676 public static function routeapp_run_order_reconcile($orders) {
677 $routeapp_client = Routeapp_API_Client::getInstance();
678 if (count($orders) > 0) {
679
680 foreach ($orders as $order) {
681 //check if order exists on Route side
682 $getOrderResponse = $routeapp_client->get_order($order->get_id());
683
684 if (is_wp_error($getOrderResponse) || !in_array($getOrderResponse['response']['code'], [200, 404])) {
685 continue;
686 }
687
688 if ($getOrderResponse['response']['code'] == 200 ) {
689 //order was found, save route order id in db
690 self::updateOrderPostMeta($order, $getOrderResponse);
691 } else {
692 //order not found
693 //create from our side
694 $routeapp_public = self::get_route_public_instance();
695 if (!$routeapp_public->routeapp_is_shipping_method_allowed(false, $order)) continue;
696 $orderData = $routeapp_public->create_order_data_api($order->get_id());
697 $response = $routeapp_client->create_order($orderData);
698 if (!is_wp_error( $response )) {
699 $body = json_decode( $response['body'] );
700 try {
701 if ($response['response']['code'] > 201 && $response['response']['code'] < 409) {
702 $note = 'Route API Error while posting data. Errorcode- '.$response['response']['code'];
703 throw new Exception($note);
704 } else if (!empty($body->id) && !empty($body->order_number) || $response['response']['code'] == 409) {
705
706 $routeCharge = $body->insured_status == 'insured_selected' ? $body->paid_to_insure : '';
707 $protected = !empty($routeCharge) ? 1 : 0;
708 self::persist_route_order_meta( $order, $body->id, $routeCharge, $protected );
709 }
710 } catch(Exception $exception) {
711 $routeapp_public = self::get_route_public_instance();
712 $extraData = array(
713 'params' => $orderData,
714 'method' => 'POST',
715 'endpoint' => 'orders'
716 );
717 $routeapp_public->routeapp_log($exception, $extraData);
718 }
719 }
720 }
721 }
722 }
723 }
724
725 /**
726 * Periodic Worker that checks Route Webhooks validations
727 *
728 *
729 * @return void
730 */
731 function routeapp_webhooks_validator() {
732 $this->routeapp_webhooks->upsert_webhooks();
733 }
734
735 public function routeapp_missing_shipments_worker() {
736 if ( ! self::acquire_cron_lock( self::MISSING_SHIPMENTS_LOCK_KEY ) ) {
737 return;
738 }
739
740 try {
741 $acceptedOrderStatuses = get_option('routeapp_included_order_statuses') ? get_option('routeapp_included_order_statuses') : [];
742 $acceptedCanceledStatuses = get_option('routeapp_cancel_order_statuses') ? get_option('routeapp_cancel_order_statuses') : [];
743 $statuses = array_merge(
744 is_array( $acceptedOrderStatuses ) ? $acceptedOrderStatuses : array(),
745 is_array( $acceptedCanceledStatuses ) ? $acceptedCanceledStatuses : array()
746 );
747 if ( empty( $statuses ) ) {
748 return;
749 }
750
751 $deadline = time() + self::get_reconcile_max_runtime();
752 $max_batches = self::get_reconcile_max_batches();
753 $batch_size = self::get_reconcile_batch_size();
754 $processed_batches = 0;
755 $more = false;
756
757 do {
758 $args = array(
759 'type' => 'shop_order',
760 'limit' => $batch_size,
761 'status' => $statuses,
762 'date_created' => '>=' . self::get_reconcile_date_after(),
763 'orderby' => 'date',
764 'order' => 'DESC',
765 'return' => 'ids',
766 'meta_query' => array(
767 array(
768 'key' => 'routeapp_shipment_tracking_number',
769 'value' => '',
770 'compare' => '!='
771 ),
772 array(
773 'key' => 'routeapp_shipment_cron_api_called',
774 'compare' => 'NOT EXISTS'
775 ),
776 )
777 );
778
779 $order_ids = wc_get_orders($args);
780 if ( ! is_array( $order_ids ) || empty( $order_ids ) ) {
781 $more = false;
782 break;
783 }
784
785 self::routeapp_reconcile_shippings( self::hydrate_orders( $order_ids ) );
786 $processed_batches++;
787 $more = count( $order_ids ) >= $batch_size;
788 } while ( $more && $processed_batches < $max_batches && time() < $deadline );
789
790 if ( $more ) {
791 self::schedule_reconcile_continue( self::MISSING_SHIPMENTS_CONTINUE_HOOK );
792 }
793 } finally {
794 self::release_cron_lock( self::MISSING_SHIPMENTS_LOCK_KEY );
795 }
796 }
797 }
798