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() );
}
}
Related
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);
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...
I want to update the status of any and all orders (paid or unpaid) to completed, triggering the emails from woocommerce. Sounds counter intuitive but it needs to happen.
I thought something like this would work:
add_action('woocommerce_order_status_changed','status_changed_processsing');
function status_changed_processsing( $order_id, $checkout = null ) {
global $woocommerce;
$order = new WC_Order( $order_id );
//assign statu to that order
$order->status = 'completed';
}
}
But I have not been successful.
TIA. Appreciate any help!
Have a try with this one:
add_action( 'woocommerce_thankyou', 'status_changed_processsing' );
function status_changed_processsing( $order_id ) {
$order = new WC_Order( $order_id );
$order->update_status( 'completed' );
}
$order = new WC_Order($order_id);
if (!empty($order)) {
$order->update_status( 'completed' );
}
try like this
So I helped someone launch a site and they wanted a discounted product when someone purchased a specific product. I found a solution and implemented it and it worked at launch of the site and is no longer changing the role of customers when they purchase the products. I tried to get support from Woothemes and they don't support customization and want them to purchase a $129 extension to handle this.
Does anyone out there have a solution for this that still works?
Here is my code:
// Update User on purchase https://gist.github.com/troydean/9322593
function lgbk_add_member( $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 == '247' || $order->user_id > 0 && $product_id == '255') {
update_user_meta( $order->user_id, 'paying_customer', 1 );
$user = new WP_User( $order->user_id );
// Remove role
$user->remove_role( 'customer' );
// Add role
$user->add_role( 'author' );
}
}
add_action( 'woocommerce_order_status_completed', 'lgbk_add_member' );
UPDATE
Normally this updated code version should work with woocommerce_order_status_completed and then you should try this code before. (This code is also compatible with next upcoming major WooCommerce update 2.7).
Here is the code:
add_action( 'woocommerce_order_status_completed', 'custom_action_on_completed_customer_email_notification' );
function custom_action_on_completed_customer_email_notification( $order_id ) {
// Set HERE your targetted products IDs:
$targetted_products = array( 247, 255 );
$order = wc_get_order( $order_id );
if ( $order->get_user_id() > 0 ) {
foreach ( $order->get_items() as $order_item ) {
// Here we detect if the a target product is part of this order items
if ( in_array( $order_item['product_id'], $targetted_products ) ){
// I think tha this is not really needed as it's set when an order has been paid…
update_user_meta( $order->get_user_id(), 'paying_customer', 1 ); // 1 => true
// Remove all roles and set 'editor' as user role (for current user)
$user = new WP_User( $order->get_user_id() );
$user->set_role( 'author' );
// Product is found, we break the loop…
break;
}
}
}
}
But as I don't know how your order is changed to 'completed' status, if you want to be sure (in all possible cases) that the customer that will buy one of your 2 specific products, will have his role changed from 'customer' to 'author' when order status is set to 'completed', I will recommend you trying to use this an email notification hook (if the first code snippet doesn't work).
For example here I use woocommerce_email_before_order_table hook, that will be executed and fired on "Completed order customer email notification" with the help of some conditions. (This code is also compatible with next upcoming major WooCommerce update 2.7).
Here is your revisited and tested code:
add_action( 'woocommerce_email_before_order_table', 'custom_action_on_completed_customer_email_notification', 10, 4 );
function custom_action_on_completed_customer_email_notification( $order, $sent_to_admin, $plain_text, $email ) {
if( 'customer_completed_order' == $email->id ){
// Set HERE your targetted products IDs:
$targetted_products = array( 247, 255 );
if ( $order->get_user_id() > 0 ) {
foreach ( $order->get_items() as $order_item ) {
// Here we detect if the a target product is part of this order items
if ( in_array( $order_item['product_id'], $targetted_products ) ){
// I think tha this is not really needed as it's set when an order has been paid…
update_user_meta( $order->get_user_id(), 'paying_customer', 1 ); // 1 => true
// Remove all roles and set 'editor' as user role (for current user)
$user = new WP_User( $order->get_user_id() );
$user->set_role( 'author' );
// Product is found, we break the loop…
break;
}
}
}
}
}
Code goes in function.php file of your active child theme (or theme). Or also in any plugin php files.
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;
}
}