Display shipping cost on product page - WooCommerce - php

I'm trying to make a custom page on Wordpress that display 2 subscription types in a form with 3 variations each (monthly, 6 month, 12 month). Each variation gets a radio button and I have a total price that is live updated when user clicks on the radio buttons. This part is working.
But now, I want to add 3 other radio buttons to choose the shipment method. (When user select one, it'll live update the total price too).
I've been searching a long time how to get shipping cost for a product but nothing has worked.

This question is too broad. So I can answer partially as you should need to make some work yourself, and ask later more specific questions…
Now the correct way to set shipping data on product page, is to use Ajax to update the data, as the action is made by the customer on client side (browser), avoiding 'post' and reload the page. But this should be your work...
1) Customer location (for shipping Zones):
You should need to get the customer location or shipping zone first.
Then you will need to update the customer country in WC()->session and in WC()->customer objects. This can be done with the following:
## Get the geolocated customer country code *(if enabled)*:
$country_code = WC()->customer->get_billing_country();
// or
// $country_code = WC()->customer->get_shipping_country();
## Set a new country code
$new_country_code = 'FR';
## 1. WC_session: set customer billing and shipping country
// Get the data
$customer_session = WC()->session->get( 'customer' );
// Change some data
$customer_session['country'] = $new_country_code; // Billing
$customer_session['shipping_country'] = $new_country_code; // Shipping
// Set the changed data
$customer_session = WC()->session->set( 'customer', $customer_session );
## 2. WC_Customer: set customer billing and shipping country
WC()->customer->set_billing_country( $new_country_code );
WC()->customer->set_shipping_country( $new_country_code );
2) The shipping methods (by Shipping Zone, with costs):
In Woocommerce the Shipping methods for a Shipping Zone are only available when customer add a product to cart…
Based on this answer code: Display shipping methods to frontend as in the admin panel? we can make a custom array of the necessary data to be used to get the shipping methods by shipping Zones, with costs and everything needed.
The code below is much more complete and include the shipping methods costs:
// Initializing variable
$zones = $data = $classes_keys = array();
// Rest of the World zone
$zone = new \WC_Shipping_Zone(0);
$zones[$zone->get_id()] = $zone->get_data();
$zones[$zone->get_id()]['formatted_zone_location'] = $zone->get_formatted_location();
$zones[$zone->get_id()]['shipping_methods'] = $zone->get_shipping_methods();
// Merging shipping zones
$shipping_zones = array_merge( $zones, WC_Shipping_Zones::get_zones() );
// Shipping Classes
$shipping = new \WC_Shipping();
$shipping_classes = $shipping->get_shipping_classes();
// The Shipping Classes for costs in "Flat rate" Shipping Method
foreach($shipping_classes as $shipping_class) {
//
$key_class_cost = 'class_cost_'.$shipping_class->term_id;
// The shipping classes
$classes_keys[$shipping_class->term_id] = array(
'term_id' => $shipping_class->term_id,
'name' => $shipping_class->name,
'slug' => $shipping_class->slug,
'count' => $shipping_class->count,
'key_cost' => $key_class_cost
);
}
// For 'No class" cost
$classes_keys[0] = array(
'term_id' => '',
'name' => 'No shipping class',
'slug' => 'no_class',
'count' => '',
'key_cost' => 'no_class_cost'
);
foreach ( $shipping_zones as $shipping_zone ) {
$zone_id = $shipping_zone['id'];
$zone_name = $zone_id == '0' ? __('Rest of the word', 'woocommerce') : $shipping_zone['zone_name'];
$zone_locations = $shipping_zone['zone_locations']; // array
$zone_location_name = $shipping_zone['formatted_zone_location'];
// Set the data in an array:
$data[$zone_id]= array(
'zone_id' => $zone_id,
'zone_name' => $zone_name,
'zone_location_name' => $zone_location_name,
'zone_locations' => $zone_locations,
'shipping_methods' => array()
);
foreach ( $shipping_zone['shipping_methods'] as $sm_obj ) {
$method_id = $sm_obj->id;
$instance_id = $sm_obj->get_instance_id();
$enabled = $sm_obj->is_enabled() ? true : 0;
// Settings specific to each shipping method
$instance_settings = $sm_obj->instance_settings;
if( $enabled ){
$data[$zone_id]['shipping_methods'][$instance_id] = array(
'$method_id' => $sm_obj->id,
'instance_id' => $instance_id,
'rate_id' => $sm_obj->get_rate_id(),
'default_name' => $sm_obj->get_method_title(),
'custom_name' => $sm_obj->get_title(),
);
if( $method_id == 'free_shipping' ){
$data[$zone_id]['shipping_methods'][$instance_id]['requires'] = $instance_settings['requires'];
$data[$zone_id]['shipping_methods'][$instance_id]['min_amount'] = $instance_settings['min_amount'];
}
if( $method_id == 'flat_rate' || $method_id == 'local_pickup' ){
$data[$zone_id]['shipping_methods'][$instance_id]['tax_status'] = $instance_settings['tax_status'];
$data[$zone_id]['shipping_methods'][$instance_id]['cost'] = $sm_obj->cost;
}
if( $method_id == 'flat_rate' ){
$data[$zone_id]['shipping_methods'][$instance_id]['class_costs'] = $instance_settings['class_costs'];
$data[$zone_id]['shipping_methods'][$instance_id]['calculation_type'] = $instance_settings['type'];
$classes_keys[0]['cost'] = $instance_settings['no_class_cost'];
foreach( $instance_settings as $key => $setting )
if ( strpos( $key, 'class_cost_') !== false ){
$class_id = str_replace('class_cost_', '', $key );
$classes_keys[$class_id]['cost'] = $setting;
}
$data[$zone_id]['shipping_methods'][$instance_id]['classes_&_costs'] = $classes_keys;
}
}
}
}
// Row output (for testing)
echo '<pre>'; print_r($data); echo '</pre>';
custom shipping methods
Now if you are using custom shipping methods (enabled sometimes by 3rd party shipping plugins) you will need to make some changes in the code…
Costs and taxes calculation
You should need to make the taxes calculations, as the costs are displayed just as they are set in shipping settings…
3) Product page
Customer location:
You will need first to have a location selector (to define the Shipping Zone) or to set the location based on Woocommerce geolocation.
Shipping Methods:
Once the Shipping Zone is defined, you can get the corresponding Shipping Methods and rates (costs), displaying on this product page your radio buttons for Shipping methods.
To get this, you should need to alter the single product pages:
Overriding the Woocommerce templates via your theme (and WC Subscriptions templates too)
Using the available filters and action hooks
Use Javascript, jQuery to alter prices and Ajax to update the customer data (WC_Session and WC_Customer).
You should need to get/set/update the "chosen_shipping_methods" with the following code (Ajax).
Get the Chosen Shipping method:
$chosen_shipping = WC()->session->get('chosen_shipping_methods')[0];
Set/Update the Chosen Shipping method (through Javascript/Ajax and admin-ajax.php):
// HERE the new method ID
$method_rate_id = array('free_shipping:10');
// Set/Update the Chosen Shipping method
WC()->session->set( 'chosen_shipping_methods', $method_rate_id );

Related

Get Shipping Methods by Product ID - Woocommerce [duplicate]

I am trying to display the shipping class name and the shipping flat rate for the product on WooCommerce single product page. I have used the code below:
$shipping_class_id = $product->get_shipping_class_id();
$shipping_class= $product->get_shipping_class();
$fee = 0;
if ($shipping_class_id) {
$flat_rates = get_option("woocommerce_flat_rates");
$fee = $flat_rates[$shipping_class]['cost'];
}
$flat_rate_settings = get_option("woocommerce_flat_rate_settings");
echo 'Shipping cost: ' . ($flat_rate_settings['cost_per_order'] + $fee);
I can get the shipping class id and also the shipping class slug with this code but not the label. I can not also get the cost of that particular shipping class which is a flat rate of 5 Euro as defined in shipping zones section.
Looking forward to your replies.
1) Get the shipping Class Label name (from a product):
The shipping class is registered as a term, so you can get the product shipping Class label easily using:
$shipping_class_id = $product->get_shipping_class_id();
$shipping_class_term = get_term($shipping_class_id, 'product_shipping_class');
if( ! is_wp_error($shipping_class_term) && is_a($shipping_class_term, 'WP_Term') ) {
$shipping_class_name = $shipping_class_term->name;
}
2) Shipping methods:
As shipping methods are based on customer shipping location, If you have multiple shipping zones, you can't really get for a product a specific shipping method as you would like.
Also the shipping cost is calculated from cart packages in multiple possibilities depending on all your different shipping settings.
Now if you have only one shipping zone and you want to target a specific shipping method and get the shipping method title and it's approximate cost (like in settings), based on the product tax class, try the following:
// HERE set your targeted shipping Zone type (method ID)
$targeted_shipping_method_id = 'flat_rate';
// The product shipping class ID
$product_class_id = $product->get_shipping_class_id();
$zone_ids = array_keys( array('') + WC_Shipping_Zones::get_zones() );
// Loop through Zone IDs
foreach ( $zone_ids as $zone_id ) {
// Get the shipping Zone object
$shipping_zone = new WC_Shipping_Zone($zone_id);
// Get all shipping method values for the shipping zone
$shipping_methods = $shipping_zone->get_shipping_methods( true, 'values' );
// Loop through Zone IDs
foreach ( $shipping_methods as $instance_id => $shipping_method ) {
// Shipping method rate ID
$rate_id = $shipping_method->get_rate_id();
// Shipping method ID
$method_id = explode( ':', $rate_id);
$method_id = reset($method_id);
// Targeting a specific shipping method ID
if( $method_id === $targeted_shipping_method_id ) {
// Get Shipping method title (label)
$title = $shipping_method->get_title();
$title = empty($title) ? $shipping_method->get_method_title() : $title;
// Get shipping method settings data
$data = $shipping_method->instance_settings;
## COST:
// For a defined shipping class
if( isset($product_class_id) && ! empty($product_class_id)
&& isset($data['class_cost_'.$product_class_id]) ) {
$cost = $data['class_cost_'.$product_class_id];
}
// For no defined shipping class when "no class cost" is defined
elseif( isset($product_class_id) && empty($product_class_id)
&& isset($data['no_class_cost']) && $data['no_class_cost'] > 0 ) {
$cost = $data['no_class_cost'];
}
// When there is no defined shipping class and when "no class cost" is defined
else {
$cost = $data['cost'];
}
// Testing output
echo '<p><strong>'.$title.'</strong>: '.$cost.'</p>';
}
}
}
Now if you want to access shipping method data

WooCommerce: Get chosen shipping method for package and store it as order item meta data

Case:
In checkout packages are splited by shipping class of products (different brands).
Every package have own shipping method that customer can choose.
And finally when customer make an order I need to store chosen shipping method for every product in meta_data so I can see it in order details on admin panel and for the RESTAPI (I use some external services to manage orders).
I tried something like this but I know this is mess, I don't understand how to handle session data and store it in order data.
add_action( 'woocommerce_checkout_create_order_shipping_item', 'order_shipping_item', 20, 4 );
function order_shipping_item($cartItemData,$order){
foreach ( WC()->cart->get_shipping_packages() as $package_id => $package ) {
// Check if a shipping for the current package exist
if ( WC()->session->__isset( 'shipping_for_package_'.$package_id ) ) {
// Loop through shipping rates for the current package
foreach ( WC()->session->get( 'shipping_for_package_'.$package_id )['rates'] as $shipping_rate_id => $shipping_rate ) {
$method_id = $shipping_rate->get_method_id(); // The shipping method slug
$instance_id = $shipping_rate->get_instance_id(); // The instance ID
$label_name = $shipping_rate->get_label(); // The label name of the method
}
}
foreach ($package['contents'] as $item) {
$item->add_meta_data('_shipping_method',$label_name);
# code...
}
}
}
Right now I have no idea how to achieve this, any clues? help!
Here is the way to save as custom order item meta data the chosen shipping method details for each order item:
// Custom function that get the chosen shipping method details for a cart item
function get_cart_item_shipping_method( $cart_item_key ){
$chosen_shippings = WC()->session->get( 'chosen_shipping_methods' ); // The chosen shipping methods
foreach( WC()->cart->get_shipping_packages() as $id => $package ) {
$chosen = $chosen_shippings[$id]; // The chosen shipping method
if( isset($package['contents'][$cart_item_key]) && WC()->session->__isset('shipping_for_package_'.$id) ) {
return WC()->session->get('shipping_for_package_'.$id)['rates'][$chosen];
}
}
}
// Save shipping method details in order line items (product) as custom order item meta data
add_action( 'woocommerce_checkout_create_order_line_item', 'add_order_line_item_custom_meta', 10, 4 );
function add_order_line_item_custom_meta( $item, $cart_item_key, $values, $order ) {
// Load shipping rate for this item
$rate = get_cart_item_shipping_method( $cart_item_key );
// Set custom order item meta data
$item->update_meta_data( '_shipping_rate_id', $rate->id ); // the shipping rate ID
$item->update_meta_data( '_shipping_rate_method_id', $rate->method_id ); // the shipping rate method ID
$item->update_meta_data( '_shipping_rate_instance_id', (int) $rate->instance_id ); // the shipping rate instance ID
$item->update_meta_data( '_shipping_rate_label', $rate->label ); // the shipping rate label
$item->update_meta_data( '_shipping_rate_cost', wc_format_decimal( (float) $rate->cost ) ); // the shipping rate cost
$item->update_meta_data( '_shipping_rate_tax', wc_format_decimal( (float) array_sum( $rate->taxes ) ) ); // the shipping rate tax
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.

Retrieve customer available shipping methods details in WooCommerce

How can I retrieve the shipping methods that are visible to the end user? ( not all shipping methods defined in Woocommerce ). I am using the "Shipping Zones by Drawing for WooCommerce" plugin. I need multiple radiuses around the store. The issue is that I have more than one and this plugin will only hide the radius where the user is located outside of it and will show the rest ( I need to show only one of them, the cheapest ).
I have tried to print the rates from woocommerce_package_rates and WC()->session but these will show all shipping methods defined including the one that it is not shown the user.
To get the costumer available shipping methods when shipping location is defined and when cart is not empty, you can use:
// Get shipping packages
$shipping_packages = WC()->cart->get_shipping_packages();
foreach( array_keys( $shipping_packages ) as $key ) {
if( $shipping_for_package = WC()->session->get('shipping_for_package_'.$key) ) {
if( isset($shipping_for_package['rates']) ) {
// Loop through customer available shipping methods
foreach ( $shipping_for_package['rates'] as $rate_key => $rate ) {
$rate_id = $rate->id; // the shipping method rate ID (or $rate_key)
$method_id = $rate->method_id; // the shipping method label
$instance_id = $rate->instance_id; // The instance ID
$cost = $rate->label; // The cost
$label = $rate->label; // The label name
$taxes = $rate->taxes; // The taxes (array)
print_pr($label);
}
}
}
}
Now to get the chosen shipping method you will use:
WC()->session->get('chosen_shipping_methods'); // (Array)

How to change Shipping Zones depending on the payment option in Woocommerce?

Target: I want to add different shipping cost for the different payment gateways depending on the pin code user have.
Tried Using Shipping Zones:
I have 3 different shipping zones added in my woo-commerce dashboard. All the zones are created using the pin codes of the city and flat rate. However, if the pin code exists in multiple zones, then the top level zone gets the first priority and all other zones get ignored at the checkout page.
Now I want to change that zone depending on the Payment Gateway user chooses. For example user, 'A' has the pin code '123456' and this pin code exists in two Shipping Zones: 'SH1', 'SH2'. So, if the user chose 'Cash On Delivery' option, then I want to disable shipping zone 'SH1' for him while activating 'SH2'.
Here is the code I tried, but not works.
add_filter( 'woocommerce_available_payment_gateways', 'change_user_zone', 20, 1);
function change_user_zone( $gateways ){
$targeted_zones_names = array('COD FCity'); // Targeted zone names
$current_payment_gateway = WC()->session->get('chosen_payment_method'); // Selected payment gateway
// Current zone name
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' ); // The chosen shipping mehod
$chosen_method = explode(':', reset($chosen_methods) );
$shipping_zone = WC_Shipping_Zones::get_zone_by( 'instance_id', $chosen_method[1] );
$current_zone_name = $shipping_zone->get_zone_name();
if($current_payment_gateway === "cod" && $current_zone_name === "COD FCity"){
$WC_Shipping_Zone = new WC_Shipping_Zone();
$var = $WC_Shipping_Zone->set_zone_locations( "India - Maharashtra - Mumbai" );
}
return $gateways;
}
Please help!
Note: I'm looking for a coding solution. If you want to suggest any plugin for this, then use the comment section, please.
Ok, I don't find a way to change the shipping zone. So I added the extra fee in the cart depending on the Payment Gateway and Shipping Zone.
add_filter( 'woocommerce_before_calculate_totals', 'update_the_cart_with_extra_fee');
function update_the_cart_with_extra_fee(){
global $woocommerce;
$targeted_zones_names = array('SH1'); // Targeted zone names
$current_payment_gateway = WC()->session->get('chosen_payment_method'); // Selected payment gateway
// Current zone name
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' ); // The chosen shipping mehod
$chosen_method = explode(':', reset($chosen_methods) );
$shipping_zone = WC_Shipping_Zones::get_zone_by( 'instance_id', $chosen_method[1] );
$current_zone_name = $shipping_zone->get_zone_name();
if($current_payment_gateway === "cod" && $current_zone_name === "SH1"){
$extra_shipping_cost = 0;
//Loop through the cart to find out the extra costs
foreach ( $woocommerce->cart->get_cart() as $cart_item_key => $values ) {
//Get the product info
$_product = $values['data'];
//Get the custom field value - make sure it's i.e. 10 not $10.00
$custom_shipping_cost = 60;
//Adding together the extra costs
$extra_shipping_cost = $extra_shipping_cost + $custom_shipping_cost;
$woocommerce->cart->add_fee( __('Cash on Delivery', 'woocommerce'), $extra_shipping_cost ); // Place this outside of foreach loop if you want to add fee for every product in the cart.
}
}
}
I hope this will help someone.

WooCommerce Admin Create Order & Adding Shipping on Order Pay

I am creating order in the admin. I added some functionality to the order pay page which is sent to the customer (form-pay.php). I added ability to remove items from the order as well as update your billing and shipping info (both for the order and account). I use ajax and calculate_totals() after an item is removed, which works.
However, I can't seem to figure out how to get shipping applied to the order. I need this to happen when the order is created in the admin and when someone removes an item on the frontend.
I tried just setting the shipping post meta but that isn't working.
function my_order_update_shipping($order_id, $items) {
$order = wc_get_order($order_id);
$order_subtotal = $order->get_subtotal();
if($order_subtotal > '17.99'){
update_post_meta($order_id, '_order_shipping', '0');
}else{
update_post_meta($order_id, '_order_shipping', '4');
}
}
add_action('woocommerce_before_save_order_items', 'my_order_update_shipping');
How can I achieve this? Or apply a shipping method in this way?
I finally figure this out. Works good but you need to click the "recalculate" button when adding/removing an item in the admin > create order. The below code applies 1 of 2 shipping methods based on a static subtotal amount.
$delivery_zones = WC_Shipping_Zones::get_zones();
foreach ((array) $delivery_zones as $key => $the_zone) {
$shipping_methods = $the_zone['shipping_methods'];
}
// Apply Correct Shipping Method
if ($order_subtotal > '17.99') {
$rate = $shipping_methods[2];
$item = new WC_Order_Item_Shipping();
$item->set_props(array('method_id' => $rate->id, 'total' => wc_format_decimal($rate->cost)));
$order->add_item($item);
} else {
$rate = $shipping_methods[1];
$item = new WC_Order_Item_Shipping();
$item->set_props(array('method_id' => $rate->id, 'total' => wc_format_decimal($rate->cost)));
$order->add_item($item);
}
$order->calculate_totals();
$order->save();

Categories