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

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

477 lines 18.5 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 private $routeapp_webhooks;
19
20 /**
21 * Initialize the class and set its properties.
22 *
23 * @since 1.0.0
24 */
25 public function __construct() {
26 $this->routeapp_webhooks = new Routeapp_Webhooks();
27 add_filter('cron_schedules', array($this, 'wpcron_schedule'));
28 add_action('routeapp_check_for_missing_orders', array($this, 'routeapp_validation_worker'));
29 add_action('routeapp_check_for_missing_shipments', array($this, 'routeapp_missing_shipments_worker'));
30 add_action('routeapp_check_for_invalid_webhooks', array($this, 'routeapp_webhooks_validator'));
31 $this->init();
32 }
33
34 /**
35 * Add wp_cron Minutely and Biminutely Interval Schedules if not is scheduled
36 *
37 * @since 1.0.3
38 * @return void
39 */
40 public function init() {
41 $this->set_schedule_event('routeapp_daily', 'routeapp_check_for_missing_orders');
42 $this->set_schedule_event('routeapp_5_hours', 'routeapp_check_for_missing_shipments');
43 $this->set_schedule_event('routeapp_5_hours', 'routeapp_check_for_invalid_webhooks');
44 }
45
46 public function set_schedule_event($event, $action) {
47 if (!wp_next_scheduled($action)) {
48 wp_schedule_event(time(), $event, $action);
49 };
50 }
51
52 /**
53 * @param $routeapp_client
54 * @param array $trackingPayload
55 * @param $order
56 * @param $params
57 * @return bool
58 */
59 public static function resend_shipment(array $trackingPayload)
60 {
61 $routeapp_client = Routeapp_API_Client::getInstance();
62
63 try {
64 $response = $routeapp_client->create_shipment($trackingPayload['tracking_number'], $trackingPayload);
65
66 if (!is_wp_error($response) &&
67 (wp_remote_retrieve_response_code($response) == 409 ||
68 wp_remote_retrieve_response_code($response) == 201)
69 ) {
70 return true;
71 }
72
73 return false;
74
75 } catch (Exception $exception) {
76 self::get_route_public_instance()->routeapp_log($exception, [
77 'endpoint' => 'shipments',
78 'params' => $trackingPayload,
79 'method' => 'POST'
80 ]);
81 return false;
82 }
83 }
84
85
86 /**
87 * Add wp_cron Daily, Daily Interval Schedules
88 *
89 * @since 1.0.3
90 * @param array $schedules
91 * @return array $schedules
92 */
93
94 public function wpcron_schedule($schedules) {
95
96 $schedules['routeapp_daily'] = array(
97 'interval' => 60 * 60 * 24,
98 'display' => __('Once Daily', 'routeapp')
99 );
100
101 $schedules['routeapp_5_hours'] = array(
102 'interval' => 60 * 60 * 5,
103 'display' => __('Every 5 hours', 'routeapp')
104 );
105
106 return $schedules;
107 }
108
109 /**
110 *
111 * Check if the order is created on Route API.
112 *
113 */
114 public static function has_route_order_id( $order_id ) {
115 return self::get_meta_data( $order_id, '_routeapp_order_id');
116 }
117
118 /**
119 * get meta data if custom order table is enabled
120 * @param $order_id
121 * @param $post_meta
122 * @return array|mixed|string
123 */
124 public static function get_meta_data( $order_id, $post_meta ) {
125 if ( class_exists('Automattic\WooCommerce\Utilities\OrderUtil')
126 && OrderUtil::custom_orders_table_usage_is_enabled() ) {
127 $order = wc_get_order($order_id);
128 return $order->get_meta($post_meta);
129 } else {
130 // Traditional CPT-based orders are in use.
131 return get_post_meta($order_id, $post_meta, true);
132 }
133 }
134
135
136 /**
137 *
138 * Get the Route Public class instance
139 *
140 */
141 public static function get_route_public_instance() {
142 global $routeapp_public;
143 return $routeapp_public;
144 }
145
146 /**
147 *
148 * Set the 'routeapp_shipment_cron_api_called' as a flag to avoid multiple requests to Route
149 *
150 */
151 public static function has_reconcile_shipping_triggered( $order_id ) {
152 return self::get_meta_data( $order_id, 'routeapp_shipment_cron_api_called') == 'success';
153 }
154
155 /**
156 *
157 * Get the 'routeapp_shipment_cron_api_called' as a flag see if the order has already be sent to Route
158 *
159 */
160 public static function set_reconcile_shipping_triggered( $order_id ) {
161 self::update_post_meta_value( $order_id, 'routeapp_shipment_cron_api_called', 'success' );
162 }
163
164 /**
165 * Update post meta value
166 *
167 * @param $order_id
168 * @param $post_meta
169 * @param $value
170 */
171 public static function update_post_meta_value( $order_id, $post_meta, $value) {
172 if ( class_exists('Automattic\WooCommerce\Utilities\OrderUtil')
173 && OrderUtil::custom_orders_table_usage_is_enabled() ) {
174 $order = wc_get_order($order_id);
175 $order->update_meta_data($post_meta, $value );
176 $order->save();
177 } else {
178 // Traditional CPT-based orders are in use.
179 update_post_meta( $order_id, $post_meta, $value );
180 }
181 }
182
183 /**
184 * Periodic Worker Triggering Missing Shippings on Route
185 *
186 * @return void
187 */
188 public static function routeapp_reconcile_shippings($orders)
189 {
190 $routeapp_client = Routeapp_API_Client::getInstance();
191 if (count($orders) > 0) {
192 foreach ($orders as $order) {
193
194 if (!$order || self::has_reconcile_shipping_triggered($order->get_id()) || !self::has_route_order_id($order->get_id())) {
195 continue;
196 }
197
198 try {
199
200 $trackingPayloads = self::format_shipping_data_api($order->get_id());
201 $tracking_numbers = array();
202 foreach ($trackingPayloads as $trackingPayload){
203 $shipmentResponse = $routeapp_client->get_shipment($trackingPayload['tracking_number'], $order->get_id());
204 $tracking_numbers[] = $trackingPayload['tracking_number'];
205 if (is_wp_error($shipmentResponse) || (isset($shipmentResponse['response']['code']) && $shipmentResponse['response']['code'] == 200)) {
206 continue;
207 }
208
209 self::resend_shipment($trackingPayload);
210 }
211
212 //save on post_meta the tracking ids
213 if (!empty($tracking_numbers)) {
214 $tracking_numbers = implode(Routeapp_WooCommerce_Tracking_Provider::SEPARATOR_PIPE, $tracking_numbers);
215 self::update_post_meta_value($order->get_id(), 'routeapp_shipment_tracking_number', $tracking_numbers);
216 }
217
218 self::set_reconcile_shipping_triggered($order->get_id());
219
220 } catch (Exception $exception) {
221 continue;
222 }
223 }
224 }
225 }
226
227
228 /**
229 * create shipping data sending for API
230 * @since 1.0.0
231 * @param integer $order_id
232 */
233 public static function format_shipping_data_api($order_id)
234 {
235 $order = wc_get_order($order_id);
236
237 if (!$order) return [];
238
239 $tracking_number = self::get_meta_data( $order->get_id(), 'routeapp_shipment_tracking_number');
240 $routeapp_shipment_tracking = new Routeapp_Shipment_Tracking();
241 $courier_id = $routeapp_shipment_tracking->get_shipping_provider_name($order_id);
242 $product_ids = array();
243
244 foreach ($order->get_items() as $order_item) {
245 for ($i = 1; $i <= $order_item->get_quantity(); $i++) {
246 if ($order_item->get_product() instanceof WC_Product) {
247 array_push($product_ids, $order_item->get_product()->get_id());
248 }
249 }
250 }
251
252 if (strpos($tracking_number, Routeapp_WooCommerce_Tracking_Provider::SEPARATOR_PIPE) > 0) {
253 $tracking_numbers = explode(Routeapp_WooCommerce_Tracking_Provider::SEPARATOR_PIPE, $tracking_number);
254 } elseif (strpos($tracking_number, Routeapp_WooCommerce_Tracking_Provider::SEPARATOR_COMMA) > 0) {
255 $tracking_numbers = explode(Routeapp_WooCommerce_Tracking_Provider::SEPARATOR_COMMA, $tracking_number);
256 }else{
257 $tracking_numbers = [$tracking_number];
258 }
259
260 $trackings = [];
261
262 foreach ($tracking_numbers as $tracking){
263 if ($tracking!='') {
264 $trackings[] = [
265 'source_order_id' => $order->get_id(),
266 'source_product_ids' => $product_ids,
267 'courier_id' => is_string($courier_id) ? $courier_id : $courier_id[$tracking],
268 'tracking_number' => $tracking,
269 ];
270 }
271 }
272 if(empty($trackings[0])) {
273 //we don't have shipping on our post_meta yet, try to get the shipping data from third party module
274 $trackings = $routeapp_shipment_tracking->get_shipping_info($order_id);
275 }
276
277 return $trackings;
278 }
279
280
281 /**
282 * Periodic Worker Triggering Transaction Validation Jobs on Route
283 *
284 * @return void
285 */
286 function routeapp_validation_worker() {
287 $after = get_option('_routeapp_last_install_date');
288
289 $acceptedOrderStatuses = get_option('routeapp_included_order_statuses') ? get_option('routeapp_included_order_statuses') : [];
290
291 $args = array(
292 'post_type' => 'shop_order',
293 'posts_per_page' => -1,
294 'post_status' => array_merge($acceptedOrderStatuses),
295 'date_created' => '>=' . $after,
296 'meta_query' => array(
297 array(
298 'key' => '_routeapp_order_id',
299 'compare' => 'NOT EXISTS'
300 ),
301 )
302 );
303
304 $orders = wc_get_orders($args);
305
306 $this::routeapp_run_order_reconcile($orders);
307 }
308
309 /**
310 * Update order status meta (_routeapp_included_order_statuses, _routeapp_cancel_order_statuses)
311 * only if they don't exist or are different from current options.
312 *
313 * @param \WC_Order $order The order to update.
314 * @param bool $auto_save If true (default), saves the order when HPOS and meta changed.
315 * Set to false when the caller will save the order (e.g. massiveOrderSave).
316 * @return void
317 */
318 public static function updateOrderStatusMetaIfNeeded( $order, $auto_save = true ) {
319 if ( ! $order ) {
320 return;
321 }
322 $routeapp_public = self::get_route_public_instance();
323 if ( ! $routeapp_public ) {
324 return;
325 }
326 $is_hpos = class_exists( 'Automattic\WooCommerce\Utilities\OrderUtil' )
327 && OrderUtil::custom_orders_table_usage_is_enabled();
328 $acceptedOrderStatuses = get_option( 'routeapp_included_order_statuses' ) ? get_option( 'routeapp_included_order_statuses' ) : array();
329 $acceptedCanceledStatuses = get_option( 'routeapp_cancel_order_statuses' ) ? get_option( 'routeapp_cancel_order_statuses' ) : array();
330
331 $updated = false;
332 $updated |= $routeapp_public->updateRouteOrderMetaData( $order, $is_hpos, '_routeapp_included_order_statuses', $acceptedOrderStatuses );
333 $updated |= $routeapp_public->updateRouteOrderMetaData( $order, $is_hpos, '_routeapp_cancel_order_statuses', $acceptedCanceledStatuses );
334
335 if ( $updated && $is_hpos && $auto_save ) {
336 $order->save();
337 }
338 }
339
340 /**
341 * Update order post_meta with route order data
342 *
343 * @param $order
344 * @param $routeOrder
345 * @return void
346 */
347 public static function updateOrderPostMeta($order, $routeOrder) {
348 //order was found, save route order id in db
349 $routeOrder = json_decode($routeOrder['body'], true);
350 $routeCharge = $routeOrder['insured_status'] == 'insured_selected' ? $routeOrder['paid_to_insure'] : '';
351 $protected = !empty($routeCharge) ? 1 : 0;
352 $acceptedOrderStatuses = get_option( 'routeapp_included_order_statuses' ) ? get_option( 'routeapp_included_order_statuses' ) : array();
353 $acceptedCanceledStatuses = get_option( 'routeapp_cancel_order_statuses' ) ? get_option( 'routeapp_cancel_order_statuses' ) : array();
354
355 if ( class_exists('Automattic\WooCommerce\Utilities\OrderUtil')
356 && OrderUtil::custom_orders_table_usage_is_enabled() ) {
357 // HPOS usage is enabled.
358 $order->update_meta_data( '_routeapp_order_id', $routeOrder['id'] );
359 $order->update_meta_data( '_routeapp_route_charge', $routeCharge );
360 $order->update_meta_data( '_routeapp_route_protection', $protected );
361 self::updateOrderStatusMetaIfNeeded( $order, false );
362 $order->save();
363 } else {
364 // Traditional CPT-based orders are in use.
365 update_post_meta( $order->get_id(), '_routeapp_order_id', $routeOrder['id'] );
366 update_post_meta( $order->get_id(), '_routeapp_route_charge', $routeCharge );
367 update_post_meta( $order->get_id(), '_routeapp_route_protection', $protected );
368 self::updateOrderStatusMetaIfNeeded( $order );
369 }
370 }
371
372 public static function routeapp_run_order_reconcile($orders) {
373 $routeapp_client = Routeapp_API_Client::getInstance();
374 if (count($orders) > 0) {
375
376 foreach ($orders as $order) {
377 //check if order exists on Route side
378 $getOrderResponse = $routeapp_client->get_order($order->get_id());
379
380 if (is_wp_error($getOrderResponse) || !in_array($getOrderResponse['response']['code'], [200, 404])) {
381 continue;
382 }
383
384 if ($getOrderResponse['response']['code'] == 200 ) {
385 //order was found, save route order id in db
386 self::updateOrderPostMeta($order, $getOrderResponse);
387 } else {
388 //order not found
389 //create from our side
390 $routeapp_public = self::get_route_public_instance();
391 if (!$routeapp_public->routeapp_is_shipping_method_allowed(false, $order)) continue;
392 $orderData = $routeapp_public->create_order_data_api($order->get_id());
393 $response = $routeapp_client->create_order($orderData);
394 if (!is_wp_error( $response )) {
395 $body = json_decode( $response['body'] );
396 try {
397 if ($response['response']['code'] > 201 && $response['response']['code'] < 409) {
398 $note = 'Route API Error while posting data. Errorcode- '.$response['response']['code'];
399 throw new Exception($note);
400 } else if (!empty($body->id) && !empty($body->order_number) || $response['response']['code'] == 409) {
401
402 $routeCharge = $body->insured_status == 'insured_selected' ? $body->paid_to_insure : '';
403 $protected = !empty($routeCharge) ? 1 : 0;
404 $acceptedOrderStatuses = get_option( 'routeapp_included_order_statuses' ) ? get_option( 'routeapp_included_order_statuses' ) : array();
405 $acceptedCanceledStatuses = get_option( 'routeapp_cancel_order_statuses' ) ? get_option( 'routeapp_cancel_order_statuses' ) : array();
406
407 if ( class_exists('Automattic\WooCommerce\Utilities\OrderUtil')
408 && OrderUtil::custom_orders_table_usage_is_enabled() ) {
409 // HPOS usage is enabled.
410 $order->update_meta_data('_routeapp_order_id', $body->id );
411 $order->update_meta_data('_routeapp_route_charge', $routeCharge );
412 $order->update_meta_data('_routeapp_route_protection', $protected );
413 self::updateOrderStatusMetaIfNeeded( $order, false );
414 $order->save();
415 } else {
416 // Traditional CPT-based orders are in use.
417 update_post_meta( $order->get_id(), '_routeapp_order_id', $body->id );
418 update_post_meta( $order->get_id(), '_routeapp_route_charge', $routeCharge );
419 update_post_meta( $order->get_id(), '_routeapp_route_protection', $protected );
420 self::updateOrderStatusMetaIfNeeded( $order );
421 }
422 }
423 } catch(Exception $exception) {
424 $routeapp_public = self::get_route_public_instance();
425 $extraData = array(
426 'params' => $orderData,
427 'method' => 'POST',
428 'endpoint' => 'orders'
429 );
430 $routeapp_public->routeapp_log($exception, $extraData);
431 }
432 }
433 }
434 }
435 }
436 }
437
438 /**
439 * Periodic Worker that checks Route Webhooks validations
440 *
441 *
442 * @return void
443 */
444 function routeapp_webhooks_validator() {
445 $this->routeapp_webhooks->upsert_webhooks();
446 }
447
448 public function routeapp_missing_shipments_worker() {
449 $after = get_option('_routeapp_last_install_date');
450
451 $acceptedOrderStatuses = get_option('routeapp_included_order_statuses') ? get_option('routeapp_included_order_statuses') : [];
452 $acceptedCanceledStatuses = get_option('routeapp_cancel_order_statuses') ? get_option('routeapp_cancel_order_statuses') : [];
453
454 $args = array(
455 'post_type' => 'shop_order',
456 'posts_per_page' => -1,
457 'post_status' => array_merge($acceptedOrderStatuses, $acceptedCanceledStatuses),
458 'date_created' => '>=' . $after,
459 'meta_query' => array(
460 array(
461 'key' => 'routeapp_shipment_tracking_number',
462 'value' => '',
463 'compare' => '!='
464 ),
465 array(
466 'key' => 'routeapp_shipment_cron_api_called',
467 'compare' => 'NOT EXISTS'
468 ),
469 )
470 );
471
472 $orders = wc_get_orders($args);
473
474 $this->routeapp_reconcile_shippings($orders);
475 }
476 }
477