I'm using Add fee to Woocommerce Shipping Class without the use of zones or flat rate answer code, that allows me add an additional 'Fee' if any product from the 'Dangerous Goods' shipping class has been added to cart.
This is great as it allows me to cover the additional costs associated to packaging/shipping, however it is also adding the 'fee' when users select 'Local Pickup' shipping option.
I would love to exclude 'Local Pickup' from the additional fee if selected.
Therefore i use:
function fees_fees_fees() {
$shippingClasses['dangerous-goods'] = ['description' => 'Dangerous Goods Fee', 'fee' => 130];
foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
$shipping_class = get_the_terms( $values['product_id'], 'product_shipping_class' );
foreach($shippingClasses as $key => $val) {
if ( isset( $shipping_class[0]->slug ) && in_array( $shipping_class[0]->slug, [$key] ) ) {
WC()->cart->add_fee( __($val['description'], 'woocommerce'), $val['fee'] ); }
}
}
}
add_action( 'woocommerce_cart_calculate_fees', 'fees_fees_fees' );
However, I'm stuck on the next step. Any adivce?
The use of WC()->cart is not necessary, as $cart is already passed to the callback function
WC()->session->get( 'chosen_shipping_methods' ) can be used to get the chosen shipping method, then it is a matter of adding an if condition
So you get:
function action_woocommerce_cart_calculate_fees( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) return;
// Get chosen shipping method
$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
$chosen_shipping_method = substr( $chosen_shipping_methods[0], 0, strpos( $chosen_shipping_methods[0], ':' ) );
// NOT for local pickup
if ( $chosen_shipping_method != 'local_pickup' ) {
// Settings
$shipping_classes['dangerous-goods'] = [ 'description' => 'Dangerous Goods Fee', 'fee' => 130 ];
// Loop though cart items
foreach ( $cart->get_cart() as $cart_item ) {
$shipping_class = get_the_terms( $cart_item['product_id'], 'product_shipping_class' );
foreach ( $shipping_classes as $key => $val ) {
if ( isset( $shipping_class[0]->slug ) && in_array( $shipping_class[0]->slug, [$key] ) ) {
$cart->add_fee( __( $val['description'], 'woocommerce' ), $val['fee'] );
}
}
}
}
}
add_action( 'woocommerce_cart_calculate_fees', 'action_woocommerce_cart_calculate_fees', 10, 1 );
Simply add check to slug.
if ( isset( $shipping_class[0]->slug ) && in_array( $shipping_class[0]->slug, [$key] ) && $shipping_class[0]->slug != 'local_pickup') {
Related
I can add a product to cart, when a certain coupon is used via the woocommerce_applied_coupon hook and the add_to_cart() function
add_action('woocommerce_applied_coupon', 'apply_product_on_coupon');
function apply_product_on_coupon( ) {
global $woocommerce;
$coupon_id = 'mybday';
$free_product_id = 131468;
if(in_array($coupon_id, $woocommerce->cart->get_applied_coupons())){
$woocommerce->cart->add_to_cart($free_product_id, 1);
}
}
My question: is there a way to also discount it to 0 in the same callback function?
Your current code contains some mistakes or could be optimized:
global $woocommerce can be replaced by WC()
$woocommerce->cart->get_applied_coupons() is not necessary, as the coupon that have been applied is passed to the callback function.
Instead, use the last available argument in WC_Cart::add_to_cart() method, which will allow you to add any custom cart item data. Then you will be able to get that data easily from the cart object.
So you get:
function action_woocommerce_applied_coupon( $coupon_code ) {
// Settings
$product_id = 131468;
$quantity = 1;
$free_price = 0;
$coupon_codes = array( 'coupon1', 'mybday' );
// Compare
if ( in_array( $coupon_code, $coupon_codes ) ) {
// Add product to cart
WC()->cart->add_to_cart( $product_id, $quantity, 0, array(), array( 'free_price' => $free_price ) );
}
}
add_action( 'woocommerce_applied_coupon', 'action_woocommerce_applied_coupon', 10, 1 );
// Set free price from custom cart item data
function action_woocommerce_before_calculate_totals( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 ) return;
// Loop through cart contents
foreach ( $cart->get_cart_contents() as $cart_item ) {
if ( isset( $cart_item['free_price'] ) ) {
$cart_item['data']->set_price( $cart_item['free_price'] );
}
}
}
add_action( 'woocommerce_before_calculate_totals', 'action_woocommerce_before_calculate_totals', 10, 1 );
Note: in addition to using woocommerce_applied_coupon, you must also use woocommerce_removed_coupon for when the coupon is removed, the product is removed as well
function action_woocommerce_removed_coupon( $coupon_code ) {
// Settings
$product_id = 131468;
$coupon_codes = array( 'coupon1', 'mybday' );
// Compare
if ( in_array( $coupon_code, $coupon_codes ) ) {
// Loop through cart contents
foreach ( WC()->cart->get_cart_contents() as $cart_item_key => $cart_item ) {
// When product in cart
if ( $cart_item['product_id'] == $product_id ) {
// Remove cart item
WC()->cart->remove_cart_item( $cart_item_key );
break;
}
}
}
}
add_action( 'woocommerce_removed_coupon', 'action_woocommerce_removed_coupon', 10, 1 );
I need to automatically set a certain order status (different than processing) when getting a new order.
This is achieved by this function:
add_action('woocommerce_thankyou','change_order_status');
function change_order_status( $order_id ) {
if ( ! $order_id ) { return; }
$order = wc_get_order( $order_id );
if( 'processing'== $order->get_status() ) {
$order->update_status( 'wc-custom-status' );
}
}
This totally works. Now I only need this to happen when a product has a customization.
The way to customize a product is filling an input field before adding to cart. The input is attached to the item data:
// Add custom cart item data
add_filter( 'woocommerce_add_cart_item_data', 'add_custom_cart_item_data', 10, 2 );
function add_custom_cart_item_data( $cart_item_data, $product_id ){
if( isset($_POST['custom_text']) ) {
$cart_item_data['custom_text'] = sanitize_text_field( $_POST['custom_text'] );
$cart_item_data['unique_key'] = md5( microtime().rand() ); // Make each item unique
}
return $cart_item_data;
}
Then the custom text is retrieved and displayed in cart and in the order data using this:
// Display custom cart item data on cart and checkout
add_filter( 'woocommerce_get_item_data', 'display_custom_cart_item_data', 10, 2 );
function display_custom_cart_item_data( $cart_item_data, $cart_item ) {
if ( !empty( $cart_item['custom_text'] ) ){
$cart_item_data[] = array(
'name' => __('Customization', 'woocommerce'),
'value' => $cart_item['custom_text'] // Already sanitized field
);
}
return $cart_item_data;
}
// Save and display custom item data everywhere on orders and email notifications
add_action( 'woocommerce_checkout_create_order_line_item', 'add_product_custom_field_as_order_item_meta', 10, 4 );
function add_product_custom_field_as_order_item_meta( $item, $cart_item_key, $values, $order ) {
if ( isset($values['custom_text']) ) {
$item->update_meta_data('Add on', $values['custom_text'] );
}
}
I'm trying using the if ( isset($values['custom_text']) ) part as a trigger of the function to change the order status only if the product add on is set and other similar methods (like if ( !empty( $cart_item['custom_text'] ) ) but I'm not sure this is the way to go:
add_action('woocommerce_thankyou','change_order_status');
function change_order_status( $order_id ) {
if ( ! $order_id ) {return;}
$order = wc_get_order( $order_id );
if ( isset($values['custom_text']) ) {
if( 'processing'== $order->get_status() ) {
$order->update_status( 'wc-custom-status' );
}
}
}
This above does nothing. Am I anywhere near it with this approach?
EDIT: I tried this too
add_action('woocommerce_thankyou','change_order_status');
function change_order_status( $order_id ) {
if ( ! $order_id ) {return;}
$order = wc_get_order( $order_id );
foreach ( $order->get_items() as $item_id => $item ) {
$allmeta = $item->get_meta_data();
if ( isset($values['custom_text']) ) {
if( 'processing'== $order->get_status() ) {
$order->update_status( 'wc-custom-status' );
}
}
}
}
Your code contains some unnecessary steps as well as some shortcomings
For example, the woocommerce_add_cart_item_data hook contains 3 arguments versus 2
So you get:
// Add custom cart item data
function filter_add_cart_item_data( $cart_item_data, $product_id, $variation_id ) {
// Isset and NOT empty
if ( isset ( $_POST[ 'custom_text' ] ) && ! empty ( $_POST[ 'custom_text' ] ) ) {
$cart_item_data['custom_text'] = sanitize_text_field( $_POST['custom_text'] );
}
return $cart_item_data;
}
add_filter( 'woocommerce_add_cart_item_data', 'filter_add_cart_item_data', 10, 3 );
// Display custom cart item data on cart and checkout
function filter_woocommerce_get_item_data( $cart_data, $cart_item = null ) {
if ( isset ( $cart_item['custom_text'] ) ) {
$cart_data[] = array(
'name' => __( 'Customization', 'woocommerce' ),
'value' => $cart_item['custom_text']
);
}
return $cart_data;
}
add_filter( 'woocommerce_get_item_data', 'filter_woocommerce_get_item_data', 10, 2 );
// Add the information as meta data so that it can be seen as part of the order
// Save and display custom item data everywhere on orders and email notifications
function action_woocommerce_checkout_create_order_line_item( $item, $cart_item_key, $values, $order ) {
if ( isset ( $values['custom_text'] ) ) {
$item->update_meta_data( 'custom_text', $values['custom_text'] );
}
}
add_action( 'woocommerce_checkout_create_order_line_item', 'action_woocommerce_checkout_create_order_line_item', 10, 4 );
// On thankyou page
function action_woocommerce_thankyou( $order_id ) {
// Get $order object
$order = wc_get_order( $order_id );
// Is a WC_Order
if ( is_a( $order, 'WC_Order' ) ) {
// Only when the current order status is 'processing'
if ( $order->get_status() == 'processing' ) {
// Loop trough
foreach ( $order->get_items() as $item ) {
// Get meta
$value = $item->get_meta( 'custom_text' );
// NOT empty
if ( ! empty ( $value ) ) {
// Update status (change to desired status)
$order->update_status( 'cancelled' );
// Stop loop
break;
}
}
}
}
}
add_action( 'woocommerce_thankyou', 'action_woocommerce_thankyou', 10, 1 );
Currently it is just checking if the value exists, to compare it effectively
Change
// NOT empty
if ( ! empty ( $value ) ) {
// Update status
$order->update_status( 'cancelled' );
// Stop loop
break;
}
To
// Compare
if ( $value == 'some value' ) ) {
// Update status
$order->update_status( 'cancelled' );
// Stop loop
break;
}
I am using the following code to auto apply a coupon when customer has $100 or more in cart.
add_action( 'woocommerce_checkout_before_order_review' , 'add_coupon_notice' );
function add_coupon_notice() {
$cart_total = WC()->cart->get_subtotal();
$minimum_amount = 100;
$currency_code = get_woocommerce_currency();
wc_clear_notices();
if ( $cart_total < $minimum_amount ) {
WC()->cart->remove_coupon( '20OFF100' );
wc_print_notice( "Get 20% off if you spend more than $$minimum_amount", 'notice' );
} else {
WC()->cart->apply_coupon( '20OFF100' );
wc_print_notice( '20% off $100 or more - Discount Applied!', 'notice' );
}
wc_clear_notices();
}
However, I want to exclude a specific product from this $100 minimum.
The specific product is on sale, and I've checked "Exclude Sale Items" in the coupon admin screen, but the code below is ignoring that.
Why isn't the 'Exclude Sale Items' working, and/or how can I go about this?
The biggest misconception in your code is that even though you checked "Exclude sale items" in the coupon admin screen, that the use of WC()->cart->get_subtotal() does not take this into account.
The solution:
You can use the woocommerce_before_calculate_totals hook
To avoid problems, use a coupon code without capital letters
When going through the cart content, we keep track of the total, except for the excluded products
Based on the current amount threshold and whether or not the coupon code has already been applied, we will display notices
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;
// Coupon code
$coupon_code = 'coupon1';
// Total amount threshold
$amount_threshold = 100;
// Targeted product IDs, several can be entered, separated by a comma
$targeted_product_ids = array( 30, 813 );
// Initializing variables
$total_amount = 0;
$applied_coupons = $cart->get_applied_coupons();
// Loop through cart contents
foreach( $cart->get_cart_contents() as $cart_item ) {
// Excluding targeted product IDs
if ( ! in_array( $cart_item['data']->get_id(), $targeted_product_ids ) ) {
// Get the cart total amount
$total_amount += $cart_item['line_total'] + $cart_item['line_tax'];
}
}
// Applying coupon
if ( ! in_array( $coupon_code, $applied_coupons ) && $total_amount >= $amount_threshold ) {
$cart->apply_coupon( $coupon_code );
wc_clear_notices();
wc_add_notice( __( 'Succeeded', 'woocommerce' ), 'notice' );
}
// Buy more
elseif ( ! in_array( $coupon_code, $applied_coupons ) && $total_amount < $amount_threshold ) {
wc_clear_notices();
wc_add_notice( __( 'Buy more', 'woocommerce' ), 'notice' );
}
// Removing coupon
elseif ( in_array( $coupon_code, $applied_coupons ) && $total_amount < $amount_threshold ) {
$cart->remove_coupon( $coupon_code );
wc_clear_notices();
wc_add_notice( __( 'Removed', 'woocommerce' ), 'notice' );
}
}
add_action( 'woocommerce_before_calculate_totals', 'action_woocommerce_before_calculate_totals', 10, 1 );
After reading https://rudrastyh.com/woocommerce/columns.html I was able to add a column with the order payment method on WooCommerce admin orders list
add_filter('manage_edit-shop_order_columns', 'misha_order_items_column' );
function misha_order_items_column( $order_columns ) {
$order_columns['order_payment_method'] = "Payment method";
return $order_columns;
}
add_action( 'manage_shop_order_posts_custom_column' , 'misha_order_items_column_cnt' );
function misha_order_items_column_cnt( $colname ) {
global $the_order; // the global order object
if( $colname == 'order_payment_method' ) {
// Get payment method
$payment_method = $the_order->get_payment_method();
echo $payment_method;
}
}
This works perfectly, and adds the desired statuses in the custom column
Then, based on a code I found, I made adjustments to add the filter
add_action('restrict_manage_posts', 'add_shop_order_filter_by_state');
function add_shop_order_filter_by_state(){
global $pagenow, $typenow, $the_order;
if( 'shop_order' === $typenow && 'edit.php' === $pagenow ) {
// Get payment method
$payment_method = $the_order->get_payment_method();
// Initializing
$filter_id = 'payment_method';
$current = isset($_GET[$filter_id])? $_GET[$filter_id] : '';
echo '<select name="'.$filter_id.'">
<option value="">'.__( 'Filter by payment method', 'woocommerce' )."</option>";
// Loop through shipping zones locations array
foreach( $payment_method as $method ) {
echo $method;
}
echo '</select>';
}
}
add_filter( 'request', 'process_admin_shop_order_filtering_by_state', 99 );
function process_admin_shop_order_filtering_by_state( $vars ) {
global $pagenow, $typenow;
$filter_id = 'payment_method';
if ( $pagenow == 'edit.php' && 'shop_order' === $typenow
&& isset( $_GET[$filter_id] ) && ! empty($_GET[$filter_id]) ) {
$vars['meta_key'] = 'payment_method';
$vars['meta_value'] = $_GET[$filter_id];
$vars['orderby'] = 'meta_value';
}
return $vars;
}
But because of those changes, in the 2nd part of my code I have an error in my log files:
"Uncaught Error: Call to a member function get_payment_method() on
null".
It seems that the global variable $the_order is not recognized?
Although I don't immediately have an idea how I can apply this differently. Any adivce would be appreciated
You are close in the search for your answer, however your code contains some small errors:
Make use of WC_Payment_Gateways::get_available_payment_gateways()
instead of get_payment_method()
The correct meta key is _payment_method versus payment_method
I optimized the code a bit
So you get:
// Display new column on WooCommerce admin orders list (header)
function filter_manage_edit_shop_order_columns( $columns ) {
// Add new column after order status (4) column
return array_slice( $columns, 0, 4, true )
+ array( 'order_payment_method' => __( 'Payment method', 'woocommerce' ) )
+ array_slice( $columns, 4, NULL, true );
}
add_filter( 'manage_edit-shop_order_columns', 'filter_manage_edit_shop_order_columns', 10, 1 );
// Display details after order status column, on order admin list (populate the column)
function action_manage_shop_order_posts_custom_column( $column, $post_id ) {
// Compare
if ( $column == 'order_payment_method' ) {
// Get order
$order = wc_get_order( $post_id );
// Get the payment method
$payment_method = $order->get_payment_method();
// NOT empty
if ( ! empty ( $payment_method ) ) {
echo ucfirst( $payment_method );
} else {
echo __( 'N/A', 'woocommerce' );
}
}
}
add_action( 'manage_shop_order_posts_custom_column' , 'action_manage_shop_order_posts_custom_column', 10, 2 );
// Add filter dropdown
function action_restrict_manage_posts( $post_type, $which ) {
global $pagenow;
// Compare
if ( $post_type === 'shop_order' && $pagenow === 'edit.php' ) {
// Filter ID
$filter_id = 'filter-by-payment';
$current = isset( $_GET[$filter_id] ) ? $_GET[$filter_id] : '';
// Get available gateways
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
// Create a drop-down list
echo '<select name="' . $filter_id . '">
<option value="">' . __( 'Filter by payment method', 'woocommerce' ) . '</option>';
foreach ( $available_gateways as $key => $available_gateway ) {
printf( '<option %s value="%s">%s</option>', $key === $current ? 'selected="selected"' : '', $key, ucfirst( $key ) );
}
echo '</select>';
}
}
add_action( 'restrict_manage_posts', 'action_restrict_manage_posts', 10, 2 );
// Filter request
function filter_request( $vars ) {
global $pagenow, $typenow;
// Filter ID
$filter_id = 'filter-by-payment';
// Only on WooCommerce admin orders list
if ( $pagenow == 'edit.php' && 'shop_order' === $typenow && isset( $_GET[$filter_id] ) && ! empty( $_GET[$filter_id] ) ) {
$vars['meta_key'] = '_payment_method';
$vars['meta_value'] = $_GET[$filter_id];
$vars['orderby'] = 'meta_value';
}
return $vars;
}
add_filter( 'request', 'filter_request', 10, 1 );
Result:
I've added in a function for a custom field on the product general options:
// Add Custom Field to Product under General
function create_extra_fee_field() {
$args = array(
'id' => 'park_fee',
'label' => __( 'Natl Park Entrance Fee', 'tranq-lsx-child' ),
'class' => 'tranq-custom-field',
'desc_tip' => true,
'description' => __( 'This sets the Fee that will be added to the car.', 'tranq-lsx-child' ),
);
woocommerce_wp_text_input( $args );
}
add_action( 'woocommerce_product_options_general_product_data', 'create_extra_fee_field' );
// Save Custom Field Data
function save_extra_fee_field( $post_id ) {
$product = wc_get_product( $post_id );
$title = isset( $_POST['park_fee'] ) ? $_POST['park_fee'] : '';
$product->update_meta_data( 'park_fee', sanitize_text_field( $title ) );
$product->save();
}
add_action( 'woocommerce_process_product_meta', 'save_extra_fee_field' );
I would like to add this as an extra fee to the Cart Totals which multiplied based on the amount of Person/People that were selected from the Woocommerce Bookings.
Something like this:
// Calculate Extra Fee Based on Amount of People.
add_action('woocommerce_cart_calculate_fees' , 'add_custom_fees');
function add_custom_fees( WC_Cart $cart ){
$park_fee = get_post_meta($item['product_id'] , 'park_fee', true);
foreach( $cart->get_cart() as $item ){
$fees += $item[ 'park_fee' ] * 14706;
}
if( $fees != 0 ){
$cart->add_fee( 'Park Fee', $fees);
}
}
How do I go about in achieving this? Any Links to a walkthrough would be greatly appreciated.
I've added this and it seems to work:
// Add Custom Field to Cart Totals
function woo_add_cart_fee() {
global $woocommerce;
foreach( WC()->cart->get_cart() as $cart_item ){
// Get the WC_Product object (instance)
$product = $cart_item['data'];
$product_id = $product->get_id(); // get the product ID
$custom_field_value = get_post_meta( $product->get_id(), 'park_fee', true );
$person = array_sum( $cart_item['booking']['_persons'] );
}
$additional_fee_name = "Natl Park Entrance Fee";
$extra_fee = $custom_field_value * $person;
$addedFee = false;
// first check to make sure it isn’t already there
foreach ( $woocommerce->cart->get_fees() as $_fee )
{
if ($_fee->id == sanitize_title($additional_fee_name) )
{
$_fee->amount = (float) esc_attr( $extra_fee );
}
}
if (!$addedFee)
{
$woocommerce->cart->add_fee( __($additional_fee_name, "woocommerce"),
$extra_fee, $additional_fee_taxable );
}
}
add_action( "woocommerce_before_calculate_totals", "woo_add_cart_fee" );