Search code examples
phpwoocommercesession-variablesshipping-method

Update prices for shipping methods dynamically in WooCommerce


I am trying to update the shipping prices for my shipping methods dynamically. I get the shipping costs from an API and I want to update the prices when the I successfully get the prices from the API response. I am using the following code :

function handle_api_response( $rates ) {

   // api call code here.....
   if ($response_code === 200){
        $response_data = json_decode($response, true);
        $price = $response_data['shipments']
   }

   add_filter( 'woocommerce_package_rates', 'set_shipping_prices', PHP_INT_MAX, 1 );
   function set_shipping_prices( $rates ) {
     foreach ( $rates as $rate_id => $rate ) {
        $rates[ $rate_id ]->cost = $price;
     }
      return $rates;
   }    

}

The above code does not work but if I take the filter outside of the handle_api_response function and set some static value to the cost it seems to work. Like this:

function handle_api_response( $rates ) {

   // api call code here.....

   if ($response_code === 200){
        $response_data = json_decode($response, true);
        $price = $response_data['shipments']
   }

}

add_filter( 'woocommerce_package_rates', 'set_shipping_prices', PHP_INT_MAX, 1 );
   function set_shipping_prices( $rates ) {
     foreach ( $rates as $rate_id => $rate ) {
        $rates[ $rate_id ]->cost = 50;
     }
      return $rates;
}    

My problem is that, since I get the price value from the API I need to pass the price from the API response to set_shipping_prices which runs when the filter is triggered.


Solution

  • This answer code is untested and may need some adjustments, but it can be the right path, to make your external API call effective.

    We can try using a WC_Session variables to set the shipping cost that we want to pass:

    function handle_api_response( $rates ) {
    
        // api call code here.....
     
        if ($response_code === 200){
            $response_data = json_decode($response, true);
            $cost = $response_data['shipments'];
             
            if ( $cost > 0 ) {
                // Set the cost in a WC Session variable
                WC()->session->set('shipment_cost', floatval($cost));
    
                // Trying to trigger "Update checkout" Ajax event
                ?><script>jQuery('body').trigger('update_checkout');</script><?php
            }
        }
    }
    

    Then we can recall the WC_Session variable in your hooked function like:

    add_filter('woocommerce_package_rates', 'update_shipping_costs', 10, 2);
    function update_shipping_costs( $rates, $package ) {
        foreach ( $rates as $rate_id => $rate ) {
            // Get the new cost from the Session variable
            $new_cost = WC()->session->get('shipment_cost');
    
            if( isset($rate->cost) && $rate->cost > 0 && $new_cost > 0 ) {
                $rates[ $rate_id ]->cost = $new_cost; // Set the new cost
            }
        }
        return $rates;
    }
    

    But we need something else to refresh cached chipping methods, to get it effective:

    add_action('woocommerce_checkout_update_order_review', 'refresh_shipping_methods');
    function refresh_shipping_methods( $post_data ){
        $bool = true;
    
        // We check for the session variable
        if ( WC()->session->get('shipment_cost') > 0 ) {
            $bool = false;
        }
    
        // Mandatory to make it work with shipping methods
        foreach ( WC()->cart->get_shipping_packages() as $package_key => $package ){
            WC()->session->set( 'shipping_for_package_' . $package_key, $bool );
        }
        WC()->cart->calculate_shipping();
    }
    

    And to finish we unset the WC Session variable (on checkout first load and thankyou page):

    add_action('wp_footer', 'reset_wc_session_variable');
    function reset_wc_session_variable() {
        if (is_checkout() && WC()->session->get('shipment_cost') > 0) {
            WC()->session->__unset('shipment_cost');
        }
    }
    

    Code goes in functions.php file of your child theme (or in a plugin). It could work.

    Related: Remove shipping cost if custom checkbox is checked in WooCommerce Checkout