PluginProbe
Route ‑ Shipping Protection / 2.4.18
Route ‑ Shipping Protection v2.4.18
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
← All changes | includes/class-routeapp-api-client.php +107 -5 2.3.1 → 2.4.18 View file →
@@ -452,18 +452,64 @@
452 452 */
453 453 public function create_shipment($tracking_id, $data = array())
454 454 {
455 455 if (empty($tracking_id)) return false;
456 - return $this->_make_private_api_call('shipments', array(
456 + $payload = array(
457 457 'tracking_number' => $this->sanitize_value($tracking_id),
458 - 'source_order_id' => $data['source_order_id'],
459 - 'source_product_ids' => $data['source_product_ids'],
460 - 'courier_id' => $this->sanitize_value($data['courier_id']),
461 - ), 'POST');
458 + 'source_order_id' => isset($data['source_order_id']) ? $data['source_order_id'] : null,
459 + 'source_product_ids' => isset($data['source_product_ids']) ? $data['source_product_ids'] : array(),
460 + 'courier_id' => $this->sanitize_value(isset($data['courier_id']) ? $data['courier_id'] : ''),
461 + );
462 + $response = $this->_make_private_api_call('shipments', $payload, 'POST');
463 +
464 + return $this->retry_create_shipment_with_parent_product_ids($payload, $response);
462 465 }
463 466
464 467 /**
468 + * If the first create-shipment is rejected (HTTP 400, typically Invalid
469 + * ProductIDs), retry once with parent product_ids from the Woo order.
470 + * Those match webhook order-create when variation meta never reaches Route.
465 471 *
472 + * @param array $payload Original shipment payload.
473 + * @param array|\WP_Error $response First create-shipment response.
474 + * @return array|\WP_Error
475 + */
476 + private function retry_create_shipment_with_parent_product_ids($payload, $response)
477 + {
478 + if ( ! class_exists('Routeapp_WooCommerce_Common_Tracking_Provider') ) {
479 + return $response;
480 + }
481 +
482 + if ( Routeapp_WooCommerce_Common_Tracking_Provider::is_successful_shipment_create_response($response) ) {
483 + return $response;
484 + }
485 +
486 + $code = 0;
487 + if ( isset($response['response']['code']) ) {
488 + $code = (int) $response['response']['code'];
489 + } elseif ( function_exists('wp_remote_retrieve_response_code') ) {
490 + $code = (int) wp_remote_retrieve_response_code($response);
491 + }
492 + if ( $code !== 400 || empty($payload['source_order_id']) ) {
493 + return $response;
494 + }
495 +
496 + $parent_ids = Routeapp_WooCommerce_Common_Tracking_Provider::get_order_products_parent_ids($payload['source_order_id']);
497 + if ( empty($parent_ids) ) {
498 + return $response;
499 + }
500 +
501 + $original_ids = array_map('strval', (array) $payload['source_product_ids']);
502 + if ( $original_ids === $parent_ids ) {
503 + return $response;
504 + }
505 +
506 + $payload['source_product_ids'] = $parent_ids;
507 + return $this->_make_private_api_call('shipments', $payload, 'POST');
508 + }
509 +
510 + /**
511 + *
466 512 * Sanitize shipstation tracking numbers. Moved to here from the
467 513 * class-routeapp-shipstation.php script because it is sometimes
468 514 * getting bypassed and orders are coming through with "-(SHIPSTATION)"
469 515 * at the end. Also added a more specific check for both a shipstation
@@ -796,6 +842,62 @@
796 842
797 843 protected function _make_private_api_call_using_user_token($endpoint, $params = array(), $method = 'GET', $version='v1')
798 844 {
799 845 return $this->_make_api_call($this->get_user_token(), $endpoint, $params, $method, $version);
846 + }
847 +
848 + /**
849 + * Exchange a one-time token for merchant data (no merchant API auth; Route v1/otp/verify).
850 + *
851 + * @param string $token OTP from Route Dashboard after signing in with platformUrl.
852 + * @return array|\WP_Error Merchant payload with id, public_api_key, prod_api_secret, store_domain on success.
853 + */
854 + public function verify_otp( $token ) {
855 + $url = $this->_api_url . 'otp/verify';
856 + $headers = array(
857 + 'Content-Type' => 'application/json',
858 + 'platform' => 'woocommerce',
859 + );
860 + $wooVersion = '';
861 + if ( in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ), true ) &&
862 + defined( 'WC_VERSION' ) ) {
863 + $wooVersion = WC_VERSION;
864 + }
865 + $wordpressVersion = function_exists( 'get_bloginfo' ) ? get_bloginfo( 'version' ) : '';
866 + $headers['platform_version'] = 'WooCommerce: ' . $wooVersion . ' WordPress: ' . $wordpressVersion;
867 + $headers['module_version'] = defined( 'ROUTEAPP_VERSION' ) ? ROUTEAPP_VERSION : '';
868 +
869 + $args = array(
870 + 'timeout' => 15,
871 + 'method' => 'POST',
872 + 'headers' => $headers,
873 + 'body' => wp_json_encode( array( 'token' => $token ) ),
874 + );
875 +
876 + $response = wp_remote_request( $url, $args );
877 +
878 + if ( is_wp_error( $response ) ) {
879 + return $response;
880 + }
881 +
882 + $code = wp_remote_retrieve_response_code( $response );
883 + $body_raw = wp_remote_retrieve_body( $response );
884 + $data = json_decode( $body_raw, true );
885 +
886 + if ( 200 !== (int) $code ) {
887 + $message = is_array( $data ) && ! empty( $data['error'] ) ? $data['error'] : 'OTP verification failed';
888 + return new \WP_Error( 'route_otp_verify_failed', $message, array( 'status' => $code ) );
889 + }
890 +
891 + if ( ! is_array( $data ) ) {
892 + return new \WP_Error( 'route_otp_invalid_response', 'Unexpected response from Route API' );
893 + }
894 +
895 + $result = isset( $data['result'] ) && is_array( $data['result'] ) ? $data['result'] : $data;
896 +
897 + if ( empty( $result['id'] ) || empty( $result['public_api_key'] ) || empty( $result['prod_api_secret'] ) ) {
898 + return new \WP_Error( 'route_otp_incomplete', 'OTP response missing merchant credentials' );
899 + }
900 +
901 + return $result;
800 902 }
801 903 }