Per product flat rate shipping WooCommerce - woocommerce

I'd like to do per product shipping programmatically. And I don't want to use the plugin. This seems like it would easy:
add meta field called "shipping_price" for each product.
hook into checkout and update shipping based off each products "shipping_price" that's in your cart
I know how to do #1. but any ideas on the best way to achieve #2?

This can be done in the following way, using get_post_meta to get the meta field 'shipping_price'
Note 1: to test this code added a line with dummy data
Note 2: Don't forget to specify the $rate->method_id
function filter_woocommerce_package_rates( $rates, $package ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) return;
// Set variable
$cost = 0;
// Loop through line items
foreach( $package['contents'] as $line_item ) {
// Get product id
$product_id = $line_item['product_id'];
// Quantity
$quantity = $line_item['quantity'];
// Get post meta
$shipping_price = get_post_meta( $product_id, 'shipping_price', true);
// DEBUG, for testing purposes, REMOVE AFTERWARDS!!
$shipping_price = 10;
if ( $shipping_price ) {
$cost += $shipping_price * $quantity;
}
}
if ( $cost > 0 ) {
// (Multiple)
foreach ( $rates as $rate_key => $rate ) {
// Targeting
if ( in_array( $rate->method_id, array( 'free_shipping', 'distance_rate', 'table_rate' ) ) ) {
// Set rate cost
$rates[$rate_key]->cost = $cost;
}
}
// Single
// Set rate cost
// $rates['free_shipping']->cost = $cost;
}
return $rates;
}
add_filter( 'woocommerce_package_rates', 'filter_woocommerce_package_rates', 100, 2 );

Expanding on 7uc1f3rs answer. I had to slightly modify to get it to work with WooCommerce 3.0+ new way of doing it.
$rates[$rate_key]->cost = $cost;
needs to be
$rates[$rate_key]->set_cost($cost);
You can use functions from https://docs.woocommerce.com/wc-apidocs/class-WC_Shipping_Rate.html for $rates
function filter_woocommerce_package_rates( $rates, $package ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) return;
// Set variable
$cost = 0;
// Loop through line items
foreach( $package['contents'] as $line_item ) {
// Get product id
$product_id = $line_item['product_id'];
// Quantity
$quantity = $line_item['quantity'];
// Get post meta
//$shipping_price = get_post_meta( $product_id, 'shipping_price', true);
// DEBUG, for testing purposes, REMOVE AFTERWARDS!!
$shipping_price = 10;
if ( $shipping_price ) {
$cost += $shipping_price * $quantity;
}
}
if ( $cost > 0 ) {
// (Multiple)
foreach ( $rates as $rate_key => $rate ) {
// Targeting
if ( in_array( $rate->method_id, array( 'free_shipping', 'flat_rate' ) ) ) {
// Set rate cost
// use functions from https://docs.woocommerce.com/wc-apidocs/class-WC_Shipping_Rate.html
$rates[$rate_key]->set_cost($cost);
}
}
// Single
// Set rate cost
//$rates['flat_rate:1']->set_cost($cost);
// use this to see your rates
//echo '<pre>'; print_r($rates); echo '</pre>';
}
return $rates;
}
add_filter( 'woocommerce_package_rates', 'filter_woocommerce_package_rates', 100, 2 );

Related

How to update WooCommerce product price based on the selected payment gateway? [duplicate]

I want to add percentage value to cart item prices based on the selected payment gateway.
The problem I am facing is variation product price is not updating for the product price. Initially selected price is showing all the time.
How can I get the changed price accordingly?
My code so far:
// Set custom cart item price
function add_custom_price( $cart ) {
// This is necessary for WC 3.0+
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// Avoiding hook repetition (when using price calculations for example | optional)
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item ) {
$chosen_payment_method = WC()->session->get('chosen_payment_method');
if($chosen_payment_method == 'cod') {
$increaseby = 3;
} elseif($chosen_payment_method == 'paypal') {
$increaseby = 8;
} else {
$increaseby = 10;
}
$price = get_post_meta($cart_item['product_id'] , '_price', true);
$price = $price + (($price * $increaseby)/100);
$cart_item['data']->set_price( $price );
}
}
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price', 1000, 1);
Any help highly appreciated.
There are some mistakes in your code
Use WC()->session->get( 'chosen_payment_method' ); outside the foreach loop
get_post_meta() is not needed to get the price, you can use get_price()
You will also need jQuery that is triggered when changing the payment method.
So you get:
function action_woocommerce_before_calculate_totals( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Get payment method
$chosen_payment_method = WC()->session->get( 'chosen_payment_method' );
// Compare
if ( $chosen_payment_method == 'cod' ) {
$increaseby = 3;
} elseif ( $chosen_payment_method == 'paypal' ) {
$increaseby = 8;
} else {
$increaseby = 10;
}
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
// Get price
$price = $cart_item['data']->get_price();
// Set price
$cart_item['data']->set_price( $price + ( $price * $increaseby ) / 100 );
}
}
add_action( 'woocommerce_before_calculate_totals', 'action_woocommerce_before_calculate_totals', 10, 1 );
function action_wp_footer() {
if ( is_checkout() && ! is_wc_endpoint_url() ) :
?>
<script type="text/javascript">
jQuery(function($){
$( 'form.checkout' ).on( 'change', 'input[name="payment_method"]', function() {
$(document.body).trigger( 'update_checkout' );
});
});
</script>
<?php
endif;
}
add_action( 'wp_footer', 'action_wp_footer' );

How to find out the cheapest shipping method and change the shipping label in woocommerce?

So here's what I need to do: find out the cheapest shipping method and change the "Free Shipping" method label to "Free Shipping" + "Cheapest method label". This way the customer will be able to know what is the shipping method used for the free shipping.
First I thought about global variables.. then used the wp_options, but I am not certain if the wp_options are user shared or user-specific data.
Would you have any good ideas or corrections on the following code, which by the way isn't working? Thanks!
add_filter( 'woocommerce_shipping_chosen_method', 'wf_default_shipping_method', 10 );
function wf_default_shipping_method( $method ) {
$the_cheapest_cost = 1000000;
$packages = WC()->shipping()->get_packages()[0]['rates'];
foreach ( array_keys( $packages ) as $key ) {
if ( ( $packages[$key]->cost > 0 ) && ( $packages[$key]->cost < $the_cheapest_cost ) ) {
$the_cheapest_cost = $packages[$key]->cost;
$transport_label = $packages[$key]->label;
$method_id = $packages[$key]->id;
}
}
if(get_option('atransport')){
update_option('atransport', $transport_label);
}else{
add_option('atransport', $transport_label);
}
return $method_id;
}
add_filter( 'woocommerce_package_rates', 'change_shipping_methods_label_names', 20, 2 );
function change_shipping_methods_label_names( $rates, $package ) {
$transport = get_option('atransport');
foreach( $rates as $rate_key => $rate ) {
if ( __( 'Free Shipping', 'woocommerce' ) == $rate->label ){
$rates[$rate_key]->label = __( 'FreeShipping -' . $transport, 'woocommerce' ); // New label name
}
}
delete_option('atransport');
return $rates;
}
You can iterate a loop of $rates and get the cheapest method. then based on the found cheapest method you can change the label. code will go in your active theme functions.php file.
add_filter( 'woocommerce_package_rates', 'hide_other_shipping_when_free_is_available', 100, 1 );
function hide_other_shipping_when_free_is_available( $rates ) {
$cheapest_method = '';
// Loop through shipping rates
if ( is_array( $rates ) ) {
foreach ( $rates as $key => $rate ) {
// Set variables when the rate is cheaper than the one saved
if ( empty( $cheapest_method ) || $rate->cost < $cheapest_method->cost ) {
$cheapest_method = $rate;
}
}
}
// Return the cheapest rate when possible
if ( ! empty( $cheapest_method ) ) {
$free = array();
foreach ( $rates as $rate_id => $rate ) {
if ( $rate->id === $cheapest_method->id ) {
if ( __( 'Free Shipping', 'woocommerce' ) == $rate->label ){
$rates[ $rate_id ] = $rate;
// Here we append the labbelled saved price formated display
$rates[ $rate_id ]->label = 'Free Shipping '.$rates[ $rate_id ]->label;
break; // stop the loop
}
}
}
return $rates;
}
return $rates;
}
Tested and works

Disable shipping method when total of products from certain category below $99

I'm trying to disable certain shipping method if the sum of the products from category "Pillows" is below $99 (it's some sort of a premium shipping active only if customer orders more than $99).
I have this piece of code
add_filter( 'woocommerce_package_rates', 'unset_shipping_below_free', 10, 2 );
function unset_shipping_below_free( $rates, $package ) {
$categories = array('PILLOWS'); // Defined targeted product categories
$threshold = 99; // Defined threshold amount
$cart = WC()->cart;
$cart_items = $cart->get_cart();
//How to sum the value of Pillows in the cart?
if ( $subtotal_pillows < $threshold ) {
if ( isset( $rates['free_shipping:4'] ) ) {
unset( $rates['free_shipping:4'] );
}
if ( isset( $rates['flexible_shipping_single:5'] ) ) {
unset( $rates['flexible_shipping_single:5'] );
}
}
return $rates;
}
But I can't figure out how to sum the value of the Pillows. Any help greatly appeciated
You should try this:
add_filter( 'woocommerce_package_rates', 'unset_shipping_below_free', 10, 2 );
function unset_shipping_below_free( $rates, $package ) {
$categories = array('pillows'); // Defined targeted product categories slug
$threshold = 99; // Defined threshold amount
$subtotal_pillows = 0;
// Loop through all products in the Cart
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
if ( has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
$quantity = $cart_item['quantity'];
$line_subtotal = $cart_item['line_subtotal'];
$item_price = $line_subtotal / $quantity;
$subtotal_pillows = $subtotal_pillows+$item_price;
}
}
//echo $subtotal_pillows; exit;
//How to sum the value of Pillows in the cart?
if ( $subtotal_pillows < $threshold ) {
if ( isset( $rates['free_shipping:4'] ) ) {
unset( $rates['free_shipping:4'] );
}
if ( isset( $rates['flexible_shipping_single:5'] ) ) {
unset( $rates['flexible_shipping_single:5'] );
}
}
return $rates;
}
Note: $categories = array('pillows'); you should use category slug instead of category name for that you will got $subtotal_pillows properly category wise

Disable payment gateway when specific products in WooCommerce cart

I want to unset the payment method 'bacs' for the product ID 6197.
I have tried the code below, but this does not have the desired result.
add_filter( 'woocommerce_available_payment_gateways', 'wp_unset_gateway_by_id', 10, 2 );`
function wp_unset_gateway_by_id( $available_gateways, $products) {
global $woocommerce;
$unset = false;
$product_ids = array('6197');
if( in_array( $product->get_id(), $product_ids ) || ( $product->is_type('variation') && in_array( $product->get_parent_id(), $product_ids ) ) ){
unset( $available_gateways['bacs'] );
return $available_gateways;
}
}
I believe I am doing something wrong, but I am relatively new to this. Any advice would be appreciated.
$products is not passed as argument to the woocommerce_available_payment_gateways filter hook
Apply it in the following way:
function filter_woocommerce_available_payment_gateways( $payment_gateways ) {
// Not on admin
if ( is_admin() ) return $payment_gateways;
// The targeted product ids (several can be added, separated by a comma)
$targeted_ids = array( 6197 );
// Flag
$found = false;
// WC Cart
if ( WC()->cart ) {
// Loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item ) {
if ( array_intersect( $targeted_ids, array( $cart_item['product_id'], $cart_item['variation_id'] ) ) ) {
$found = true;
break;
}
}
}
// True
if ( $found ) {
// Bacs
if ( isset( $payment_gateways['bacs'] ) ) {
unset( $payment_gateways['bacs'] );
}
}
return $payment_gateways;
}
add_filter( 'woocommerce_available_payment_gateways', 'filter_woocommerce_available_payment_gateways', 10, 1 );

How to dynamically change shipping costs in WooCommerce checkout based on a custom field?

How can I change shipping cost in Woocommerce by Ajax request?
I tried this:
add_action('wp_ajax_set_shipping_price', 'set_shipping_price');
add_action('wp_ajax_nopriv_set_shipping_price', 'set_shipping_price');
function set_shipping_price(){
$packages = WC()->cart->get_shipping_packages();
foreach ($packages as $package_key => $package){
$session_key = 'shipping_for_package_'.$package_key;
$stored_rates = WC()->session->__unset( $session_key );
$WC_Shipping = new WC_Shipping();
$WC_Shipping->calculate_shipping_for_package( $package, $package_key = 0);
WC()->cart->calculate_shipping();
WC()->cart->calculate_totals();
}
wp_die();
}
and:
add_filter( 'woocommerce_package_rates', 'custom_shipping_costs', 20, 2 );
function custom_shipping_costs( $rates, $package ) {
if (isset($_POST['cost'])){
$new_cost = $_POST['cost'];
}
$new_cost = 0;
$tax_rate = 0.2;
foreach( $rates as $rate_key => $rate ){
if( $rate->method_id != 'free_shipping'){
$rates[$rate_key]->cost = $new_cost;
$taxes = array();
foreach ($rates[$rate_key]->taxes as $key => $tax){
if( $rates[$rate_key]->taxes[$key] > 0 )
$taxes[$key] = $new_cost * $tax_rate;
}
$rates[$rate_key]->taxes = $taxes;
}
}
return $rates;
}
The hook woocommerce_package_rates works at page load but do nothing by ajax. Help please.
Here is the explanation on how to get post data from an Ajax call.
Additionally, you can use the WooCommerce update_checkout event to update the checkout (and thus recalculate shipping costs) after a field in the checkout has changed or been clicked (instead of creating a custom Ajax call). Here you will find a complete list.
So, assuming you want to recalculate shipping costs when the field with id custom_shipping_price (for example) changes, you can use this script:
// updates the checkout (and shipping charge calculation) when the value of a field changes
add_action( 'wp_footer', 'update_checkout' );
function update_checkout() {
// only in the checkout
if ( ! is_checkout() ) {
return;
}
?>
<script type="text/javascript">
jQuery( document ).ready(function( $ ) {
// when the value of the field with id "custom_shipping_price" changes it updates the checkout
jQuery('#custom_shipping_price').change(function(){
jQuery('body').trigger('update_checkout');
});
});
</script>
<?php
}
Now you will need to use the woocommerce_package_rates hook to calculate the new shipping cost.
Make sure you initialize the value of the $new_cost variable in
case the custom field is not set or is empty.
Here the function:
// update the shipping cost based on a custom field in the checkout
add_filter( 'woocommerce_package_rates', 'update_shipping_cost_based_on_custom_field', 10, 2 );
function update_shipping_cost_based_on_custom_field( $rates, $package ) {
if ( ! $_POST || is_admin() || ! is_ajax() ) {
return;
}
// gets the post serialized data sent with the Ajax call
if ( isset( $_POST['post_data'] ) ) {
parse_str( $_POST['post_data'], $post_data );
} else {
$post_data = $_POST;
}
// set the cost of shipping (if the custom field should be empty)
$new_cost = 0;
// if the field is set it gets the value
if ( isset( $post_data['custom_shipping_price'] ) && ! empty( $post_data['custom_shipping_price'] ) ) {
// forces conversion of value into number
$new_cost = (float) str_replace( ',', '.', $post_data['custom_shipping_price'] );
}
// set the percentage of taxes (ex. 22%)
$tax_rate = 0.22;
foreach( $rates as $rate_key => $rate ) {
if ( 'free_shipping' !== $rate->method_id ) {
// set rate cost
$rates[$rate_key]->cost = $new_cost;
// set taxes rate cost (if enabled)
$taxes = array();
foreach ( $rates[$rate_key]->taxes as $key => $tax ) {
if ( $rates[$rate_key]->taxes[$key] > 0 ) {
$taxes[$key] = $new_cost * $tax_rate;
}
}
$rates[$rate_key]->taxes = $taxes;
}
}
return $rates;
}
Both codes have been tested and work. Add them to your active theme's functions.php.

Resources