Applying programmatically a coupon to an Order in WooCommerce3 - php

I'm developing a plugin that creates an order directly (no cart) and applies a coupon. In version 3.0 of the woo API the function add_coupon() has been deprecated in favour of a WC_Order_Item_Coupon object you add to the order.
Create the coupon
$coupon = new WC_Order_Item_Coupon();
$coupon->set_props(array('code' => $coupon, 'discount' => $discount_total,
'discount_tax' => 0));
$coupon->save();
This is successful. I can validate by calling $coupon->get_discount().
I then add the coupon to the order and recalculate totals:
$order->add_item($item);
$order->calculate_totals($discount_total);
$order->save();
Logging into wp-admin I can see the order with coupon code visible. However, the coupon has had no effect on line items or total.
Have a misunderstood how api v3.0 intends us to handle coupons?

How about using WC_Abstract_Order::apply_coupon?
/**
* Apply a coupon to the order and recalculate totals.
*
* #since 3.2.0
* #param string|WC_Coupon $raw_coupon Coupon code or object.
* #return true|WP_Error True if applied, error if not.
*/
public function apply_coupon( $raw_coupon )
Here is my code.
$user = wp_get_current_user();
$order = new WC_Order();
$order->set_status('completed');
$order->set_customer_id($user->ID);
$order->add_product($product , 1); // This is an existing SIMPLE product
$order->set_currency( get_woocommerce_currency() );
$order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
$order->set_customer_ip_address( WC_Geolocation::get_ip_address() );
$order->set_customer_user_agent( wc_get_user_agent() );
$order->set_address([
'first_name' => $user->first_name,
'email' => $user->user_email,
], 'billing' );
// $order->calculate_totals(); // You don't need this
$order->apply_coupon($coupon_code);
$order->save();

OK, so I played about a little longer and it looks like in V3 things are a little more manual.
Adding a WC_Order_Item_Coupon item to a woo order does simply that. It adds the coupon object to the order object. No calculations are made and the product line items remain unchanged. You have to iterate over the product items manually and apply the coupon yourself by calculating the line item totals and subtotals. calculate_totals() then does as expected.
// Create the coupon
global $woocommerce;
$coupon = new WC_Coupon($coupon_code);
// Get the coupon discount amount (My coupon is a fixed value off)
$discount_total = $coupon->get_amount();
// Loop through products and apply the coupon discount
foreach($order->get_items() as $order_item){
$product_id = $order_item->get_product_id();
if($this->coupon_applies_to_product($coupon, $product_id)){
$total = $order_item->get_total();
$order_item->set_subtotal($total);
$order_item->set_total($total - $discount_total);
$order_item->save();
}
}
$order->save();
I wrote a helper function to make sure the coupon applies to the product in question coupon_applies_to_product(). Strictly not needed given I'm creating the order entirely in code.. but I use it it other places so added it.
// Add the coupon to the order
$item = new WC_Order_Item_Coupon();
$item->set_props(array('code' => $coupon_code, 'discount' => $discount_total, 'discount_tax' => 0));
$order->add_item($item);
$order->save();
You now get the nicely formatted order in wp-admin with line items showing the specific discount + the coupon code etc.

Related

How to update WooCommerce order item quantity

I need to update the order item meta in a woocommerce oder on checkout page or while woocommerce creates the order.
I'm using the plugin visual product configurator and it is not passing the right quantity of some items of the order to woocommerce order meta, especially when I use multiple variations on the same product.
Is there a hook for me to use to update the item quantity for a certain order item and how can I use it?
The plugin returns me an array with all the cart information and I can only check if an item of the order appears multiple times - if yes I need to change the quantity of that item to that number in the woocommerce order/database.
I was thinking of adding the following hook to my functions.php
add_action('woocommerce_checkout_create_order', 'change_qty', 1,1);
function change_qty($item_qty){
foreach($item_qty as $qty) {
$qty['product_id'] = $id;
$qty['qty'] = $new_qty
$order->update_meta_data('quantity', $new_qty, $id)
}
}
Whereas $item_qty is be an multi-dimensional array containing the item_ids and adjusted quantities.
Another problem I'm facing is that I dont know when I need to call that function because I get the array from the plugin on the checkout page, but I think WooCommerce has not yet created an order at that moment?
The result should be an adjusted item quantity in the woocommerce order summary in the backend.
To update the order item quantity, you can use WC_Order_Item_Product set_quantity() method.
The correct hook to update order items (line items) is woocommerce_checkout_create_order_line_item action hook, that is triggered during order creation, before data is saved to databased.
add_action('woocommerce_checkout_create_order_line_item', 'change_order_line_item_quantity', 10, 4 );
function change_order_line_item_quantity( $item, $cart_item_key, $cart_item, $order ) {
// Your code goes below
// Get order item quantity
$quantity = $item->get_quantity();
$new_qty = $quantity + 2;
// Update order item quantity
$item->set_quantity( $new_qty );
}
The function arguments (variables) are defined and usable:
$item is the WC_Order_Item_Product Object (not saved yet to database)
$cart_item_key is the related cart item key
$cart_item is the related cart item data
$order is the WC_Order Object (not saved yet to database)
Related:
Get Order items and WC_Order_Item_Product in Woocommerce 3
WC_Order_Item_Product Class and methods API Documentation
WC_Checkout and woocommerce_checkout_create_order_line_item action hook located in the create_order_line_items() method
This can help you (we hook into payment completed notification from the payment provider). If you want to update the _qty just after the order was created, I can change my function. But for now I would update it only when the payment was successful.:
/**
* Update order item qty after payment successful
*/
add_filter( 'woocommerce_payment_complete_order_status', 'update_order_item_qty', 10, 2 );
function update_order_item_qty( $order_status, $order_id ) {
//Get the order and items
$order = new WC_Order( $order_id );
$items = $order->get_items();
//New qty
$new_qty = 0;
foreach ( $items as $item_id => $item_data ) {
update_meta_data( '_qty', $new_qty, $item_id );
}
}
Please try if this is what you'r looking for.

Setting free trial programatically for a cart item in WooCommerce Subscriptions

I'm trying to set the free trial period of a cart item in WooCommerce during an add to cart AJAX request. The code seems to work when there is one subscription product in the cart, but when there are two or more of the same subscription product in the cart, the free trial is applied to all those products as well, even though the code is only specifically altering the cart data for one of the products. The code I'm using is below. Also $job->get_free_trial_length() is definitely returning the correct value. Any help would be greatly appreciated as I've been struggling with this for some time! Thanks.
UPDATE
When changing the free trial period on the cart item, it seems to also updates the subscription product's free trial period, which is why the other products also changed. So my question is, can free trials be done on a cart item level instead of a product level?
/**
* Add the subscription package to the user's cart.
*
* #param int $job_id
* #param int $variation_id
* #return string
*/
function add_package_to_cart( $job_id, $variation_id ) {
$job = fcrm_get_job( $job_id );
$product_id = $job->get_linked_subscription_product_id();
$quantity = 1;
$cart_item_data = array(
'job_id' => $job_id
);
// Add to cart
$key = WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, array(), $cart_item_data );
// Set the free trial period
wcs_set_objects_property( WC()->cart->cart_contents[ $key ]['data'], 'subscription_trial_length', $job->get_free_trial_length() );
return $key;
}
Found the solution. wcs_set_objects_property() accepts a 4th argument to not save the value to the database, but instead keep the value in memory. Then call the function every time cart totals are calculated.

WooCommerce Admin Create Order & Adding Shipping on Order Pay

I am creating order in the admin. I added some functionality to the order pay page which is sent to the customer (form-pay.php). I added ability to remove items from the order as well as update your billing and shipping info (both for the order and account). I use ajax and calculate_totals() after an item is removed, which works.
However, I can't seem to figure out how to get shipping applied to the order. I need this to happen when the order is created in the admin and when someone removes an item on the frontend.
I tried just setting the shipping post meta but that isn't working.
function my_order_update_shipping($order_id, $items) {
$order = wc_get_order($order_id);
$order_subtotal = $order->get_subtotal();
if($order_subtotal > '17.99'){
update_post_meta($order_id, '_order_shipping', '0');
}else{
update_post_meta($order_id, '_order_shipping', '4');
}
}
add_action('woocommerce_before_save_order_items', 'my_order_update_shipping');
How can I achieve this? Or apply a shipping method in this way?
I finally figure this out. Works good but you need to click the "recalculate" button when adding/removing an item in the admin > create order. The below code applies 1 of 2 shipping methods based on a static subtotal amount.
$delivery_zones = WC_Shipping_Zones::get_zones();
foreach ((array) $delivery_zones as $key => $the_zone) {
$shipping_methods = $the_zone['shipping_methods'];
}
// Apply Correct Shipping Method
if ($order_subtotal > '17.99') {
$rate = $shipping_methods[2];
$item = new WC_Order_Item_Shipping();
$item->set_props(array('method_id' => $rate->id, 'total' => wc_format_decimal($rate->cost)));
$order->add_item($item);
} else {
$rate = $shipping_methods[1];
$item = new WC_Order_Item_Shipping();
$item->set_props(array('method_id' => $rate->id, 'total' => wc_format_decimal($rate->cost)));
$order->add_item($item);
}
$order->calculate_totals();
$order->save();

Change Order item prices in Woocommerce 3

I need to change the item price in a woocommerce order but everything I found is to changing the price in the cart but this is not what I need because I need to change after the checkout process.
Does somebody can give me a clue on how to do that?
You need to use the new CRUD setters methods introduced with Woocommerce 3:
For order object you will use WC_Order methods,
For order "line item" you will use WC_Order_Item_Product methods,
For both of them you could be also use some WC_Data methods like save()…
Here is a working basic example with a static price and a static order ID:
$order_id = 809; // Static order Id (can be removed to get a dynamic order ID from $order_id variable)
$order = wc_get_order( $order_id ); // The WC_Order object instance
// Loop through Order items ("line_item" type)
foreach( $order->get_items() as $item_id => $item ){
$new_product_price = 50; // A static replacement product price
$product_quantity = (int) $item->get_quantity(); // product Quantity
// The new line item price
$new_line_item_price = $new_product_price * $product_quantity;
// Set the new price
$item->set_subtotal( $new_line_item_price );
$item->set_total( $new_line_item_price );
// Make new taxes calculations
$item->calculate_taxes();
$item->save(); // Save line item data
}
// Make the calculations for the order and SAVE
$order->calculate_totals();
Then you will have to replace the static price by your submitted new price in your custom page, which is not so simple, as you will need to target the correct $item_id…
Thank you very much, I spent 4 hours looking for how to change the quantity of the product in the order and based on your code (I rewrote the necessary part) I finally got it! that's if someone needs to change the quantity product in the order `
$order = wc_get_order( $_POST['orderID'] );
foreach( $order->get_items() as $item_id => $item ){
$product = $item->get_product();
$product_price = (int) $product->get_price(); // A static replacement product price
$new_quantity = (int) $_POST['productQty'] // product Quantity
// The new line item price
$new_line_item_price = $product_price * $new_quantity;
// Set the new price
$item->set_quantity($_POST['orderQty']);
$item->set_subtotal( $new_line_item_price );
$item->set_total( $new_line_item_price );
// Make new taxes calculations
$item->calculate_taxes();
$item->save(); // Save line item data
}
// Make the calculations for the order and SAVE
$order->calculate_totals();`
#LoicTheAztec
Completing an automated woo-commerce Payment by manually inputting an identifier
The shopper goes online, creates an account , adds a payment method, and fills their cart .
We hold the amount plus 15% when they check out.
woocommerce sends order details to the delivery team that takes the gig .
They go to the store and shop
After checking out at the physical store, the new invoice total is uploaded to woo-commerce via the shopping app .
This manually entered amount will be the IDENTIFIER in the stripe that TRIGGERS the order completion

How to create custom discount for the cart in woocommerce

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...
?>

Categories