| @@ -35,8 +35,9 @@ | ||
| 35 | 35 | const ROUTE_PROTECTION_SKU = 'ROUTEINS'; |
| 36 | 36 | const ROUTE_FEE_ITEM_META = '_routeapp_protection_fee'; |
| 37 | 37 | const ROUTE_FEE_SKU_META = '_routeapp_protection_sku'; |
| 38 | 38 | const ALLOWED_QUOTE_TYPE = 'paid_by_customer'; |
| 39 | + const PICKUP_SHIPPING_METHOD_IDS = array( 'local_pickup', 'pickup_location' ); | |
| 39 | 40 | |
| 40 | 41 | const AMPLITUDE_API_KEY = [ |
| 41 | 42 | 'production' => '6d4f9158886e0c6d78e3bd97663ba920', |
| 42 | 43 | 'stage' => '99a869d62986f6415610c6741057bc92', |
| @@ -127,11 +128,8 @@ | ||
| 127 | 128 | //CHECKOUT HOOK |
| 128 | 129 | $checkout_hook = $this->routeapp_get_checkout_hook(); |
| 129 | 130 | add_action($checkout_hook, array($this, 'checkout_route_insurance'), 1, 0); |
| 130 | 131 | |
| 131 | - | |
| 132 | - add_action('woocommerce_checkout_before_order_review', array($this, 'checkout_route_insurance_fee'), 20, 1); | |
| 133 | - | |
| 134 | 132 | add_action('woocommerce_cart_calculate_fees', array($this, 'checkout_route_insurance_fee'), 20, 1); |
| 135 | 133 | add_action('wp_ajax_woo_get_ajax_data', array($this, 'checkout_route_insurance_set_session')); |
| 136 | 134 | add_action('wp_ajax_nopriv_woo_get_ajax_data', array($this, 'checkout_route_insurance_set_session')); |
| 137 | 135 | |
| @@ -431,20 +429,24 @@ | ||
| 431 | 429 | } |
| 432 | 430 | } |
| 433 | 431 | |
| 434 | 432 | /** |
| 435 | - * Add route insurance fee in checkout and cart | |
| 433 | + * Add route insurance fee in checkout and cart. | |
| 434 | + * Only runs during woocommerce_cart_calculate_fees. The old | |
| 435 | + * woocommerce_checkout_before_order_review binding is gone — it fired | |
| 436 | + * after totals were already calculated and re-added the fee after opt-out. | |
| 437 | + * | |
| 436 | 438 | * @since 1.0.0 |
| 437 | 439 | * @param Object $cart_or_checkout |
| 438 | - * MSS-6261:The issue: checkout_route_insurance_fee is bound to hooks that pass different parameter types: | |
| 439 | - * woocommerce_checkout_before_order_review -> WC_Checkout | |
| 440 | - * woocommerce_cart_calculate_fees -> WC_Cart | |
| 441 | - * At line 388, it assumes a WC_Cart and calls get_cart_hash(), which fails when a WC_Checkout is passed. | |
| 442 | - * Fixing by detecting the object type and handling both cases. | |
| 443 | - */ | |
| 440 | + */ | |
| 444 | 441 | public function checkout_route_insurance_fee( $cart_or_checkout ) { |
| 445 | 442 | if ( (is_admin() && ! defined( 'DOING_AJAX' )) ) return; |
| 446 | 443 | |
| 444 | + // Widget AJAX / fragment hooks must not add fees (re-adds after opt-out, races coupons). | |
| 445 | + if ( function_exists( 'doing_action' ) && ! doing_action( 'woocommerce_cart_calculate_fees' ) ) { | |
| 446 | + return; | |
| 447 | + } | |
| 448 | + | |
| 447 | 449 | // Determine if we received a WC_Cart or WC_Checkout object |
| 448 | 450 | $is_checkout_object = is_a( $cart_or_checkout, 'WC_Checkout' ); |
| 449 | 451 | $cart = $is_checkout_object ? WC()->cart : $cart_or_checkout; |
| 450 | 452 | |
| @@ -475,10 +477,20 @@ | ||
| 475 | 477 | $checkbox = $route_insurance_quote->payment_responsible->ToggleState; |
| 476 | 478 | WC()->session->set('checkbox_checked', $checkbox); |
| 477 | 479 | } |
| 478 | 480 | |
| 481 | + // First checkout paint still defaults ON when ToggleState is missing | |
| 482 | + // (pre-existing behavior). Do not default ON during checkout AJAX — | |
| 483 | + // that re-added the fee after opt-out when the session was empty. | |
| 484 | + if ( $this->routeapp_should_default_fee_opt_in( $checkbox ) ) { | |
| 485 | + $checkbox = true; | |
| 486 | + if ( WC()->session ) { | |
| 487 | + WC()->session->set( 'checkbox_checked', true ); | |
| 488 | + } | |
| 489 | + } | |
| 490 | + | |
| 479 | 491 | Route_Setup::set_setup_check_widget(Route_Setup::SETUP_CHECK_WIDGET_PHP); |
| 480 | - if ( (is_null($checkbox) && is_checkout() || ($checkbox === TRUE || $checkbox === 1)) && $route_insurance_amount > 0 && $this->routeapp_is_shipping_method_allowed()) { | |
| 492 | + if ( ($checkbox === TRUE || $checkbox === 1) && $route_insurance_amount > 0 && $this->routeapp_is_shipping_method_allowed()) { | |
| 481 | 493 | $cart->add_fee( $this->routeapp_get_insurance_label(), |
| 482 | 494 | $route_insurance_amount, |
| 483 | 495 | $this->routeapp_get_fee_taxable(), |
| 484 | 496 | $this->routeapp_get_taxable_class()); |
| @@ -485,8 +497,35 @@ | ||
| 485 | 497 | } |
| 486 | 498 | } |
| 487 | 499 | |
| 488 | 500 | /** |
| 501 | + * WooCommerce / CheckoutWC totals refresh (update_order_review, etc.). | |
| 502 | + * | |
| 503 | + * @return bool | |
| 504 | + */ | |
| 505 | + public function routeapp_is_checkout_ajax_refresh() { | |
| 506 | + if ( ! empty( $_REQUEST['wc-ajax'] ) ) { | |
| 507 | + return true; | |
| 508 | + } | |
| 509 | + | |
| 510 | + return defined( 'DOING_AJAX' ) && DOING_AJAX; | |
| 511 | + } | |
| 512 | + | |
| 513 | + /** | |
| 514 | + * Keep the pre-existing first-load default (fee ON) when there is no | |
| 515 | + * session choice and no ToggleState. Skip that default on AJAX refreshes. | |
| 516 | + * | |
| 517 | + * @param mixed $checkbox Session checkbox value. | |
| 518 | + * @return bool | |
| 519 | + */ | |
| 520 | + public function routeapp_should_default_fee_opt_in( $checkbox ) { | |
| 521 | + return is_null( $checkbox ) | |
| 522 | + && function_exists( 'is_checkout' ) | |
| 523 | + && is_checkout() | |
| 524 | + && ! $this->routeapp_is_checkout_ajax_refresh(); | |
| 525 | + } | |
| 526 | + | |
| 527 | + /** | |
| 489 | 528 | * Create checkout |
| 490 | 529 | * @since 1.0.0 |
| 491 | 530 | * |
| 492 | 531 | */ |
| @@ -631,8 +670,16 @@ | ||
| 631 | 670 | 'merchantId' => $this->routeapp_api_client->get_merchant_id(), |
| 632 | 671 | ) |
| 633 | 672 | ); |
| 634 | 673 | |
| 674 | + wp_localize_script( | |
| 675 | + $this->plugin_name, | |
| 676 | + 'routeappWidgetConfig', | |
| 677 | + array( | |
| 678 | + 'excludedShippingMethods' => array_values( (array) $this->routeapp_get_excluded_shipping_methods() ), | |
| 679 | + ) | |
| 680 | + ); | |
| 681 | + | |
| 635 | 682 | wp_enqueue_style( |
| 636 | 683 | $this->plugin_name . '-widget', |
| 637 | 684 | plugin_dir_url( __FILE__ ) . 'css/routeapp-widget.css', |
| 638 | 685 | array(), |
| @@ -694,17 +741,11 @@ | ||
| 694 | 741 | '<input type="hidden" value="' . $this->routeapp_api_client->get_merchant_id() . '" class="routeapp-merchant-id">'. |
| 695 | 742 | '<input type="hidden" value="' . $custom_env . '" class="routeapp-env">'. |
| 696 | 743 | '<input type="hidden" value="' . $checkbox .'" class="routeapp-checkbox-cookie">'; |
| 697 | 744 | |
| 698 | - //check if shipping method is allowed | |
| 699 | - $cart = WC()->cart; | |
| 700 | - $shipping_method = $cart->calculate_shipping(); | |
| 701 | - if (isset($shipping_method[0]->method_id)) { | |
| 702 | - $shipping_method = $shipping_method[0]->method_id; | |
| 703 | - $show_widget = $this->routeapp_is_shipping_method_allowed($shipping_method); | |
| 704 | - if (!$show_widget) { | |
| 705 | - $return .= '<input type="hidden" value="true" class="routeapp-invalid-shipping-method">'; | |
| 706 | - } | |
| 745 | + $chosen_methods = ( WC()->session ) ? WC()->session->get( 'chosen_shipping_methods' ) : array(); | |
| 746 | + if ( ! empty( $chosen_methods[0] ) && ! $this->routeapp_is_shipping_method_allowed( $chosen_methods[0] ) ) { | |
| 747 | + $return .= '<input type="hidden" value="true" class="routeapp-invalid-shipping-method">'; | |
| 707 | 748 | } |
| 708 | 749 | return $return . '<div id="RouteWidget" class="route-div"></div></div>' ; |
| 709 | 750 | } |
| 710 | 751 | |
| @@ -1310,10 +1351,9 @@ | ||
| 1310 | 1351 | } else { |
| 1311 | 1352 | return false; |
| 1312 | 1353 | } |
| 1313 | 1354 | } |
| 1314 | - $excludedShippingMethods = $this->routeapp_get_excluded_shipping_methods(); | |
| 1315 | - if (is_array($excludedShippingMethods) && in_array($shipping_method, $excludedShippingMethods)) { | |
| 1355 | + if ($this->routeapp_is_shipping_method_excluded($shipping_method)) { | |
| 1316 | 1356 | return false; |
| 1317 | 1357 | } |
| 1318 | 1358 | } else { |
| 1319 | 1359 | if (WC()->session->get('shipping_for_package_0')) { |
| @@ -1320,10 +1360,9 @@ | ||
| 1320 | 1360 | foreach( WC()->session->get('shipping_for_package_0')['rates'] as $method_id => $rate ){ |
| 1321 | 1361 | if( WC()->session->get('chosen_shipping_methods')[0] == $method_id ){ |
| 1322 | 1362 | $shippingMethod = explode(':', $rate->id); |
| 1323 | 1363 | $shippingMethod = $shippingMethod[0]; |
| 1324 | - $excludedShippingMethods = $this->routeapp_get_excluded_shipping_methods(); | |
| 1325 | - if (in_array($shippingMethod, $excludedShippingMethods)) { | |
| 1364 | + if ($this->routeapp_is_shipping_method_excluded($shippingMethod)) { | |
| 1326 | 1365 | return false; |
| 1327 | 1366 | } |
| 1328 | 1367 | } |
| 1329 | 1368 | } |
| @@ -1331,8 +1370,30 @@ | ||
| 1331 | 1370 | } |
| 1332 | 1371 | return true; |
| 1333 | 1372 | } |
| 1334 | 1373 | |
| 1374 | + /** | |
| 1375 | + * Whether a shipping method is configured as in-store pickup (excluded from protection). | |
| 1376 | + * WooCommerce Blocks local pickup uses pickup_location; classic checkout uses local_pickup. | |
| 1377 | + * | |
| 1378 | + * @param string $shipping_method Method id, optionally with instance suffix (e.g. pickup_location:0). | |
| 1379 | + * @return bool | |
| 1380 | + */ | |
| 1381 | + public function routeapp_is_shipping_method_excluded($shipping_method) { | |
| 1382 | + $shipping_method = explode(':', (string) $shipping_method)[0]; | |
| 1383 | + $excluded = $this->routeapp_get_excluded_shipping_methods(); | |
| 1384 | + if (!is_array($excluded) || empty($excluded) || $shipping_method === '') { | |
| 1385 | + return false; | |
| 1386 | + } | |
| 1387 | + if (in_array($shipping_method, $excluded, true)) { | |
| 1388 | + return true; | |
| 1389 | + } | |
| 1390 | + if (in_array($shipping_method, self::PICKUP_SHIPPING_METHOD_IDS, true)) { | |
| 1391 | + return (bool) array_intersect(self::PICKUP_SHIPPING_METHOD_IDS, $excluded); | |
| 1392 | + } | |
| 1393 | + return false; | |
| 1394 | + } | |
| 1395 | + | |
| 1335 | 1396 | public function routeapp_get_insurance_label() { |
| 1336 | 1397 | return __(self::ROUTE_LABEL, 'routeapp'); |
| 1337 | 1398 | } |
| 1338 | 1399 | |
| @@ -1578,8 +1639,11 @@ | ||
| 1578 | 1639 | return rest_ensure_response( [] ); |
| 1579 | 1640 | } |
| 1580 | 1641 | |
| 1581 | 1642 | $shipping_method = $_POST['shipping_method']; |
| 1643 | + if ($shipping_method === 'false' || $shipping_method === '') { | |
| 1644 | + $shipping_method = false; | |
| 1645 | + } | |
| 1582 | 1646 | |
| 1583 | 1647 | //check first if shipping method is allowed |
| 1584 | 1648 | if ($shipping_method) { |
| 1585 | 1649 | $show_widget = $this->routeapp_is_shipping_method_allowed($shipping_method); |
| @@ -1589,11 +1653,10 @@ | ||
| 1589 | 1653 | } |
| 1590 | 1654 | } |
| 1591 | 1655 | |
| 1592 | 1656 | $cart = WC()->cart; |
| 1593 | - $this->checkout_route_insurance_fee($cart); | |
| 1594 | 1657 | $response= [ |
| 1595 | - 'routeapp-subtotal' => round($this->get_cart_subtotal_with_only_shippable_items($cart), 2), | |
| 1658 | + 'routeapp-subtotal' => $cart ? round($this->get_cart_subtotal_with_only_shippable_items($cart), 2) : 0, | |
| 1596 | 1659 | ]; |
| 1597 | 1660 | |
| 1598 | 1661 | wp_send_json( $response ); |
| 1599 | 1662 | } |
| @@ -2302,9 +2365,9 @@ | ||
| 2302 | 2365 | if ( empty( $method_id ) ) { |
| 2303 | 2366 | continue; |
| 2304 | 2367 | } |
| 2305 | 2368 | $method_id = explode( ':', $method_id )[0]; |
| 2306 | - if ( in_array( $method_id, $excluded_methods, true ) ) { | |
| 2369 | + if ( $this->routeapp_is_shipping_method_excluded( $method_id ) ) { | |
| 2307 | 2370 | return 1; |
| 2308 | 2371 | } |
| 2309 | 2372 | } |
| 2310 | 2373 | return 0; |
| @@ -2324,9 +2387,9 @@ | ||
| 2324 | 2387 | if ( ! isset( $packages['rates'][ $chosen_id ] ) ) { |
| 2325 | 2388 | return 0; |
| 2326 | 2389 | } |
| 2327 | 2390 | $method_id = explode( ':', $packages['rates'][ $chosen_id ]->id )[0]; |
| 2328 | - if ( in_array( $method_id, $excluded_methods, true ) ) { | |
| 2391 | + if ( $this->routeapp_is_shipping_method_excluded( $method_id ) ) { | |
| 2329 | 2392 | return 1; |
| 2330 | 2393 | } |
| 2331 | 2394 | |
| 2332 | 2395 | return 0; |