I'm creating a Plugin in WooCommerce and have a small issue with adding custom discounts to the CART / CHECKOUT page.
How can I apply custom discount to the cart without creating coupons?
Say I want to give some discount of 5 dollars on the cart page. How can I do that?
Below is my code from the plugin file where I have used a coupon to apply discount, but I want to add another custom discount without the use of coupon.
Action Hook in the plugin file :
add_action('woocommerce_calculate_totals',array(&$this,'cart_order_total_action'));
and its function in the plugin file is :
public function cart_order_total_action(){
if ( is_user_logged_in() ){
global $woocommerce;
global $current_user;
global $wpdb;
$u_id = $current_user->ID;
$table_name = $wpdb->prefix."woocommerce_customer_reward_ms";
$thetable2 = $wpdb->prefix . "woocommerce_customer_reward_cart_ms";
$table_name3 = $wpdb->prefix."woocommerce_customer_reward_points_log_ms";
$data = $wpdb->get_row("SELECT * from $table_name where id=$u_id");
$data2 = $wpdb->get_row("SELECT * from $thetable2");
/* Order Id goes here */
$orders=array();//order ids
$args = array(
'numberposts' => -1,
'meta_key' => '_customer_user',
'meta_value' => $current_user->ID,
'post_type' => 'shop_order',
'post_status' => 'publish',
'tax_query'=>array(
array(
'taxonomy' =>'shop_order_status',
'field' => 'slug',
'terms' =>'on-hold'
)
)
);
$posts=get_posts($args);
$orders=wp_list_pluck( $posts, 'ID' );
$order = $orders[0];
/* Order Id ends here */
if($data){
$user_points = $data->points;
$points_set = $data2->woo_pts_set;
$coupon_code = 'wooreward_discount';
if($user_points>=$points_set){
// this following Code is optional and can be removed......as there is no need of if statement here
if ( $woocommerce->cart->has_discount( $coupon_code ) ) {
/*$woocommerce->add_error( __('Coupon Code Already Applied.!!','woocommerce'));*/
return false;
}else{
$woocommerce->cart->add_discount(sanitize_text_field($coupon_code));
$woocommerce->add_message( __('Taxco925 Reward Discount Applied.!!','woocommerce'));
}
}else{
$woocommerce->add_error( __('Not Enough Taxco925 Points.!!','woocommerce'));
}
}else{
$woocommerce->add_error( __('You have have not earned any Taxco925 Points yet.!!','woocommerce'));
}
}
}
As you can see this line $woocommerce->cart->add_discount(sanitize_text_field($coupon_code));
adds my discount to the cart. But it uses coupon in the background to do so . Is there any way I can add a custom discount without the use of coupon.
add_action('woocommerce_checkout_order_processed','custom_disount',10,1);
function custom_disount($order_id){
$order = wc_get_order($order_id);
$order_items = $order->get_items();
foreach ($order_items as $order_item_key => $order_item) {
$product = new WC_Product((int) $order_item['product_id']);
$quantity = (int) $order_item['qty'];
$discount=($product->regular_price*30)/100; //30% disount.
wc_update_order_item_meta($order_item_key,'_line_total',($product->regular_price*$quantity)-($discount*$quantity));
}
}
You can add discount to each and every product in the cart using "woocommerce_get_discounted_price" hook.
For Eg.:
function filter_woocommerce_get_discounted_price( $price, $values, $instance ) {
//$price represents the current product price without discount
//$values represents the product object
//$instance represent the cart object
$discount = 300; // add custom discount rule , This is just an example
return ($price - $discount);
};
add_filter('woocommerce_get_discounted_price','filter_woocommerce_get_discounted_price', 10, 3 );
Maybe too late, but If someone have another solution tell me.
I use something like:
$discount = floatval(10);
if(!empty($discount) || $discount != 0){
$discount *= -1; // convert positive to negative fees
$woocommerce->cart->add_fee('discount', $discount, true, '' ); // add negative fees
}
If you use paypal standard payment, you got an error because you can't submit a product with negative pricing.
You just need to edit the paypal woocommerce plugin to pass this value.
But other Payment method is ok!
Best Regards,
Add fee with negative value will not produce the right total fee.
Tax is added on the fee amount resulting in higher total fee than expected.
You need to create a "coupon" and apply it to the cart before you create the order from the cart (it will not calculate right if you apply it on $order directly). Then recalculate the cart->total and finally create an order from the cart, after you have saved the order you can remove the "dynamic" created "coupon" if you want. You can create dynamic coupons with any dynamic $value and of any type (fixed, percent etc etc).
This is the only way to add discounts in woo3+.
Fee is doing it wrong when it comes to discounts. Also woo say about fee "Not use negative values here!".
I guessed you wanted some example?
here....
<?php
// this code inside wordpress and with woo3+ of course......
// you have to figure out the rest yourself, how to implement it. but here it is...
$order_data = array (
'status' => 'on-hold' // or whatever order staus
// can have more data if need here...
);
// below creates a coupon with discount_type = fixed_cart, default.
$coupon = array (
'post_title' => 'coupon_discount',
'post_status' => 'publish',
'post_type' => 'shop_coupon'
);
// can be modified with update_post_meta discount_type = percent and so on....
$dynamic_discount = 20; // yes, just a number can be from another dynamic input source....
$new_coupon_id = wp_insert_post( $coupon ); // add the coupon to the cart
add_post_meta( $new_coupon_id , 'coupon_amount' , $dynamic_discount , true ); // add the "discount" value ($dynamic_discount)..... depends on discount_type... in this case fixed_cart
WC()->cart->add_to_cart( 2122 , 2 ); // add products, product_id , quantity ..... can be in a loop.
WC()->cart->add_discount( 'coupon_discount' ); // APPLY THE COUPON WITH DISCOUNT -> This is the trick....
WC()->cart->calculate_totals(); // do some math on the "cart"
WC()->checkout(); // yes, checkout the "cart", now with the discount....
$order_id = WC()->checkout()->create_order( $order_data ); // basic order data, see the top in this script.. get new created order_id.
$order = wc_get_order( $order_id ); // get the order...
// can do more with $order here if want, but NOT any coupons... it just not work in $order as is...
$order->calculate_totals(); // math
WC()->cart->empty_cart(); // empty cart....
$order->save(); // save the order...
wp_delete_post( $new_coupon_id , true ); // IF you want to delete the "dynamic" coupon created above... up 2 u, if not you will end up with a lot of coupons
// sorry, a bad example, uggly code, but at least it work.... :)
// btw, i like Pattaya, send bitcoins :)
// Again, sorry for uggly code...
?>
Related
I am creating a 'Build Your Own' page. After selecting 4 products from the options, 1 main product gets added to the basket with 4 of the selected products being added as meta_data under that main product.
You can see below the main product with 4 selected products (IDs).
After paying for this item, I need to add each selected product to the order, so that they are within the order on the backend. I'm having to do it like this, because I need the stock of the selected product to go down, eventually pulling into a stock management system we use (veeqo)
Any help is appreciated. The code below allows me get the some meta_data for woocommerce_thankyou but I am not sure if it will work then...
add_action('woocommerce_thankyou', 'BuildYourOwn', 10, 1);
function BuildYourOwn( $order_id ) {
if ( !$order_id ){
return;
}
$firstTime = get_post_meta( $order_id, '_thankyou_action_done', true );
// Allow code execution only once
if( !$firstTime ) {
// Get an instance of the WC_Order object
$order = wc_get_order( $order_id );
$exItems = '';
// Loop through order items
foreach ( $order->get_items() as $item_id => $item ) {
//print_r($item);
// Get the product object
$product = $item->get_product();
// Get the product sku
$product_sku = $product->get_sku();
// Get the product name
$product_id = $product->get_name();
$extras = $item->get_formatted_meta_data('_', true);
$exItems.=$product_sku;
if(!empty($extras)){
$exItems.=$product_sku.' -';
foreach($extras as $extra){
$exItems.= ' ['.$extra->key.' : '. preg_replace("/[^A-Za-z0-9?#,.&%!\s]/","",$extra->value).'] ';
}
}
$exItems.="\n";
}
var_dump($exItems);
Maybe I worded the question wrong - But I figured it out:
I used the code below to get the meta_data which I then looped through and got individual item id and added it to the basket this way.
// Get the product meta data
$extras = $item->get_formatted_meta_data('_', true);
if ($product_id == 60023){
if(!empty($extras)){
$args = array(
'subtotal' => 0,
'total' => 0,
);
foreach($extras as $extra){
$mini_name = $extra->value;
$mini = get_page_by_title( $mini_name, OBJECT, 'product' );
$mini_id = $mini->ID;
$order->add_product( wc_get_product($mini_id), 1, $args); // Add Minis
}
}
}
The only slight issue is woocommerce_thankyou hook not firing if paypal users don't come back to the site after paying.
I have created a product on WooCommerce, and added two options on product detail page using the hook woocommerce_before_add_to_cart_button. Now when customers add product to cart from product detail page they have two options their. They can choose one option from these two options.
Then I have stored the user selected value in cart meta using the woocommerce hook woocommerce_add_cart_item_data.
I am using the code from this answer: Save product custom field radio button value in cart and display it on Cart page
This is my code:
// single Product Page options
add_action("woocommerce_before_add_to_cart_button", "options_on_single_product");
function options_on_single_product(){
$dp_product_id = get_the_ID();
$product_url = get_permalink($dp_product_id);
?>
<input type="radio" name="custom_options" checked="checked" value="option1"> option1<br />
<input type="radio" name="custom_options" value="option2"> option2
<?php
}
//Store the custom field
add_filter( 'woocommerce_add_cart_item_data', 'save_custom_data_with_add_to_cart', 10, 2 );
function save_custom_data_with_add_to_cart( $cart_item_meta, $product_id ) {
global $woocommerce;
$cart_item_meta['custom_options'] = $_POST['custom_options'];
return $cart_item_meta;
}
And this is what I have tried:
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price', 10, 1);
function add_custom_price( $cart_obj ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
foreach ( $cart_obj->get_cart() as $key => $value ) {
$product_id = $value['product_id'];
$custom_options = $value['custom_options'];
$coupon_code = $value['coupon_code'];
if($custom_options == 'option2')
{
if($coupon_code !='')
{
global $woocommerce;
if ( WC()->cart->has_discount( $coupon_code ) ) return;
(WC()->cart->add_discount( $coupon_code ))
//code for second discount
}
else{
$percentage = get_post_meta( $product_id , 'percentage', true );
//print_r($value);
$old_price = $value['data']->regular_price;
$new_price = ($percentage / 100) * $old_price;
$value['data']->set_price( $new_price );
}
}
}
}
Now what I am trying to get with that last snippet is:
If Option1 is selected by the customer then woocommerce regular process is run.
If Option2 is selected then firstly coupon code applied to cart (if code entered by the customer) and then the price is divide by some percentage (stored in product meta) is applied afterward.
But it’s not working as expected because the changed product price is maid before and coupon discount is applied after on this changed price.
What I would like is that the coupon discount will be applied first on the product regular price and then after change this price with my custom product discount.
Is this possible? How can I achieve that?
Thanks.
This is not really possible … Why? … Because (the logic):
You have the product price
Then the coupon discount is applied to that price (afterwards)
==> if you change the product price, the coupon is will be applied to that changed price
What you can do instead:
You don't change product price
if entered the coupon is applied and …
If "option2" product is added to cart:
Apply a custom discount (a negative fee) based on the product price added after using WC_cart add_fee() method…
For this last case you will have to fine tune your additional discount.
If the coupon has not been applied or it's removed there is no additional discount.
Your custom function will be hooked in woocommerce_cart_calculate_fees action hook instead:
add_action( 'woocommerce_cart_calculate_fees', 'option2_additional_discount', 10, 1 );
function option2_additional_discount( $cart_obj ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
$discount = 0;
$applied_coupons = $cart_obj->get_applied_coupons();
foreach ( $cart_obj->get_cart() as $item_values ) {
if( 'option2' == $item_values['custom_options'] && !empty($applied_coupons) ){
$product_id = $item_values['product_id'];
$percentage = get_post_meta( $product_id , 'percentage', true );
$quantity = $item_values['quantity'];
$product_reg_price = $item_values['data']->regular_price;
$line_total = $item_values['line_total'];
$line_subtotal = $item_values['line_subtotal'];
$percentage = 90;
## ----- CALCULATIONS (To Fine tune) ----- ##
$item_discounted_price = ($percentage / 100) * $product_reg_price * $item_values['quantity'];
// Or Besed on line item subtotal
$discounted_price = ($percentage / 100) * $line_subtotal;
$discount += $product_reg_price - $item_discounted_price;
}
}
if($discount != 0)
$cart_obj->add_fee( __( 'Option2 discount', 'woocommerce' ) , - $discount );
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
This code is tested and works.
Adding a negative fee using the WC_Cart->add_fee() method wasn't working for me. When i check the WC Cart class, it even states you are not allowed to use a negative ammount.
See the docs.
I did the following:
create a placeholder coupon with a 'secure' code, e.g. custom_discount_fjgndfl28. Set a discount ammount of 0, so when somebody (somehow) uses this coupon outside your program the discount is still 0.
Use filter woocommerce_get_shop_coupon_data, and set all the coupon data you want for that coupon/session.
Hook into woocommerce_before_calculate_totals and set your custom coupon to the cart.
At this point the Cart should calculate everything correctly. And when it becomes an order, it also has the correct discount ammount.
Note: the coupon code is also used as a label in some templates. Use filter woocommerce_cart_totals_coupon_label to change it.
Example functions:
/**
* NOTE: All the hooks and filters below have to be called from your own
* does_it_need_custom_discount() function. I used the 'wp' hook for mine.
* Do not copy/paste this to your functions.php.
**/
add_filter('woocommerce_get_shop_coupon_data', 'addVirtualCoupon', 10, 2);
function addVirtualCoupon($unknown_param, $curr_coupon_code) {
if($curr_coupon_code == 'custom_discount_fjgndfl28') {
// possible types are: 'fixed_cart', 'percent', 'fixed_product' or 'percent_product.
$discount_type = 'fixed_cart';
// how you calculate the ammount and where you get the data from is totally up to you.
$amount = $get_or_calculate_the_coupon_ammount;
if(!$discount_type || !$amount) return false;
$coupon = array(
'id' => 9999999999 . rand(2,9),
'amount' => $amount,
'individual_use' => false,
'product_ids' => array(),
'exclude_product_ids' => array(),
'usage_limit' => '',
'usage_limit_per_user' => '',
'limit_usage_to_x_items' => '',
'usage_count' => '',
'expiry_date' => '',
'apply_before_tax' => 'yes',
'free_shipping' => false,
'product_categories' => array(),
'exclude_product_categories' => array(),
'exclude_sale_items' => false,
'minimum_amount' => '',
'maximum_amount' => '',
'customer_email' => '',
'discount_type' => $discount_type,
);
return $coupon;
}
}
add_action('woocommerce_before_calculate_totals', 'applyFakeCoupons');
function applyFakeCoupons() {
global $woocommerce;
// $woocommerce->cart->remove_coupons(); remove existing coupons if needed.
$woocommerce->cart->applied_coupons[] = $this->coupon_code;
}
add_filter( 'woocommerce_cart_totals_coupon_label', 'cart_totals_coupon_label', 100, 2 );
function cart_totals_coupon_label($label, $coupon) {
if($coupon) {
$code = $coupon->get_code();
if($code == 'custom_discount_fjgndfl28') {
return 'Your custom coupon label';
}
}
return $label;
}
Please Note: i copied these functions out of a class that handles much more, it's only to help you get going.
In WooCommerce I basically need to set a stock quantity of 10 for 4 separate products so when someone purchases 1 of these products the overall stock level goes down to 9.
The stock quantity for these products has to be linked so when 1 is purchased the quantity for all of them goes down.
I can't simply set 1 product up as a 'variable product' to achive this as each product has to be a 'simple product'.
Does anyone know of a plugin or way of setting up WooCommerce to achieve this?
You could try this custom function that is using the woocommerce_thankyou hook, to get first, the smallest stock value of the bought products in the customer order and then update all products with this stock value. This is untested, so you will have to give me some feed back on it.
Here is this code:
add_action( 'woocommerce_thankyou', 'updating_product_unified_stock', 10, 1 );
function unifying_product_stock($order_id){
$stock_updated = get_post_meta($order_id, 'stock_updated', true);
if(empty($stock_updated)):
// Inserting in the order meta data a custom field value to avoid repetition of this code,
// if the customer reload the "Order received" page…
update_post_meta($order_id, 'stock_updated', 'yes');
$products_stock_arr = array();
$products_ids = array();
// Getting the Order Object post data
$order = wc_get_order( $order_id );
foreach ( $order->get_items() as $item_id => $item ) {
$product_stock = get_post_meta($item_id, '_stock', true);
$products_stock_arr[] = $product_stock; // adding the product stock in the array
}
// Get the smallest stock value in the array of stock values
$new_stock_number = min($products_stock_arr);
// get all published simple products
$all_products = get_posts( array(
'post_type' => 'product',
'posts_per_page' => -1,
'post_status' => 'publish'
));
// Iterating through each published product
// UPDATING PRODUCTS WITH THE NEW STOCK VALUE:
foreach( $all_products as $product)
update_post_meta( $product_id, '_stock', $new_stock_number );
endif;
}
This code goes in function.php file of your active child theme (or theme) or also in any plugin php files.
I am trying to update wp_postmeta table for all products sale price.
I'm struggling with how to work this, due to both fields being meta_key / meta_value pairs in this wp_postmeta table.
How can I write a query that will update all '_sale_price' = 0 WHERE '_sale_price' > '_price'?
A different approach with a custom function that should do the same job. You will have to use this function just one time, and then remove it after the job is done (at first site load, the process will depend on number of products you have).
Here is the code:
function update_products_sale_price(){
$args = array(
'posts_per_page' => -1,
'post_type' => 'product',
'post_status' => 'publish'
);
// getting all products
$products = get_posts( $args );
// Going through all products
foreach ( $products as $key => $value ) {
// the product ID
$product_id = $value->ID;
// Getting the product sale price
$sale_price = get_post_meta($product_id, '_sale_price', true);
// if product sale price is not defined we give to the variable a 0 value
if (empty($sale_price))
$sale_price = 0;
// Getting the product sale price
$price = get_post_meta($product_id, '_price', true);
// udate sale_price to 0 if sale price is bigger than price
if ($sale_price > $price)
update_post_meta($product_id, '_sale_price', '0');
}
}
// Here the function we will do the job.
update_products_sale_price();
You can also embed the code function in a hook…
This code goes on function.php file of your active child theme or theme
This code is tested and fully functional
References:
WordPress Code Reference - Function get_post_meta()
WordPress Code Reference - Function update_post_meta()
I tried using the 'Hide if sold out' option in Woocommerce. But I am using a different plugin to display my products (GridFX Masonry Gallery) and it is still showing the sold out products. Is there a way to change the product to draft when the last item has been purchased and is sold out? Is there a snippet to do this?
The previous answer by Maha Dev cannot work.
In a theme functions file the get_stock_quantity() method doesn't exist unless you call the product data using wc_get_product.
Using set_post_type will change the actual type of the post, not it's status, you need to use wp_update_post and set the post status.
This is the code I ended up using:
/*
* Unpublish products after purchase
*/
function lbb_unpublish_prod($order_id) {
$order = new WC_Order( $order_id );
$all_products = $order->get_items();
foreach ($all_products as $product){
$product_object = wc_get_product($product['product_id']);
// This will only work if stock management has been enabled
if( ! $product_object->get_stock_quantity() ) {
wp_update_post(array(
'ID' => $product['product_id'],
'post_status' => 'draft'
));
}
}
}
add_action( 'woocommerce_thankyou', 'lbb_unpublish_prod', 10, 1 );
Better way is to set product stock (1 in your case). Select 'Hide out of stock products'. It will hide out of stock products. But if you actually want to hide ordered products then see my code below:
//functions.php
add_action( 'woocommerce_thankyou', 'your_func', 10, 1 );
function your_func($order_id) {
$order = new WC_Order( $order_id );
$all_products = $order->get_items();
foreach ($all_products as $product){
// This will only work if stock management has been enabled
if( ! $product->get_stock_quantity() )
set_post_type ($product['product_id'], 'draft');
}
}
This will hide all the products added in the cart for order. You can customize this function as you want.