I'm adding a custom fees to WC with WC()->cart->add_fee() method.
My problem is that I'd like to add metadata to that fee item too. Preferably same time I'm adding the actual fee.
Apparently the WC_Order_Item_Fee Object is generated in the order creation only, so there seems to be no way to add FeeItem-specific metadata to custom fees.
Of course I could save this meta to session, but because add_fee doesn't return any identifier I have no idea which custom fee is actually which.
Any ideas how to solve this issue?
This is the code I use to add Fees:
add_filter('woocommerce_cart_calculate_fees', function (){
foreach( FeeChecker::getFees() as $fee )
{
$cart->add_fee("Added fee: ". $fee, 10 , true, $tax_class);
}
}
Note: In your code the $cart argument is missing from the hooked function and it's an action hook, but not a filter hook.
The WC_Cart method add_fee() doesn't allow to add custom meta data, so you will need to add it before on add_to_cart event or in WC_Session.
You can add custom meta data to WC_Order_Item_Fee when order is submitted using the following code example (using WC_Session to set and get the custom meta data in here):
// Add a custom cart fee
add_action( 'woocommerce_cart_calculate_fees', 'adding_cart_fees', 10, 1 );
function adding_cart_fees( $cart ){
$cart->add_fee(__("Added fee"), 10, true, '');
}
// Set Fee custom meta data in WC_Session
add_action( 'woocommerce_calculate_totals', 'calculate_totals_for_fees_meta_data', 10, 1 );
function calculate_totals_for_fees_meta_data( $cart ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
$fees_meta = WC()->session->get('fees_meta');
$update = false;
// Loop through applied fees
foreach( $cart->get_fees() as $fee_key => $fee ) {
// Set the fee in the fee custom meta data array
if( ! isset($fees_meta[$fee_key]) ){
$fees_meta[$fee_key] = 'some value';
$update = true;
}
}
// If any fee meta data doesn't exist yet, we update the WC_Session custom meta data array
if ( $update ) {
WC()->session->set('fees_meta', $fees_meta);
}
}
// Save fee custom meta data to WC_Order_Item_Fee.
add_action( 'woocommerce_checkout_create_order_fee_item', 'save_custom_met_data_to_fee_order_items', 10, 4 );
function save_custom_met_data_to_fee_order_items( $item, $fee_key, $fee, $order ) {
// Get fee meta data from WC_Session
$fees_meta = WC()->session->get('fees_meta');
// If fee custom meta data exist, save it to fee order item
if ( isset($fees_meta[$fee_key]) ) {
$item->update_meta_data( 'custom_key', $fees_meta[$fee_key] );
}
}
// Remove Fee meta data from WC_Session.
add_action( 'woocommerce_checkout_create_order', 'remove_fee_custom_met_data_from_wc_session', 10, 2 );
function remove_fee_custom_met_data_from_wc_session( $order, $data ) {
$fees_meta = WC()->session->__unset('fees_meta');
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
The custom meta data is saved to WC_Order_Item_Fee and you can get it using something like:
// Get an instance of the WC_Order Object (if needed)
$order = wc_get_order( $order_id );
// loop through fee order items
foreach ( $order->get_items('fee') as $fee_key => $item ) {
// Get the fee custom meta data
$fee_custom_meta = $item->get_meta('custom_key');
if ( $fee_custom_meta ) {
// Display the custom meta data value
echo '<p>' . $fee_custom_meta . '</p>';
}
}
A somewhat hacky way is to use the ID field of the fee item to link to data stored somewhere else.
$args = array(
'id' => $data_id,
'name' => $name,
'amount' => (float) $amount,
'taxable' => false,
'tax_class' => '',
);
$cart->fees_api()->add_fee( $args );
Later you can use the hook woocommerce_checkout_create_order_fee_item to fetch the data and populate the order item fee. In my case:
add_action( 'woocommerce_checkout_create_order_fee_item', array( $this, 'create_order_fee_item' ), 20, 4 );
public function create_order_fee_item( $item, $fee_key, $fee, $order ) {
$item->add_meta_data( 'product_id', $fee_key, true );
}
It seems I overlooked WC_Cart->fees_api
In there I have method that actually returns the created Fee so I know the exact Fee in woocommerce_checkout_create_order_fee_item action
Edit: This code was originally mostly done by LoicTheAztec. I edited it to fit my particular use case and posted it as solution. Unfortunatelly he deleted his post which could have been beneficial to others.
// Add cod fee
add_action('woocommerce_cart_calculate_fees', function ( $cart ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
//Make sure it's the right payment method
if( WC()->session->chosen_payment_method == "cod"){
$tax_class = '';
$amount = 5;
$session_data = [];
foreach( $cart->get_shipping_packages() as $package)
{
$fee = $cart->fees_api()->add_fee(
array(
'name' => "Additional cost: ".$package['custom_data'],
'amount' => (float) $amount,
'taxable' => true,
'tax_class' => $tax_class,
)
);
$session_data [ $fee->id ] = $package['custom_data'];
}
WC()->session->set('COD_fee_meta', $session_data);
}
}, 10, 2);
// Save fee custom meta data to WC_Order_Item_Fee.
add_action( 'woocommerce_checkout_create_order_fee_item', function ( $item, $fee_key, $fee, $order ) {
// Get fee meta data from WC_Session
$fees_meta = WC()->session->get('COD_fee_meta');
// If fee custom meta data exist, save it to fee order item
if ( isset($fees_meta[$fee_key]) ) {
$item->update_meta_data( '_custom_data', $fees_meta[$fee_key] );
}
}, 10, 4 );
// Remove Fee meta data from WC_Session.
add_action( 'woocommerce_checkout_create_order', function ( $order, $data ) {
WC()->session->__unset('COD_fee_meta');
}, 10, 2 );
Related
The following code add a custom field to admin product settings to manage guest checkout at product level:
// Display Guest Checkout Field
add_action( 'woocommerce_product_options_general_product_data', 'woo_add_custom_general_fields' );
function woo_add_custom_general_fields() {
global $woocommerce, $post;
echo '<div class="options_group">';
// Checkbox
woocommerce_wp_checkbox( array(
'id' => '_allow_guest_checkout',
'wrapper_class' => 'show_if_simple',
'label' => __('Checkout', 'woocommerce' ),
'description' => __('Allow Guest Checkout', 'woocommerce' )
) );
echo '</div>';
}
// Save Guest Checkout Field
add_action( 'woocommerce_process_product_meta', 'woo_add_custom_general_fields_save' );
function woo_add_custom_general_fields_save( $post_id ){
$woocommerce_checkbox = isset( $_POST['_allow_guest_checkout'] ) ? 'yes' : 'no';
update_post_meta( $post_id, '_allow_guest_checkout', $woocommerce_checkbox );
}
// Enable Guest Checkout on Certain products
add_filter( 'pre_option_woocommerce_enable_guest_checkout', 'enable_guest_checkout_based_on_product' );
function enable_guest_checkout_based_on_product( $value ) {
if ( WC()->cart ) {
$cart = WC()->cart->get_cart();
foreach ( $cart as $item ) {
if ( get_post_meta( $item['product_id'], '_allow_guest_checkout', true ) == 'yes' ) {
$value = "yes";
} else {
$value = "no";
break;
}
}
}
return $value;
}
But it doesn't work actually. What I am doing wrong? How can I fix it?
I am trying to allow guest purchases for specific products. The admin custom field display and save custom field value is working (the 2 first functions), But login/register never comes up on checkout page, even if there are products in cart that doesn't allow guest checkout.
The filter hook enable_guest_checkout_based_on_product doesn't exist anymore and has been replaced by another hook a bit different.
So your code is going to be:
add_filter( 'woocommerce_checkout_registration_required', 'change_tax_class_user_role', 900 );
function change_tax_class_user_role( $registration_required ) {
if ( ! WC()->cart->is_empty() ) {
$registration_required = false; // Initializing (allowing guest checkout by default)
// Loop through cart items
foreach ( WC()->cart->get_cart() as $item ) {
// Check if there is any item in cart that has not the option "Guest checkout allowed"
if ( get_post_meta( $item['product_id'], '_allow_guest_checkout', true ) !== 'yes' ) {
return true; // Found: Force checkout user registration and exit
}
}
}
return $registration_required;
}
Code goes in functions.php file of your active child theme (or active theme). It should works.
Related continuation: Redirection for non checkout guest allowed in WooCommerce
I am having some really hard times trying to make it work.
I've had an idea in back of my head to:
be able to assign single user to a coupon code through an extra field in general coupon tab, which is listing unassigned users to coupon codes
I dont want to use third party extensions, custom-fields, etc. I was hoping I'll be able to do it through meta data but I failed. Not sure how to get it done properly.
add two extra columns on orders page and display both coupon code and user assigned to it.
After some time reading docs and xDebugging in phpstorm I have also failed to get it done.
function order_sellers_and_coupons_columns_values($column)
{
global $post, $the_order;
if ($column == 'order_coupon_code') {
$coupons = $the_order->get_used_coupons(); // not sure how to get coupon object by the coupon code
echo (count($coupons)) ? $coupons[0] : '';
}
}
// even though I see the order objects have an items and coupon lines property,
// which is an object, i can't get access to it
$the_order->items["coupon_lines"]
I am not asking for ready-to-go solution, but to show me the way how to get it done.
Thanks in advance for any kind of help.
In WooCommerce admin single coupon pages, we add an extra field for the seller (dealer):
// Add a custom field to Admin coupon settings pages
add_action( 'woocommerce_coupon_options', 'add_coupon_text_field', 10 );
function add_coupon_text_field() {
woocommerce_wp_text_input( array(
'id' => 'seller_id',
'label' => __( 'Assing a seller (dealer)', 'woocommerce' ),
'placeholder' => '',
'description' => __( 'Assign a seller / dealer to a coupon', 'woocommerce' ),
'desc_tip' => true,
) );
}
// Save the custom field value from Admin coupon settings pages
add_action( 'woocommerce_coupon_options_save', 'save_coupon_text_field', 10, 2 );
function save_coupon_text_field( $post_id, $coupon ) {
if( isset( $_POST['seller_id'] ) ) {
$coupon->update_meta_data( 'seller_id', sanitize_text_field( $_POST['seller_id'] ) );
$coupon->save();
}
}
Then using the following you will add to admin orders list the coupon code (when it's used in the order) with the seller / dealer name:
// Adding a new column to admin orders list
add_filter( 'manage_edit-shop_order_columns', 'custom_shop_order_column' );
function custom_shop_order_column($columns)
{
$reordered_columns = array();
// Inserting columns to a specific location
foreach( $columns as $key => $column){
$reordered_columns[$key] = $column;
if( $key == 'order_status' ){
// Inserting after "Status" column
$reordered_columns['coupons'] = __( 'Coupon','theme_domain');
}
}
return $reordered_columns;
}
// Adding used coupon codes
add_action( 'manage_shop_order_posts_custom_column' , 'custom_orders_list_column_content', 10, 2 );
function custom_orders_list_column_content( $column, $post_id )
{
global $the_order;
if ( $column == 'coupons' ) {
$coupons = (array) $the_order->get_used_coupons();
$dealers = [];
foreach( $coupons as $coupon_code ) {
$coupon = new WC_Coupon( $coupon_code );
$dealers[] = $coupon->get_meta('seller_id');
}
if( count($coupons) > 0 )
echo implode( ', ', $coupons );
if( count($dealers) > 0 )
echo '<br><small>(' . implode( ', ', $dealers ) . ')</small>';
}
}
All code goes in functions.php file of your active child theme (or active theme). Tested and works.
On Admin coupon single pages:
On Admin edit orders list:
I'm trying to display custom product field checkout_name on the checkout page but I can't seem to figure out how. I'm following checkout hooks visual guide from here.
add_action( 'woocommerce_checkout_before_customer_details', 'custom_before_checkout_form', 10 );
function custom_before_checkout_form( $cart_data ){
$meta_key = 'checkout_name';
$product_id = $cart_item['product_id'];
$meta_value = get_post_meta( $product_id, $meta_key, true );
if( !empty( $cart_data ) )
$custom_items = $cart_data;
if( !empty($meta_value) ) {
$custom_items[] = array(
'key' => __('Store Name', 'woocommerce'),
'value' => $meta_value,
'display' => $meta_value,
);
}
return $custom_items;
}
Custom checkout fields need to be inside the checkout form. If not the field values are not posted on submission.
There is also some errors in your code. Try the following instead using a hook located inside the checkout form, just before billing fields (assuming that the custom product field checkout_name exist).
add_action( 'woocommerce_checkout_before_customer_details', 'custom_before_checkout_form' );
function custom_before_checkout_form(){
// Loop though cart items
foreach ( WC()->cart->get_cart() as $item ) {
// Get the WC_Product Object
$product = $item['data'];
echo '<div align="center">' . $product->get_meta( 'checkout_name' ) . '</div><br>';
}
}
Code goes in functions.php file of your active child theme (or active theme). It should better work.
In WooCommerce and I have added a custom field "description" for each product.
I was able to find a way to show both, the label name and the value:
add_filter( 'woocommerce_add_cart_item_data', 'save_days_field', 10, 2 );
function save_days_field( $cart_item_data, $product_id ) {
$special_item = get_post_meta( $product_id , 'description',true );
if(!empty($special_item)) {
$cart_item_data[ 'description' ] = $special_item;
// below statement make sure every add to cart action as unique line item
$cart_item_data['unique_key'] = md5( microtime().rand() );
WC()->session->set( 'description', $special_item );
}
return $cart_item_data;
}
// Render meta on cart and checkout
add_filter( 'woocommerce_get_item_data','rendering_meta_field_on_cart_and_checkout', 10, 2 );
function rendering_meta_field_on_cart_and_checkout( $cart_item_data, $cart_item ) {
if( isset( $cart_item['description'] ) ) {
$cart_item_data[] = array( "name" => __( "Description", "woocommerce" ), "value" => $cart_item['description'] );
}
return $cart_item_data;
}
Now I need to display ONLY the value (not the label name "Description") of this custom field in the cart and checkout table. I need to display with <small>, like the attribute I am displaying with this code:
add_filter('woocommerce_cart_item_name', 'wp_woo_cart_attributes', 10, 2);
function wp_woo_cart_attributes($cart_item, $cart_item_key){
$productId = $cart_item_key['product_id'];
$product = wc_get_product($productId);
$taxonomy = 'pa_color';
$value = $product->get_attribute($taxonomy);
if ($value) {
$label = get_taxonomy($taxonomy)->labels->singular_name;
$cart_item .= "<small>$value</small>";
}
return $cart_item;
}
How can I make it for this custom field, displaying the value only?
You don't need to include a product custom field as custom cart item data, as it's directly accessible from the product object (or the product ID).
Note: On a cart item variable $cart_item, the WC_Product Object is included and available using $cart_item['data'].
Try the following to add a custom field after the item name in cart and checkout pages:
// Display in cart and checkout pages
add_filter( 'woocommerce_cart_item_name', 'customizing_cart_item_name', 10, 3 );
function customizing_cart_item_name( $product_name, $cart_item, $cart_item_key ) {
$product = $cart_item['data']; // Get the WC_Product Object
if ( $value = $product->get_meta('description') ) {
$product_name .= '<small>'.$value.'</small>';
}
return $product_name;
}
To display it on orders and email notifications use:
// Display in orders and email notifications
add_filter( 'woocommerce_order_item_name', 'customizing_order_item_name', 10, 2 );
function customizing_order_item_name( $product_name, $item ) {
$product = $item->get_product(); // Get the WC_Product Object
if ( $value = $product->get_meta('description') ) {
$product_name .= '<small>'.$value.'</small>';
}
return $product_name;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
Currently i have some custom calculation of product price based on different situation. When customer added a product in to cart then the custom price is set in session data , cart_item_data['my-price'] and i implemented using add_filter( 'woocommerce_add_cart_item') function and everything seems to working now .
Now the price in view cart page, checkout page is correct with my cart_item_data['my-price'].
But the only problem i am facing is the price is not updated in woocommerce mini cart that is appeared in the menu ,How can i change this ?
When i google i see a filter
add_filter('woocommerce_cart_item_price');
but i can't understand how to use this i do the following
add_filter('woocommerce_cart_item_price','modify_cart_product_price',10,3);
function modify_cart_product_price( $price, $cart_item, $cart_item_key){
if($cart_item['my-price']!==0){
$price =$cart_item['my-price'];
}
return $price;
//exit;
}
Here individual price is getting correct , but total price is wrong
Updated (october 2021)
For testing this successfully (and as I don't know how you make calculations), I have added a custom hidden field in product add to cart form with the following:
// The hidden product custom field
add_action( 'woocommerce_before_add_to_cart_button', 'add_gift_wrap_field' );
function add_gift_wrap_field() {
global $product;
// The fake calculated price
?>
<input type="hidden" id="my-price" name="my-price" value="115">
<?php
}
When product is added to cart, this my-price custom field is also submitted (posted). To set this value in cart object I use the following function:
add_filter( 'woocommerce_add_cart_item', 'custom_cart_item_prices', 20, 2 );
function custom_cart_item_prices( $cart_item_data, $cart_item_key ) {
// Get and set your price calculation
if( isset( $_POST['my-price'] ) ){
$cart_item_data['my-price'] = $_POST['my-price'];
// Every add to cart action is set as a unique line item
$cart_item_data['unique_key'] = md5( microtime().rand() );
}
return $cart_item_data;
}
Now to apply (set) the new calculated price my-price to the cart item, I use this last function:
// For mini cart *(cart item displayed price)*
add_action( 'woocommerce_cart_item_price', 'filter_cart_item_price', 10, 2 );
function filter_cart_item_price( $price, $cart_item ) {
if ( ! is_checkout() && isset($cart_item['my-price']) ) {
$args = array( 'price' => floatval( $cart_item['my-price'] ) );
if ( WC()->cart->display_prices_including_tax() ) {
$product_price = wc_get_price_including_tax( $cart_item['data'], $args );
} else {
$product_price = wc_get_price_excluding_tax( $cart_item['data'], $args );
}
return wc_price( $product_price );
}
return $price;
}
add_action( 'woocommerce_before_calculate_totals', 'set_calculated_cart_item_price', 20, 1 );
function set_calculated_cart_item_price( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item ){
if( isset( $cart_item['my-price'] ) && ! empty( $cart_item['my-price'] ) || $cart_item['my-price'] != 0 ){
// Set the calculated item price (if there is one)
$cart_item['data']->set_price( $cart_item['my-price'] );
}
}
}
All code goes in function.php file of your active child theme (or active theme).
Tested and works