I own/operate a WooCommerce Shop and I am looking for a way to Set up a Shop Notice when More than One Order comes in from the same Customer, Shipping to the Same Address.
I have been lucky enough to be blessed with tons of repeat customers. They will order from my shop on let's say a Monday and pay the shipping rate for their item(s) & then order on say a Tuesday (before I am able to ship out the first order), so I try to include the second product into first order to "SAVE" on shipping. But I can only save on Shipping if I pay attention to the customer's shipping address and bundle the items.
I would like my site to be a bit more efficient in this area and check for "OPEN" or "PROCESSING" orders that have matching shipping address's and pop up an alert when I open one of the orders in question...
Is something like this possible?
I have searched and searched and searched and nothing... I'm not completely sure where to start on this...
Is this possible with a custom script in functions.php file?
Is there a plugin that can help do this?
Any help is greatly appreciated.
Thanks.
Version code for WooCommerce 3+
add_action( 'woocommerce_admin_order_data_after_order_details', 'same_shipping_open_order', 10, 1 );
function same_shipping_open_order( $order ){
// Define HERE, in the array, your "OPEN" orders statuses
$open_order_statuses = array('wc-pending','wc-processing','wc-on-hold');
// Initialising variables
$matching = false;
// Get Order data (WC 3+ compatibility)
$order_data = $order->get_data();
// Get the shipping 'address_1' & 'postcode' fields for the CURRENT ORDER
$order_ship_address1 = $order_data['shipping']['address_1']; # (WC 3+ compatibility)
$order_ship_postcode = $order_data['shipping']['postcode']; # (WC 3+ compatibility)
// Getting customer orders, with an open status
$open_orders = wc_get_orders( array(
'numberposts' => -1,
'meta_key' => '_customer_user',
'meta_value' => $order->get_user_id(),
'post_type' => 'shop_order',
'post_status' => $open_order_statuses,
'exclude' => array($order_data['id']), # (WC 3+ compatibility)
) );
// Other "Open" orders for this customer
if( count($open_orders) != 0 ){
// Iterating through each orders
foreach($open_orders as $open_order){
// Get "open" Order data (WC 3+ compatibility)
$open_order_data = $open_order->get_data();
// Orders Id
$open_order_id = $open_order_data['id'];
if( $order_ship_address1 == $open_order_data['shipping']['address_1'] && $order_ship_postcode == $open_order_data['shipping']['postcode'] ){
// set condition to true (There is at once 1 matched order)
$matching = true;
// Other orders edit url
$order_edit_url = home_url( "/wp-admin/post.php?post=$open_order_id&action=edit/" );
// Storing orders edit url + ID
$results_arr[] = "<a href='$order_edit_url'>#$open_order_id</a>";
}
}
}
// If there is matching "Open" orders shipping addresss with this order
if ( $matching ) {
## 0. Converting the array in a string for output
$output_html = implode(', ', $results_arr);
## 1. Displaying an alert message on the order
echo '<br clear="all"><p style="margin-top:12px !important;"><strong style="color:red;">'. __("Same Shipping on Open Orders IDs: ").'</strong><br>'.$output_html.'</p>';
## 2. Javascript Alert message
?>
<script>
(function($){
alert('SAME SHIPPING ON OPEN ORDERS!');
})(jQuery);
</script>
<?php
}
}
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 the same way on WooCommerce 3+
See this related updated answer: How to get order details
in WooCommerce 3.0+
For WooCommerce version 2.5.x and 2.6.x
For WOOCOMMERCE VERSION 3.0+ see THIS UPDATE
This is completely possible using in woocommerce_admin_order_data_after_order_details action hook a custom hooked function in which you will have to define in an array the orders statuses for your "Open orders".
Here is this functional and tested code:
add_action( 'woocommerce_admin_order_data_after_order_details', 'same_shipping_open_order', 10, 1 );
function same_shipping_open_order( $order ){
// Define HERE, in the array, your "OPEN" orders statuses
$open_order_statuses = array('wc-pending','wc-processing','wc-on-hold');
// Initialising variables
$matching = false;
// Get the shipping 'address_1' & 'postcode' fields for the CURRENT ORDER
$order_ship_address1 = $order->shipping_address_1;
$order_ship_postcode = $order->shipping_postcode;
// Getting customer orders, with an open status
$open_orders = wc_get_orders( array(
'numberposts' => -1,
'meta_key' => '_customer_user',
'meta_value' => $order->get_user_id(),
'post_type' => 'shop_order',
'post_status' => $open_order_statuses,
'exclude' => array($order->id),
) );
// Other "Open" orders for this customer
if( count($open_orders) != 0 ){
// Iterating through each orders
foreach($open_orders as $open_order){
if( $order_ship_address1 == $open_order->shipping_address_1 && $order_ship_postcode == $open_order->shipping_postcode ){
$matching = true; // set condition to true
$open_order_id = $open_order->id;
// Other orders edit url
$order_edit_url = home_url( "/wp-admin/post.php?post=$open_order_id&action=edit/" );
// Storing orders edit url + ID
$results_arr[] = "<a href='$order_edit_url'>#$open_order_id</a>";
}
}
}
// If there is matching "Open" orders shipping addresss with this order
if ( $matching ) {
## 0. Converting the array in a string for output
$output_html = implode(', ', $results_arr);
## 1. Displaying an alert message on the order
echo '<br clear="all"><p style="margin-top:12px !important;"><strong style="color:red;">'. __("Same Shipping on Open Orders IDs: ").'</strong><br>'.$output_html.'</p>';
## 2. Javascript Alert message
?>
<script>
(function($){
alert('SAME SHIPPING ON OPEN ORDERS!');
})(jQuery);
</script>
<?php
}
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
You will get an alert message when opening/editing the order and also a text with the related "Open orders Ids and edit links. See the screenshot below:
Related
There is an issue when including "stock status" of the purchased items on the client email.
If the client orders the last unit of an item, even though when he ordered it was clearly instock (available for immediate shipping), then the stock status turns to outofstock/onbackorder and the email that is sent to the client (which I assume is sent/generated a few seconds after the stock value is updated from the clients own order) shows this status updated to outofstock/onbackorder, so after completing the purchase now the client thinks the product is out of stock when in fact it was not.
I'm using this code hooked onto my emails:
$product = $item->get_product();
$stockstatus = get_post_meta( $product->get_id(), '_stock_status', true );
if ($stockstatus == 'instock') { 'Available for immediate shipping'; }
elseif ($stockstatus == 'onbackorder') { 'On Preorder - slow shipping';}
We use Stock status to define shipping time of our products, it's simple, if it's in stock = immediate shipping, if it's outofstock/onbackorder = Preorder ( slow shipping )
Clients can view this information individually for each product on the product page and on the cart, however to make it as clear as possible and also keep a record of the Stock Status of when the order was made (so we know and can show to a client if a product was or not in stock when ordered) we also send this information to the client email. The issue being it displays the stock status at the time the email is sent and not at the time of the order (So if it was the last unit and the product turns to out of stock, then clients get the wrong message)
What would be the correct way to go about this situation and instead of displaying the current stock status, displaying the correct stock status at the time that the order was placed, so that the customers get the correct information?
Thank you in advance for the attention and advice
Edit:
Here you can find the full code on how I apply this upper snippet to my emails,
I apologize I did not share all the code above (the part for editing custom woocommerce email), my intention was to simplify as I felt it would be unrelated/filler/distracting from the main point as there are a few posts already covering how to customize woocommerce emails.
These are I believe the main posts that cover this:
Credits #Loictheaztec & #7uc1f3r
Customize order item meta only for WooCommerce admin email notifications
Display product ACF fields on a specific WooCommerce email
Here is the full code I am using :
// Setting the "sent_to_admin" as a global variable
function email_order_id_as_a_global($order, $sent_to_admin, $plain_text, $email) {
$GLOBALS['email_data'] = array(
'sent_to_admin' => $sent_to_admin, // <== HERE we set "$sent_to_admin" value
'email_id' => $email->id, // The email ID (to target specific email notification)
);
}
add_action('woocommerce_email_before_order_table', 'email_order_id_as_a_global', 1, 4);
function custom_order_item_name( $item_name, $item ) {
if ( ! is_wc_endpoint_url() && $item->is_type('line_item') ) {
// Getting the custom 'email_data' global variable
$refNameGlobalsVar = $GLOBALS;
$email_data = $refNameGlobalsVar['email_data'];
// Only for new order
if( is_array( $email_data ) && $email_data['email_id'] == 'new_order' ) {
// Get the WC_Product object (from order item)
$product = $item->get_product();
$product = $item->get_product();
$stockstatus = get_post_meta( $product->get_id(), '_stock_status', true );
if ($stockstatus == 'instock') { 'Available for immediate shipping'; }
elseif ($stockstatus == 'onbackorder') { 'On Preorder - slow shipping';}
}
}
}
return $item_name;
}
add_filter( 'woocommerce_order_item_name', 'custom_order_item_name', 10, 2 ); ```
You can use the following that will save the product stock status as custom order item meta data when customer place an order (so you will always get the stock status when the order was placed):
add_action('woocommerce_checkout_create_order_line_item', 'save_stock_status_order_item_meta', 10, 4 );
function save_stock_status_order_item_meta( $item, $cart_item_key, $values, $order ) {
$item->update_meta_data( '_stock_status', $values['data']->get_stock_status() );
}
Code goes in functions.php file of the active child theme (or active theme).
Then you will replace your "code hooked onto your emails" by this one:
$stock_status = $item->get_meta('_stock_status');
if ( 'instock' === $stock_status ) {
echo __('Available for immediate shipping');
} elseif ( 'onbackorder' === $stock_status ) {
echo __('On Preorder - slow shipping');
}
It should work.
I have a function that runs on the front-end of my WooCommerce Wordpress site that generates ticket numbers for people who answer a simple question correctly and no ticket order for those that do not answer correctly. It saves the users answer in the order item meta data and I have it so that the answer is displayed in the backend in an editable WooCommerce Text input field for each item within the order as each item would have a different answer.
add_action( 'woocommerce_admin_order_item_values', 'pd_admin_order_item_values', 10, 3);
function pd_admin_order_item_values( $product, $item, $item_id ) {
$user_answer = $item->get_meta( "User Answer" );
?>
<div class="edit_custom_field"> <!-- use same css class in h4 tag -->
<?php
woocommerce_wp_text_input( array(
'id' => 'custom_field_name',
'label' => 'Update User Answer:',
'value' => $user_answer,
'wrapper_class' => 'form-field-wide'
) );
?>
</div>
<?php
}
I need to make it so that if an admin changes it to the correct field and updates the post it will save the new data and run the function to assign a ticket number. I am using the correct hook I believe as it crashes when I hit update on the order but the logs are saying that it has too few arguments to run. I have seen some people have used $post_id and $post instead of the order variants but changing it to call these variables hasn't worked either.
add_action( 'woocommerce_process_shop_order_meta', 'custom_woocommerce_process_shop_order_meta', 10, 4 );
function custom_woocommerce_process_shop_order_meta( $item, $order_id, $order ){
// Loop through order items
foreach ($item->get_items() as $item_id => $item ) {
$admin_question = wc_sanitize_textarea( $_POST[ 'custom_field_name' ] );
$user_answer = $item->get_meta( $admin_question );
$admin_answer = $item->get_meta( "Answer" );
if( $admin_answer == $user_answer ){
// Check that tickets numbers haven't been generated yet for this item
if( $item->get_meta( "_tickets_number") )
continue;
$product = $item->get_product(); // Get the WC_Produt Object
$quantity = (int) $item->get_quantity(); // Get item quantity
// Get last ticket sold index from product meta data
$now_index = (int) $product->get_meta('_tickets_sold');
$tickets_ids = array(); // Initializing
// Generate an array of the customer tickets Ids from the product registered index (last ticket ID)
for ($i = 1; $i <= $quantity; $i++) {
$tickets_ids[] = $now_index + $i;
};
// Save the tickets numbers as order item custom meta data
$item->update_meta_data( "Tickets numbers", implode(' ', $tickets_ids) ); // Displayed string of tickets numbers on customer orders and emails
$item->update_meta_data( "User Answer", $user_answer ); // Displayed string of tickets numbers on customer orders and emails
$item->update_meta_data( "_tickets_number", $tickets_ids ); // (Optional) Array of the ticket numbers (Not displayed to the customer)
$item->save(); // Save item meta data
// Update the Ticket index for the product (custom meta data)
$product->update_meta_data('_tickets_sold', $now_index + $quantity );
$product->save(); // Save product data
} else {
$item->update_meta_data( "User Answer", $user_answer ); // Displayed string of tickets numbers on customer orders and emails
$item->save(); // Save item meta data
}
}
$order->save(); // Save all order data
}
My issue I believe lies in it not being able to call the order and order id variable when it runs the function but I am unsure.
Turns out that if I moved the order back to on hold, edited the meta data there and then moved it forward to processing again it runs the original function and assigned a number.
I am looking to display the last Order Note, currently only viewable via Admin in Woocommerce, on the customer side in their Order History.
So they can view the tracking number we add in after the order is set as complete.
https://example.com/my-account/view-order/135/
We add a customer note by first setting the order as COMPLETE via the Woocommerce API then adding an order note with the tracking link. So the tracking ref will always be the last item.
How can I show the last Order Note on the customer order history? No plugins seem to exist for Order Notes to be shown on the customer side.
Ideal outcome:
The following will display last admin order note to my account view orders pages:
add_filter( 'woocommerce_get_order_item_totals', 'account_view_order_last_order_note', 10, 3 );
function account_view_order_last_order_note( $total_rows, $order, $tax_display ){
// For "completed" orders on my account view order pages
if( $order->has_status('completed') && is_wc_endpoint_url( 'view-order' ) ){
// Get last order note
$latest_notes = wc_get_order_notes( array(
'order_id' => $order->get_id(),
'limit' => 1,
'orderby' => 'date_created_gmt',
) );
$latest_note = current( $latest_notes );
if ( isset( $latest_note->content ) ) {
// Add a new row for tracking
$total_rows['order_tracking'] = array(
'label' => __('Tracking:','woocommerce'),
'value' => $latest_note->content
);
}
}
return $total_rows;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
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'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...
?>