Woocommerce - Add Coupon is not deducting totals - php

I am creating my order like so:
$order = wc_create_order();
$product = wc_get_product( $_POST["product"] );
$order->add_product( $product, 1 );
$kupon = new WC_Coupon( $_POST["coupon"] );
$amount = $kupon->get_discount_amount( $product->price );
$order->add_coupon( $_POST["coupon"], $amount, $amount );
$order->calculate_shipping();
$order->calculate_totals();
If you take a closer look, I am adding a coupon code dynamicaly with add_coupon function from WC_Order class. Everythings works perfectly, the order is added to database with correct product, quantites, and ALSO the coupon is added - but the problem is that coupon is not "applied" to the total. It is not deducting the totals price. Here is the image:

While adding a product to an Order, we should pass an argument containing subtotal and total like this:
$args = array(
"totals" => array('subtotal' => $item["price"],
'total' => $item["price"] - $coupon_discount)
);
$order->add_product( $product, 1, $args);
Where $product is Woocommerce product. Hope this helps somebody.

Here is the solution that worked in my case for modifying order line items then applying a discount afterward - $order is a WC_Order object:
$order_total = $order->get_total()
$coupon_code = $this->get_coupon_code( $order );
$coupon = new WC_Coupon( $coupon_code );
$coupon_type = $coupon->discount_type;
$coupon_amount = $coupon->coupon_amount;
$final_discount = 0;
// You must calculate the discount yourself! I have not found a convenient method outside the WC_Cart context to do this for you.
$final_discount = $coupon_amount * ( $order_total / 100 );
$order->add_coupon( $coupon_code, $final_discount, 0 );
$order->set_total( $final_discount );
Here is the method to retrieve the coupon code for a WC_Order object. In my case I know there will never be more then 1 coupon per order so you may want to adjust it to accommodate more:
public function get_coupon_code( $subscription ) {
$coupon_used = '';
if( $subscription->get_used_coupons() ) {
$coupons_count = count( $subscription->get_used_coupons() );
foreach( $subscription->get_used_coupons() as $coupon) {
$coupon_used = $coupon;
break;
}
}
if ( $coupon_used !== '' ) {
return $coupon_used;
} else {
return false;
}
}

Related

Get used coupon codes and related discount amounts from WooCommerce orders

I need to insert in a custom plugin the code to get the name of the discount codes I enter in the settings, the discount obtained with the code and the total.
Based on Get coupon data from WooCommerce orders answer code, I have inserted the following code:
$order = wc_get_order( $order_id );
// GET THE ORDER COUPON ITEMS
$order_items = $order->get_items('coupon');
// print_r($order_items); // For testing
// LOOP THROUGH ORDER COUPON ITEMS
foreach( $order_items as $item_id => $item ){
// Retrieving the coupon ID reference
$coupon_post_obj = get_page_by_title( $item->get_name(), OBJECT, 'shop_coupon' );
$coupon_id = $coupon_post_obj->ID;
// Get an instance of WC_Coupon object (necessary to use WC_Coupon methods)
$coupon = new WC_Coupon($coupon_id);
## Filtering with your coupon custom types
if( $coupon->is_type( 'fixed' ) || $coupon->is_type( 'percent' ) || $coupon->is_type( 'fixed_product' ) ){
// Get the Coupon discount amounts in the order
$order_discount_amount = wc_get_order_item_meta( $item_id, 'discount_amount', true );
$order_discount_tax_amount = wc_get_order_item_meta( $item_id, 'discount_amount_tax', true );
## Or get the coupon amount object
$coupons_amount = $coupons->get_amount();
}
}
$confirmation = str_ireplace("{order_items}", $order_items, $confirmation);
But the only information it brings back to me, when I do an echo is the word "array".
What am I doing wrong? Any help?
Try the following instead, that will add a coma separated string of applied coupon codes with their respective discount amount:
$order = wc_get_order( $order_id ); // If needed
$output = array(); // Initializing
// loop through order items "coupon"
foreach( $order->get_items('coupon') as $item_id => $item ){
// Get the coupon array data in an unprotected array
$data = $item->get_data();
// Format desired coupon data for output
$output[] = $data['code'] . ': ' . strip_tags( wc_price( $data['discount'] + $data['discount_tax'] ) );
}
$confirmation = str_ireplace("{order_items}", implode(', ', $output), $confirmation);

Programmatically remove coupon from subscription/order

How can I programmatically remove a coupon from a subscription/order?
This is what I've tried but when running it, it doesn't seem to remove the coupon, where am I going wrong?
$subscription = new WC_Subscription( $_REQUEST['sub_id'] );
$coupon_code = $_REQUEST['voucher_code'];
$coupon = new WC_Coupon( $coupon_code );
$subscription->remove_coupon( $coupon );
The flip side of that was adding a coupon which worked fine:
$subscription->add_coupon( $coupon_code,$coupon_amount,'0' );
$subscription->set_total( $subscription->get_total() - $coupon_amount );
$subscription->save();
The remove_coupon method parameter is the coupon code, not the object.
Try this:
$subscription->remove_coupon( 'coupon_code' );
Where coupon_code is the name of the coupon that the customer applied to the cart or checkout.
Here you will find the documentation: https://woocommerce.github.io/code-reference/files/woocommerce-includes-abstracts-abstract-wc-order.html#source-view.1187
I found a way around this, I just needed to loop over each coupon:
$coupons = $subscription->get_items( 'coupon' );
// Remove the coupon line.
$strcode = strtolower($coupon_code);
foreach ( $coupons as $coupon ) {
if($coupon->get_code() == $strcode){
$subscription->remove_coupon( $coupon->get_code() );
}
}

Add update or remove WooCommerce shipping order items

I have added shipping cost for the orders that are synced from Amazon. For some reason I had to set custom shipping flat price in woo-orders created for Amazon-order. It is done as follow:
$OrderOBJ = wc_get_order(2343);
$item = new WC_Order_Item_Shipping();
$new_ship_price = 10;
$shippingItem = $OrderOBJ->get_items('shipping');
$item->set_method_title( "Amazon shipping rate" );
$item->set_method_id( "amazon_flat_rate:17" );
$item->set_total( $new_ship_price );
$OrderOBJ->update_item( $item );
$OrderOBJ->calculate_totals();
$OrderOBJ->save()
The problem is, I have to update orders in each time the status is changed in Amazon, there is no problem doing that, problem is I have to update the shipping cost also if it is updated. But I have not found anyway to do so. Can anyone tell me how to update the shipping items of orders set in this way? Or is it the fact that, once shipping item is set then we cannot update or delete it?
To add or update shipping items use the following:
$order_id = 2343;
$order = wc_get_order($order_id);
$cost = 10;
$items = (array) $order->get_items('shipping');
$country = $order->get_shipping_country();
// Set the array for tax calculations
$calculate_tax_for = array(
'country' => $country_code,
'state' => '', // Can be set (optional)
'postcode' => '', // Can be set (optional)
'city' => '', // Can be set (optional)
);
if ( sizeof( $items ) == 0 ) {
$item = new WC_Order_Item_Shipping();
$items = array($item);
$new_item = true;
}
// Loop through shipping items
foreach ( $items as $item ) {
$item->set_method_title( __("Amazon shipping rate") );
$item->set_method_id( "amazon_flat_rate:17" ); // set an existing Shipping method rate ID
$item->set_total( $cost ); // (optional)
$item->calculate_taxes( $calculate_tax_for ); // Calculate taxes
if( isset($new_item) && $new_item ) {
$order->add_item( $item );
} else {
$item->save()
}
}
$order->calculate_totals();
It should better work…
To remove shipping items use te following:
$order_id = 2343;
$order = wc_get_order($order_id);
$items = (array) $order->get_items('shipping');
if ( sizeof( $items ) > 0 ) {
// Loop through shipping items
foreach ( $items as $item_id => $item ) {
$order->remove_item( $item_id );
}
$order->calculate_totals();
}
Related: Add a shipping to an order programmatically in Woocommerce 3
The WC_Order_Item_Shipping object can be added to an order using either of 2 methods.
WC_ORDER->add_shipping( WC_Order_Item_Shipping ) This is deprecated in WooCommerce V3.
WC_ORDER->add_item( WC_Order_Item_Shipping )
If you need to persist this change on the database then use WC_ORDER->save();
References: woocommerce.github.io.../#add_shipping woocommerce.github.io.../#add_item
Just get order and delete item by id
$ordr_id = 4414;
$item_id = 986;
$order = wc_get_order($ordr_id);
$order->remove_item($item_id);
$order->calculate_totals();

Getting the correct variation price from order items in Woocommerce 3

Im trying to get the correct price for each item variation however it only seems to be getting the first price of that product variation. Not sure how to solve this.
Code:
$query = new WC_Order_Query( array(
'status' => 'on-hold',
'orderby' => 'date',
'order' => 'DESC',
'return' => 'ids',
) );
$order_ids = $query->get_orders();
foreach( $order_ids as $order_id ) {
$order = new WC_Order($order_id);
foreach ($order->get_items() as $item_id => $item_obj) {
$_product = wc_get_product($item_obj['product_id']);
$product = new WC_Product_Variable($item_obj['product_id']);
$product_variations = $product->get_available_variations();
$variation_product_id = $product_variations [0]['variation_id'];
$variation_product = new WC_Product_Variation( $variation_product_id );
$t_dy = $variation_product->get_price();
$item_qty = $item_obj['qty'];
$it_total = $item_qty * $t_dy;
$td = wc_update_order_item_meta($item_id, '_line_total', $it_total);
$order->calculate_totals();
$order->save();
}
}
Updated 3
To get the correct current variation price when the order item is a product variation is much more simple than you are doing. Then you will use Woocommerce 3 CRUD setter and getter methods to set the order item totals, save it and update the order.
The code:
// Loop through order items
foreach ($order->get_items() as $item_id => $item ) {
// Targeting only product variation items
if( $item->get_variation_id() > 0 ){
// Get an instance of the WC_Product_Variation object
$product = $item->get_product();
$price = (float) $product->get_price(); // <=== HERE the variation price
$qty = (int) $item->get_quantity(); // <=== HERE the quantity
// set line totals
$item->set_total( $price * $qty );
$item->set_subtotal( $price * $qty );
$item->save(); // save order item data
}
}
// The following need to be outside the order item loop
$order->calculate_totals(); // Save is included into the method
It should better work this way.
Related:
Get Order items and WC_Order_Item_Product in Woocommerce 3
How to get WooCommerce order details
Found the issue! - it was giving out wrong id
replace:
$variation_product_id = $product_variations [0]['variation_id'];
with this:
$product_variation_id = $item_obj->get_variation_id();
$variation_product_id = $product_variation_id;
$variation_product = new WC_Product_Variation( $variation_product_id );

Woocommerce change user role on purchase

I am trying to use this snippet of code to update my users from the default role of 'Subscriber' to the role of 'Premium' on the purchase of a product from my store.
add_action( 'woocommerce_order_status_completed','change_role_on_purchase' );
function change_role_on_purchase( $order_id ) {
$order = wc_get_order( $order_id );
$items = $order->get_items();
$products_to_check = array( '416' );
foreach ( $items as $item ) {
if ( $order->user_id > 0 && in_array( $item['product_id'], $products_to_check ) ) {
$user = new WP_User( $order->user_id );
// Change role
$user->remove_role( 'Subscriber' );
$user->add_role( 'Premium' );
// Exit the loop
break;
}
}
}
I only have 1 product in my store and it has the product ID 416 (which I have inserted in the code).
I have put this into functions.php, but i'm not having any luck. The role isn't being updated after any successful purchase. Any ideas?
Have a try with this one:
function change_role_on_purchase( $order_id ) {
$order = new WC_Order( $order_id );
$items = $order->get_items();
foreach ( $items as $item ) {
$product_name = $item['name'];
$product_id = $item['product_id'];
$product_variation_id = $item['variation_id'];
if ( $order->user_id > 0 && $product_id == '416' ) {
update_user_meta( $order->user_id, 'paying_customer', 1 );
$user = new WP_User( $order->user_id );
// Remove role
$user->remove_role( 'subscriber' );
// Add role
$user->add_role( 'premium' );
}
}
}
add_action( 'woocommerce_order_status_processing', 'change_role_on_purchase' );
what if we want to check product category instead of product id? How do we tweak this? The code below is for verifying multiple products with their respective IDs.
add_action( 'woocommerce_order_status_processing', 'change_role_on_purchase' );
function change_role_on_purchase( $order_id ) {
$order = wc_get_order( $order_id );
$items = $order->get_items();
$products_to_check = array( '27167', '27166' );
foreach ( $items as $item ) {
if ( $order->user_id > 0 && in_array( $item['product_id'], $products_to_check ) ) {
$user = new WP_User( $order->user_id );
// Change role
$user->remove_role( 'friends' );
$user->add_role( 'customer' );
// Exit the loop
break;
}
}
}
In case someone is interested in this. If your customer wants to have in the future more products that will update the buyers role after purchase, instead of manually adding the product ids in the function, you can use this one
$products_to_check = wc_get_products( array( 'return' => 'ids', 'tag' => array('your tag here') ) );
Your customer can edit by him/herself a product and assign the product tag, that will allow the update of the user role.
Same thing here... Please keep in mind that woocommerce_order_status_processing means just that: that the order status is set to "processing". This does NOT mean that the order is "complete", meaning that it has been paid for. If you use this hook, you run the risk of making content available to your customer even though his order may not have been paid for. This is what woocommerce_order_status_completed is there for, but that one doesn't seem to work for virtual products, which get the order status automatically set to "completed".
woocommerce_order_status_completed only worked for me with virtual products disabled/unchecked in the products section, meaning I had to manually "complete" the order in order to trigger this hook. Still looking for a solution...

Categories