| @@ -6,8 +6,15 @@ | ||
| 6 | 6 | |
| 7 | 7 | const ROUTE_CHARGE_POST_META = '_routeapp_route_charge'; |
| 8 | 8 | const ROUTE_PROTECTION_POST_META = '_routeapp_route_protection'; |
| 9 | 9 | |
| 10 | + /** | |
| 11 | + * Per-request quote cache keyed by admin cart ref. | |
| 12 | + * | |
| 13 | + * @var array<string, object|false> | |
| 14 | + */ | |
| 15 | + private $admin_order_quote_cache = array(); | |
| 16 | + | |
| 10 | 17 | public function __construct() |
| 11 | 18 | { |
| 12 | 19 | if (!is_admin()) { |
| 13 | 20 | return false; |
| @@ -30,21 +37,120 @@ | ||
| 30 | 37 | } |
| 31 | 38 | |
| 32 | 39 | public function routeapp_custom_admin_button($order) |
| 33 | 40 | { |
| 34 | - if (!$this->can_add_fee($order)) { | |
| 41 | + echo $this->get_route_fee_button_html($order); | |
| 42 | + } | |
| 43 | + | |
| 44 | + public function get_route_fee_button_html($order) | |
| 45 | + { | |
| 46 | + if (!$this->can_show_route_fee_buttons($order)) { | |
| 47 | + return ''; | |
| 48 | + } | |
| 49 | + | |
| 50 | + if ($this->has_route_fee_applied($order)) { | |
| 51 | + return '<button type="button" id="remove-route-fee" class="button generate-items">' . esc_html__( 'Remove Route Fee', 'routeapp' ) . '</button>'; | |
| 52 | + } | |
| 53 | + | |
| 54 | + if ($this->is_order_eligible_for_route_coverage($order)) { | |
| 55 | + return '<button type="button" id="add-route-fee" class="button generate-items">' . esc_html__( 'Add Route Fee', 'routeapp' ) . '</button>'; | |
| 56 | + } | |
| 57 | + | |
| 58 | + return ''; | |
| 59 | + } | |
| 60 | + | |
| 61 | + public function routeapp_refresh_admin_fee_button() | |
| 62 | + { | |
| 63 | + check_ajax_referer( 'order-item', 'security' ); | |
| 64 | + | |
| 65 | + if (!isset($_POST['order_id'])) { | |
| 66 | + wp_send_json_error('Missing order_id in the request.'); | |
| 67 | + return; | |
| 68 | + } | |
| 69 | + | |
| 70 | + $order = wc_get_order(absint($_POST['order_id'])); | |
| 71 | + if (!$order) { | |
| 72 | + wp_send_json_error('Invalid order_id.'); | |
| 73 | + return; | |
| 74 | + } | |
| 75 | + | |
| 76 | + wp_send_json_success(array( | |
| 77 | + 'html' => $this->get_route_fee_button_html($order), | |
| 78 | + )); | |
| 79 | + } | |
| 80 | + | |
| 81 | + private function get_admin_order_cart_ref($order, $cart_total, $cart_items) | |
| 82 | + { | |
| 83 | + $items_hash = substr(md5(wp_json_encode($cart_items)), 0, 8); | |
| 84 | + | |
| 85 | + return 'admin-' . $order->get_id() . '-' . $cart_total . '-' . $items_hash; | |
| 86 | + } | |
| 87 | + | |
| 88 | + private function get_admin_order_quote_context($order) | |
| 89 | + { | |
| 90 | + $routeapp_public = self::get_route_public_instance(); | |
| 91 | + $cart_total = round($routeapp_public->get_cart_subtotal_with_only_shippable_items($order, 'order'), 2); | |
| 92 | + $cart_items = $routeapp_public->get_cart_shippable_items($order, 'order'); | |
| 93 | + | |
| 94 | + return array( | |
| 95 | + 'total' => $cart_total, | |
| 96 | + 'items' => $cart_items, | |
| 97 | + 'ref' => $this->get_admin_order_cart_ref($order, $cart_total, $cart_items), | |
| 98 | + 'currency' => get_woocommerce_currency(), | |
| 99 | + 'public' => $routeapp_public, | |
| 100 | + ); | |
| 101 | + } | |
| 102 | + | |
| 103 | + private function fetch_admin_order_quote($order) | |
| 104 | + { | |
| 105 | + $context = $this->get_admin_order_quote_context($order); | |
| 106 | + | |
| 107 | + if ($context['total'] <= 0) { | |
| 35 | 108 | return false; |
| 36 | 109 | } |
| 37 | 110 | |
| 38 | - if ( is_admin() && in_array($order->get_status(), ['pending', 'on-hold', 'auto-draft']) ) { | |
| 39 | - if ($this->has_route_fee_applied($order)) { | |
| 40 | - echo '<button type="button" id="remove-route-fee" class="button generate-items">' . __( 'Remove Route Fee') . '</button>'; | |
| 41 | - } else { | |
| 42 | - echo '<button type="button" id="add-route-fee" class="button generate-items">' . __( 'Add Route Fee') . '</button>'; | |
| 43 | - } | |
| 111 | + if (array_key_exists($context['ref'], $this->admin_order_quote_cache)) { | |
| 112 | + return $this->admin_order_quote_cache[$context['ref']]; | |
| 44 | 113 | } |
| 114 | + | |
| 115 | + $quote = $context['public']->routeapp_get_quote_from_api( | |
| 116 | + $context['ref'], | |
| 117 | + $context['total'], | |
| 118 | + $context['currency'], | |
| 119 | + $context['items'] | |
| 120 | + ); | |
| 121 | + | |
| 122 | + $this->admin_order_quote_cache[$context['ref']] = $quote; | |
| 123 | + | |
| 124 | + return $quote; | |
| 45 | 125 | } |
| 46 | 126 | |
| 127 | + public function is_order_eligible_for_route_coverage($order) | |
| 128 | + { | |
| 129 | + if (!$order) { | |
| 130 | + return false; | |
| 131 | + } | |
| 132 | + | |
| 133 | + $routeapp_public = self::get_route_public_instance(); | |
| 134 | + $route_insurance_quote = $this->fetch_admin_order_quote($order); | |
| 135 | + | |
| 136 | + return $this->is_quote_eligible_for_customer_coverage($route_insurance_quote, $routeapp_public); | |
| 137 | + } | |
| 138 | + | |
| 139 | + private function is_quote_eligible_for_customer_coverage($route_insurance_quote, $routeapp_public) | |
| 140 | + { | |
| 141 | + if (!$route_insurance_quote) { | |
| 142 | + return false; | |
| 143 | + } | |
| 144 | + | |
| 145 | + if (!isset($route_insurance_quote->premium->amount) || $route_insurance_quote->premium->amount <= 0) { | |
| 146 | + return false; | |
| 147 | + } | |
| 148 | + | |
| 149 | + return isset($route_insurance_quote->payment_responsible->type) | |
| 150 | + && $route_insurance_quote->payment_responsible->type === $routeapp_public->get_allowed_quote_type(); | |
| 151 | + } | |
| 152 | + | |
| 47 | 153 | private function routeapp_handle_admin_fee($order) |
| 48 | 154 | { |
| 49 | 155 | if (!$this->can_add_fee($order)) { |
| 50 | 156 | return false; |
| @@ -49,19 +155,12 @@ | ||
| 49 | 155 | if (!$this->can_add_fee($order)) { |
| 50 | 156 | return false; |
| 51 | 157 | } |
| 52 | 158 | $routeapp_public = self::get_route_public_instance(); |
| 53 | - //order doesn't have a hash, we use time to auto-generate one | |
| 54 | - $cartRef = time(); | |
| 55 | - $cartTotal = round($routeapp_public->get_cart_subtotal_with_only_shippable_items($order, 'order'), 2); | |
| 56 | - $currency = get_woocommerce_currency(); | |
| 57 | - $cartItems = $routeapp_public->get_cart_shippable_items($order, 'order'); | |
| 58 | - $route_insurance_quote = $routeapp_public->routeapp_get_quote_from_api($cartRef, $cartTotal, $currency, $cartItems); | |
| 159 | + $route_insurance_quote = $this->fetch_admin_order_quote($order); | |
| 59 | 160 | |
| 60 | 161 | $route_insurance_amount = false; |
| 61 | - //only add fee if the quote is covered by customer and has a valid amount | |
| 62 | - if (isset($route_insurance_quote->premium->amount) && | |
| 63 | - (isset($route_insurance_quote->payment_responsible->type)) && $route_insurance_quote->payment_responsible->type==$routeapp_public->get_allowed_quote_type()) { | |
| 162 | + if ($this->is_quote_eligible_for_customer_coverage($route_insurance_quote, $routeapp_public)) { | |
| 64 | 163 | $route_insurance_amount = $route_insurance_quote->premium->amount; |
| 65 | 164 | } |
| 66 | 165 | if ($route_insurance_amount > 0) { |
| 67 | 166 | $this->routeapp_create_fee($order, $route_insurance_amount); |
| @@ -91,11 +190,13 @@ | ||
| 91 | 190 | |
| 92 | 191 | } |
| 93 | 192 | return true; |
| 94 | 193 | } |
| 95 | - }else { | |
| 194 | + } else { | |
| 96 | 195 | if ($this->has_route_fee_applied($order)) { |
| 97 | 196 | $this->routeapp_remove_route_fee($order); |
| 197 | + $order->calculate_totals(); | |
| 198 | + $order->save(); | |
| 98 | 199 | } |
| 99 | 200 | } |
| 100 | 201 | |
| 101 | 202 | return false; |
| @@ -109,8 +210,9 @@ | ||
| 109 | 210 | $item_fee->set_tax_class( $routeapp_public->routeapp_get_taxable_class() ); |
| 110 | 211 | $item_fee->set_tax_status( $routeapp_public->routeapp_get_fee_taxable() ? 'taxable' : 'none' ); |
| 111 | 212 | $item_fee->set_amount( $fee_amount ); |
| 112 | 213 | $item_fee->set_total( $fee_amount ); |
| 214 | + Routeapp_Public::routeapp_mark_protection_fee_item( $item_fee ); | |
| 113 | 215 | $order->add_item( $item_fee ); |
| 114 | 216 | } else { |
| 115 | 217 | $fees = $this->get_route_fees($order); |
| 116 | 218 | foreach($fees as $fee_id => $fee) { |
| @@ -116,8 +218,9 @@ | ||
| 116 | 218 | foreach($fees as $fee_id => $fee) { |
| 117 | 219 | if ($fee) { |
| 118 | 220 | $fee->set_amount($fee_amount); |
| 119 | 221 | $fee->set_total($fee_amount); |
| 222 | + Routeapp_Public::routeapp_mark_protection_fee_item( $fee ); | |
| 120 | 223 | $fee->save(); |
| 121 | 224 | } |
| 122 | 225 | } |
| 123 | 226 | } |
| @@ -127,14 +230,18 @@ | ||
| 127 | 230 | { |
| 128 | 231 | check_ajax_referer( 'order-item', 'security' ); |
| 129 | 232 | |
| 130 | 233 | if (!isset($_POST) || !$_POST['order_id']) { |
| 131 | - wp_send_json_error('its missing order id on the request'); | |
| 234 | + wp_send_json_error('Missing order_id in the request.'); | |
| 132 | 235 | return; |
| 133 | 236 | } |
| 134 | 237 | |
| 135 | 238 | $order_id = isset($_POST['order_id']) ? $_POST['order_id'] : 0; |
| 136 | 239 | $order = wc_get_order( $order_id ); |
| 240 | + if (!$order) { | |
| 241 | + wp_send_json_error('Invalid order_id.'); | |
| 242 | + return; | |
| 243 | + } | |
| 137 | 244 | |
| 138 | 245 | $response = $this->routeapp_handle_admin_fee($order); |
| 139 | 246 | |
| 140 | 247 | if ($response) { |
| @@ -149,17 +256,23 @@ | ||
| 149 | 256 | { |
| 150 | 257 | check_ajax_referer( 'order-item', 'security' ); |
| 151 | 258 | |
| 152 | 259 | if (!isset($_POST) || !$_POST['order_id']) { |
| 153 | - wp_send_json_error('its missing order id on the request'); | |
| 260 | + wp_send_json_error('Missing order_id in the request.'); | |
| 154 | 261 | return; |
| 155 | 262 | } |
| 156 | 263 | |
| 157 | 264 | $order_id = isset($_POST['order_id']) ? $_POST['order_id'] : 0; |
| 158 | 265 | $order = wc_get_order( $order_id ); |
| 266 | + if (!$order) { | |
| 267 | + wp_send_json_error('Invalid order_id.'); | |
| 268 | + return; | |
| 269 | + } | |
| 159 | 270 | |
| 160 | 271 | if ($this->has_route_fee_applied($order)) { |
| 161 | 272 | $this->routeapp_remove_route_fee($order); |
| 273 | + $order->calculate_totals(); | |
| 274 | + $order->save(); | |
| 162 | 275 | } |
| 163 | 276 | |
| 164 | 277 | wp_send_json_success('route fee removed from the order'); |
| 165 | 278 | } |
| @@ -172,10 +285,17 @@ | ||
| 172 | 285 | if ($fee) { |
| 173 | 286 | wc_delete_order_item($fee_id); |
| 174 | 287 | } |
| 175 | 288 | } |
| 176 | - delete_post_meta( $order->get_id(), self::ROUTE_CHARGE_POST_META ); | |
| 177 | - delete_post_meta( $order->get_id(), self::ROUTE_PROTECTION_POST_META ); | |
| 289 | + | |
| 290 | + if ( class_exists('Automattic\WooCommerce\Utilities\OrderUtil') | |
| 291 | + && OrderUtil::custom_orders_table_usage_is_enabled() ) { | |
| 292 | + $order->delete_meta_data(self::ROUTE_CHARGE_POST_META); | |
| 293 | + $order->delete_meta_data(self::ROUTE_PROTECTION_POST_META); | |
| 294 | + } else { | |
| 295 | + delete_post_meta( $order->get_id(), self::ROUTE_CHARGE_POST_META ); | |
| 296 | + delete_post_meta( $order->get_id(), self::ROUTE_PROTECTION_POST_META ); | |
| 297 | + } | |
| 178 | 298 | } |
| 179 | 299 | |
| 180 | 300 | public function recalculate_route_fee($order_id) |
| 181 | 301 | { |
| @@ -185,23 +305,8 @@ | ||
| 185 | 305 | $this->routeapp_handle_admin_fee($order); |
| 186 | 306 | } |
| 187 | 307 | } |
| 188 | 308 | |
| 189 | - private function get_quote_from_api($order_total) | |
| 190 | - { | |
| 191 | - $response = Routeapp_API_Client::getInstance()->get_quote($order_total, get_woocommerce_currency()); | |
| 192 | - | |
| 193 | - if (is_wp_error($response)) { | |
| 194 | - return false; | |
| 195 | - } else { | |
| 196 | - if ($response['response']['code'] == 401) { | |
| 197 | - return false; | |
| 198 | - } | |
| 199 | - $price_data = json_decode($response['body']); | |
| 200 | - return $price_data; | |
| 201 | - } | |
| 202 | - } | |
| 203 | - | |
| 204 | 309 | private function get_route_fees($order) |
| 205 | 310 | { |
| 206 | 311 | if (!$order) { |
| 207 | 312 | return []; |
| @@ -209,9 +314,9 @@ | ||
| 209 | 314 | |
| 210 | 315 | $fees = $order->get_fees(); |
| 211 | 316 | $route_fees = []; |
| 212 | 317 | foreach($fees as $fee_id => $fee) { |
| 213 | - if($fee['name'] == Routeapp_Public::ROUTE_LABEL) { | |
| 318 | + if (Routeapp_Public::routeapp_is_route_protection_fee($fee)) { | |
| 214 | 319 | $route_fees[$fee_id] = $fee; |
| 215 | 320 | } |
| 216 | 321 | } |
| 217 | 322 | return $route_fees; |
| @@ -216,11 +321,19 @@ | ||
| 216 | 321 | } |
| 217 | 322 | return $route_fees; |
| 218 | 323 | } |
| 219 | 324 | |
| 325 | + public function can_show_route_fee_buttons($order) | |
| 326 | + { | |
| 327 | + return $order | |
| 328 | + && count($order->get_items()) > 0 | |
| 329 | + && is_admin() | |
| 330 | + && in_array($order->get_status(), array('pending', 'on-hold', 'auto-draft'), true); | |
| 331 | + } | |
| 332 | + | |
| 220 | 333 | public function can_add_fee($order) |
| 221 | 334 | { |
| 222 | - return $order && count($order->get_items()) > 0 && $order->get_status() == 'auto-draft'; | |
| 335 | + return $this->can_show_route_fee_buttons($order); | |
| 223 | 336 | } |
| 224 | 337 | |
| 225 | 338 | private function has_route_fee_applied($order) |
| 226 | 339 | { |