How to add a Woocommerce Subscription order note programmatically - woocommerce

With the below code, I can successfully add a custom order note to a subscription renewal order when the order is completed.
However, I would like to modify this to add the note to the Subscription Notes instead of the Order Notes. I think Subscription Notes use the same add_order_note function as $subscription->add_order_note instead of $order->add_order_note. But I have been unsuccessful in my attempts to get the $subscription variable to work in the below code.
// Add box contents (product excerpt) as order note for subscriptions
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_renewal_order' == $email->id ){ // for processing order status customer notification…
$product_id = '';
foreach ($order->get_items() as $item_id => $item_values) {
$product_id = $item_values['product_id'];
break; // (optional) stop loop to first item
}
// The text for the note
$note = get_the_excerpt($product_id);
// Add the note
$order->add_order_note( $note, $is_customer_note = 1 );
// Save the data
$order->save();
}
}
Do you know what I need to add/change to make this add to a Subscription note instead of order note?

Related

Extending search in WooCommerce admin orders list

I'm looking for a way to extend the search field in WooCommerce admin orders list for a custom meta key. Currently i'm using the woocommerce_shop_order_search_fields filter hook.
Resulting in this code, which allows me to search by the user id, order total and order number.
add_filter( 'woocommerce_shop_order_search_fields', 'woocommerce_shop_order_search_order_total' );
function woocommerce_shop_order_search_order_total( $search_fields ) {
$search_fields[] = '_order_total';
$search_fields[] = '_user_id';
$search_fields[] = '_order_number';
return $search_fields;
}
However, these are all existing meta keys, what if I want to search for meta data that doesn't yet exist? Any adivce?
Expanding the search in WooCommerce admin orders list can be done very easily by using the woocommerce_shop_order_search_fields filter hook, you just need to add some post_meta fields for order (item(s)). As long as this data exists of course!
By default, the following metakeys are present:
_billing_address_index
_shipping_address_index
_billing_last_name
_billing_email
So, for example, if you want to extend the search by the billing first name, you can do this by simply adding the metakey _billing_first_name, and then you get:
function filter_woocommerce_shop_order_search_fields( $search_fields ) {
// Metakey
$search_fields[] = '_billing_first_name';
return $search_fields;
}
add_filter( 'woocommerce_shop_order_search_fields', 'filter_woocommerce_shop_order_search_fields', 10, 1 );
Now, what if this metadata is not present for the orders? You could add that specific metadata for future orders, when the order is created. But wait! what about the existing orders? for these orders this data would not exist!
Wouldn't it be useful if we could add this data to existing orders, or even newer orders. Well this is possible, namely by using wc_get_orders(), to get (and update) the existing orders before running the search.
So you get: (where we in this example add the user's nickname as meta data to orders)
function filter_woocommerce_shop_order_search_fields( $search_fields ) {
// The desired meta key
$meta_key = '_user_nickname';
// Get ALL orders where a certain meta key not exists
$orders = wc_get_orders( array(
'limit' => -1, // Query all orders
'meta_key' => $meta_key, // Post meta_key
'meta_compare' => 'NOT EXISTS', // Comparison argument
));
// NOT empty
if ( ! empty ( $orders ) ) {
// Loop through the orders
foreach ( $orders as $order ) {
// Get the desired information via the order object
// Get user
$user = $order->get_user();
// User is NOT empty
if ( ! empty ( $user ) ) {
// Get nickname from user
$meta_value = $user->nickname;
// Meta value is NOT empty
if ( ! empty ( $meta_value ) ) {
// Add the meta data
$order->update_meta_data( $meta_key, $meta_value );
$order->save();
}
}
}
}
// Metakey
$search_fields[] = $meta_key;
return $search_fields;
}
add_filter( 'woocommerce_shop_order_search_fields', 'filter_woocommerce_shop_order_search_fields', 10, 1 );

How to display the last ordered product in WooCommerce via a shortcode

I'm looking for a way to display the last ordered product on another page.
I think it would be possible to maybe create a shortcode in the functions that takes the order details and displays them wherever I add the shortcode.
But I can't seem to figure out how to get it to work. So far I got this information to work with:
add_shortcode( 'displaylast', 'last' );
function last(){
$customer_id = get_current_user_id();
$order = wc_get_customer_last_order( $customer_id );
return $order->get_order();
}
[displaylast] is currently showing me noting. It does work when I change get_order() to get_billing_first_name().
That displays the order name. But I can't seem to get the item that was bought. Maybe there is a get_() that I'm not seeing?
You are close, however you must obtain the last product from the order object.
So you get:
function last() {
// Not available
$na = __( 'N/A', 'woocommerce' );
// For logged in users only
if ( ! is_user_logged_in() ) return $na;
// The current user ID
$user_id = get_current_user_id();
// Get the WC_Customer instance Object for the current user
$customer = new WC_Customer( $user_id );
// Get the last WC_Order Object instance from current customer
$last_order = $customer->get_last_order();
// When empty
if ( empty ( $last_order ) ) return $na;
// Get order items
$order_items = $last_order->get_items();
// Latest WC_Order_Item_Product Object instance
$last_item = end( $order_items );
// Get product ID
$product_id = $last_item->get_variation_id() > 0 ? $last_item->get_variation_id() : $last_item->get_product_id();
// Pass product ID to products shortcode
return do_shortcode("[product id='$product_id']");
}
// Register shortcode
add_shortcode( 'display_last', 'last' );
SHORTCODE USAGE
In an existing page:
[display_last]
Or in PHP:
echo do_shortcode('[display_last]');

WooCommerce - get shipping country for non registered user

Good morning everybody.
I need to implement a method to subtract shipping costs from the total in case the shipping destination is included in a specific array of values.
This is not a case of free shipping, because this costs will be added later for other reasons.
I cannot base my decision on the user country, for two reasons:
user can be not registered
user country and shipping country can be different.
I see that WooCommerce reload the order totals when I change billing/shipping country. I believe I need to intercept this kind of change an trigger an action to insert a new cart fee (a negative one, of course).
Well, how can I do that?
This is a part of my code
function delayShippingCosts(){
global $woocommerce;
$EUcountries = ['IT','AT','BE','BG','CY','HR','DK','EE','FI','FR','DE','GR','IE','LV','LT','LU','MT','NE','PL','PT','CZ','RO','SK','SI','ES','SE','HU'];
return in_array( $woocommerce->customer->get_country() , $EUcountries);
}
add_action( 'woocommerce_cart_calculate_fees', 'scc_detract_shipping_costs' );
function scc_detract_shipping_costs(){
global $woocommerce;
if(delayShippingCosts()){
$shippingCosts = WC()->cart->get_shipping_total() * -1;
if(current_user_can('administrator')) {
$woocommerce->cart->add_fee( 'Delayed shipping costs', $shippingCosts, true, 'standard' );
}
}
}
The problem is that now I'm looking to my customer data, and these are not dynamic (and void for unregisterd / unlogged users).
Any suggestions?
Thanks!!
EDIT
Almost OK
I managed to retrieve the shipping country from "woocommerce_checkout_update_order_review" hook, like that:
function action_woocommerce_checkout_update_order_review($posted_data) {
global $shipTo;
$data = array();
$vars = explode('&', $posted_data);
foreach ($vars as $k => $value){
$v = explode('=', urldecode($value));
$data[$v[0]] = $v[1];
}
WC()->cart->calculate_shipping();
$shipTo = $data['shipping_country'] ? $data['shipping_country'] : $data['billing_country'];
// REMOVE ALL NOTICES, IF PRESENTS...
wc_clear_notices();
}
add_action('woocommerce_checkout_update_order_review', 'action_woocommerce_checkout_update_order_review', 10, 1);
add_action( 'woocommerce_cart_calculate_fees', 'scc_detract_shipping_costs' );
function scc_detract_shipping_costs(){
global $woocommerce;
... something ...
if(condition) {
wc_add_notice("info message", "error");
}
}
My problem is that the notice is not removed when "condition" in false.
I tried to call wc_remove_notices() both in woocommerce_cart_calculate_fees and woocommerce_checkout_update_order_review. No big difference! :(
Any hint?
The delayShippingCosts() function is not needed. You can get the list of European countries via the get_european_union_countries method of the WC_Countries class (unless you want to customize the list).
Also you are getting the country with the get_country() method of the WC_Customer class.
The WC_Customer::get_country function is deprecated since version 3.0.
You can get the country like this:
WC()->customer->get_shipping_country() the country of the shipping address
WC()->customer->get_billing_country() the country of the billing address
Finally, note that to apply the standard tax class for the fee you have to set the 4th parameter as an empty string instead of 'standard'. See here for more information.
TO GET THE COUNTRY FIELD FROM A USER NOT LOGGED IN WITH THE woocommerce_cart_calculate_fees HOOK
You can send an AJAX call to send the billing and shipping country value when the respective fields change in the checkout.
To make sure that the AJAX function is executed before the woocommerce_cart_calculate_fees hook it is necessary to remove the update_totals_on_change class from the billing and shipping country fields (to avoid the AJAX call being made to update the checkout) and update the checkout only after the call AJAX has been completed.
This method may take a few extra milliseconds/second to update the checkout because you have to wait for the AJAX call to create the option to complete.
See this answer for more details on how to submit an AJAX call in Wordpress.
Add the following code inside your active theme's functions.php:
// enqueue the script for the AJAX call
add_action('wp_enqueue_scripts', 'add_js_scripts');
function add_js_scripts(){
wp_enqueue_script( 'ajax-script', get_stylesheet_directory_uri().'/js/script.js', array('jquery'), '1.0', true );
wp_localize_script( 'ajax-script', 'ajax_object', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ) ) );
}
// update options with checkout country values
add_action( 'wp_ajax_nopriv_set_option_country', 'set_option_country' );
add_action( 'wp_ajax_set_option_country', 'set_option_country' );
function set_option_country() {
if ( isset( $_POST ) ) {
// get the countries valued in the checkout by the user (guest or logged in)
$countries = $_POST['countries'];
$billing_country = $countries['billing_country'];
$shipping_country = $countries['shipping_country'];
// update options
update_option( 'guest_billing_country', $billing_country );
update_option( 'guest_shipping_country', $shipping_country );
// returns the output as a response to the AJAX call
echo 'success';
}
// always die in functions echoing AJAX content
die();
}
Create a script.js file and add it inside your child theme (because I used get_stylesheet_directory_uri() instead of get_template_directory_uri()) in the directory: /child-theme/js/script.js:
jQuery(function($){
// disable AJAX update
$('#billing_country_field').removeClass('update_totals_on_change');
$('#shipping_country_field').removeClass('update_totals_on_change');
// when the country fields change
$('#billing_country, #shipping_country').change(function(){
var countries = {
'billing_country': $('#billing_country').val(),
'shipping_country': $('#shipping_country').val(),
};
$.ajax({
url: ajax_object.ajaxurl,
type : 'post',
data: {
'action': 'set_option_country',
'countries': countries
},
complete: function(){
// update checkout via AJAX
$(document.body).trigger('update_checkout');
},
success:function(data) {
console.log(data);
},
error: function(errorThrown){
console.log(errorThrown);
}
});
});
});
The code has been tested and works.
So, the correct scc_detract_shipping_costs function will be:
add_action( 'woocommerce_cart_calculate_fees', 'scc_detract_shipping_costs' );
function scc_detract_shipping_costs(){
$countries = new WC_Countries();
// get the list of countries of the european union
$eu_countries = $countries->get_european_union_countries();
// get countries from checkout
$billing_country = get_option( 'guest_billing_country' );
$shipping_country = get_option( 'guest_shipping_country' );
// if the shipping country is part of the European Union
if ( in_array( $shipping_country, $eu_countries ) ) {
$shippingCosts = WC()->cart->get_shipping_total() * -1;
if ( current_user_can('administrator') ) {
WC()->cart->add_fee( 'Delayed shipping costs', $shippingCosts, true, '' );
}
}
}
The code has been tested and works. Add it to your active theme's functions.php.
TO GET THE COUNTRY FIELD FROM A USER NOT LOGGED IN WITH THE woocommerce_calculate_totals HOOK
add_action( 'woocommerce_calculate_totals', 'get_post_checkout_data' );
function get_post_checkout_data( $cart ) {
// get post data
if ( isset( $_POST['post_data'] ) ) {
parse_str( $_POST['post_data'], $post_data );
} else {
$post_data = $_POST;
}
if ( ! empty( $post_data ) ) {
$billing_country = $post_data['billing_country'];
$shipping_country = $post_data['shipping_country'];
// ...
}
}
Add the code in your active theme's functions.php.

Woocommerce - Add another custom status so it will make the order "editable"

I created another custom statuses to wc statuses, and I want this status to leave the order "editable", so I found this code in the file abstract-wc-order.php:
public function is_editable() {
return apply_filters( 'wc_order_is_editable', in_array( $this->get_status(), array( 'pending', 'on-hold', 'auto-draft', 'failed' ) ), $this );
}
Now if I understand currently, I need to add my custom status to the array above, but I'm not really sure how to do this - to add my status to the above array by hooking the filter wc_order_is_editable, I will really appreciate to any help!
Probably something like this will work:
function so_39193164_is_editable( $editable, $order ) {
if( $order->get_status() == 'your-new-status' ){
$editable = true;
}
return $editable;
}
add_filter( 'wc_order_is_editable', 'so_39193164_is_editable', 10, 2 );
If the order status is your new status then $editable will be true. If not, it will be whatever WooCommerce already decided in the is_editable() method.

Adding extra info to order

I am programmatically adding a product to the cart. Besides this, somehow, I want to store some extra info (an array) to the order. When the client finishes the order, I want to access that info through some WordPress actions. I'll have to do this immediately after adding the product to the cart, because the info may change after that, if the user doesn't finish the order right away. Is there any way I can do it, without putting the database to work?
You should probably use the WooCommerce Cart Item Meta API and the WooCommerce Order Item Meta API.
You use them like this:
// Add to cart item
// This is triggered on add to cart
add_filter('woocommerce_add_cart_item_data', 'my_add_cart_item_data', 10, 2);
function my_add_cart_item_data( $cart_item_meta, $product_id ) {
//Here we can easily filter what values should be added to what products using the $product_id
$cart_item_meta['my_meta_key'] = 'meta value';
return $cart_item_meta;
}
// Add to order item when the cart is converted to an order
// This is triggered when the order is created
add_action('woocommerce_add_order_item_meta', 'my_order_item_meta'), 10, 2);
function my_order_item_meta( $item_id, $values, $cart_item_key ) {
// The value stored in cart above is accessable in $values here
woocommerce_add_order_item_meta( $item_id, 'meta_key', $values['my_meta_key'] );
//Or add what ever you want
$meta_value = 'value';
woocommerce_add_order_item_meta( $item_id, 'meta_key', $meta_value );
}
I hope that helps.

Resources