How do I combine these so I can autocomplete orders with only virtual subscriptions articles?
add_action( 'woocommerce_payment_complete', 'woocommerce_subscriptions_auto_complete_order' );
function woocommerce_subscriptions_auto_complete_order( $order_id ) {
if ( ! $order_id ) {
return;
}
$order = wc_get_order( $order_id );
$order->update_status( 'completed' );
}
and this which autocompletes virtual products. Although my subscriptions are virtual this didn't work
/**
* Auto Complete all WooCommerce virtual orders.
*
* #param int $order_id The order ID to check
* #return void
*/
function custom_woocommerce_auto_complete_virtual_orders( $order_id ) {
// if there is no order id, exit
if ( ! $order_id ) {
return;
}
// get the order and its exit
$order = wc_get_order( $order_id );
$items = $order->get_items();
// if there are no items, exit
if ( 0 >= count( $items ) ) {
return;
}
// go through each item
foreach ( $items as $item ) {
// if it is a variation
if ( '0' != $item['variation_id'] ) {
// make a product based upon variation
$product = new WC_Product( $item['variation_id'] );
} else {
// else make a product off of the product id
$product = new WC_Product( $item['product_id'] );
}
// if the product isn't virtual, exit
if ( ! $product->is_virtual() ) {
return;
}
}
/*
* If we made it this far, then all of our items are virtual
* We set the order to completed.
*/
$order->update_status( 'completed' );
}
add_action( 'woocommerce_thankyou', 'custom_woocommerce_auto_complete_virtual_orders' );
I found an easier way at https://action-a-day.com/autocomplete-orders-in-woocommerce-3-0/
add_filter( 'woocommerce_order_item_needs_processing' , 'filter_woo_item_needs_processing', 10, 3 );
function filter_woo_item_needs_processing( $needs_processing, $product, $order_ID ) {
$product_type = $product->get_type();
if ( $product->is_virtual()
&& ( 'subscription' == $product_type || 'subscription_variation' == $product_type || 'variable-subscription' == $product_type ) ) {
return false;
}
return $needs_processing;
}
Related
I'm using the save_post_{$post->post_type} hook, which fires once a post (order) has been saved.
The intention is to save product meta based on certain order statuses. This is the code I wrote/used for this:
add_action ( 'save_post_shop_order', function (int $postId, \WP_Post $post, bool $update): void {
$order = new WC_Order( $postId );
$order_status = $order->get_status();
$status_arrays = array( 'processing', 'on-hold', 'to-order', 'needed' );
if ( in_array($order_status, $status_arrays) ) {
return;
}
$items = $order->get_items();
foreach ( $items as $item ) {
$product_id = $item->get_product_id();
$product = wc_get_product( $product_id );
$final_product->update_meta_data( '_test', '_test' );
$final_product->save();
}
},
10,
3
);
However, I can't find the new metadata in my database. Any advice? can anyone help me how to achieve this?
Your code contains some mistakes, for example $final_product
is not equal to $product and is therefore not defined.
This should suffice: (explanation via comment tags, added in the code)
function action_save_post_shop_order( $post_id, $post, $update ) {
// Checking that is not an autosave && current user has the specified capability
if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_shop_order', $post_id ) ) {
return;
}
// Get $order object
$order = wc_get_order( $post_id );
// Is a WC_Order
if ( is_a( $order, 'WC_Order' ) ) {
// NOT has status
if ( ! $order->has_status( array( 'processing', 'on-hold', 'to-order', 'needed' ) ) ) {
// Loop through order items
foreach( $order->get_items() as $item ) {
// Get an instance of corresponding the WC_Product object
$product = $item->get_product();
// Meta: key - value
$product->update_meta_data( '_test', '_test' );
// Save
$product->save();
}
}
}
}
add_action( 'save_post_shop_order', 'action_save_post_shop_order', 10, 3 );
Note: If the order should have a certain status versus NOT having the status, just remove the ! from:
// NOT has status
if ( ! $order->has_status(..
i tryed to make a code to verify the category of the product in a order when it is payed, i have tested but it dident work, i tested with credit card, but the status went to completed automatcly.
I want the order go to status "completed" when in the order have a product with the category "pack" and dont have any other diferent category, if have any other product with a different category, i want the order to go to the status "aguardando-envio".
could you help me with this?
add_action( 'woocommerce_payment_complete', function ( $order_id ) {
if( ! $order_id ) return;
$order = wc_get_order( $order_id );
// 2. Initialize $cat_in_order variable
$cat_in_order = false;
// 3. Get order items and loop through them...
// ... if product in category, edit $cat_in_order
$items = $order->get_items();
foreach ( $items as $item ) {
$product_id = $item->get_product_id();
if ( has_term( 'videos-personalizaveis', $product_id ) ) {
$cat_in_order = true;
break;
}
if ( has_term( 'fotos-personalizaveis', $product_id ) ) {
$cat_in_order = true;
break;
}
if ( has_term( 'audios-personalizaveis', $product_id ) ) {
$cat_in_order = true;
break;
}
if ( has_term( 'produtos-pessoais', $product_id ) ) {
$cat_in_order = true;
break;
}
}
if ( $cat_in_order ) {
$order->update_status( 'aguardando-envio' );
}
if ( $cat_in_order == false ) {
$order->update_status( 'completed' );
}
}, 10, 3 );
Try the following instead, that will autocomplete order status for an exclusive product category "pack", other wise it will set the order status to 'aguardando-envio':
add_action( 'woocommerce_payment_complete_order_status', 'wc_auto_complete_paid_order', 10, 3 );
function wc_auto_complete_paid_order( $status, $order_id, $order ) {
$exclusive_category = array("pack"); // <== Here set your exclusive category
$others_found = false; // Initializing
// Loop through order items
foreach ( $order->get_items() as $item ) {
if( ! has_term( $exclusive_category, 'product_cat', $item->get_product_id() ) ) {
$others_found = true;
break;
}
}
return $others_found ? 'aguardando-envio' : 'completed';
}
Code goes in functions.php file of the active child theme (or active theme). It should works.
Notes:
The custom status 'aguardando-envio' need to be created before for WooCommerce orders, via some custom code or a plugin.
This will not work for "bacs", "cod" or "cheque" payment method Ids, where payment need to be confirmed by shop manager by changing the oder status via admin.
Related: WooCommerce: Auto complete paid orders
You have a mistake in checking whether the product is in the desired category or not. On the other hand, the order status may not be registered properly. Here you will find everything you need:
/**
* Register new status
*/
add_action( 'init', 'ywp_register_aguardando_envio_shipment_order_status' );
function ywp_register_aguardando_envio_shipment_order_status() {
register_post_status( 'wc-aguardando-envio', array(
'label' => 'Aguardando Envio',
'public' => true,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
'label_count' => _n_noop( 'Aguardando Envio (%s)', 'Aguardando Envio (%s)' )
) );
}
/**
* Add to list of WC Order statuses
*/
add_filter( 'wc_order_statuses', 'ywp_add_aguardando_envio_shipment_to_order_statuses' );
function ywp_add_aguardando_envio_shipment_to_order_statuses( $order_statuses ) {
$new_order_statuses = array();
// add new order status after processing
foreach ( $order_statuses as $key => $status ) {
$new_order_statuses[ $key ] = $status;
if ( 'wc-processing' === $key ) {
$new_order_statuses['wc-aguardando-envio'] = 'Aguardando Envio';
}
}
return $new_order_statuses;
}
/**
* Change order status
*/
add_action( 'woocommerce_thankyou', 'ywp_change_order_status_to_aguardando_envio', 99 );
function ywp_change_order_status_to_aguardando_envio( $order_id ) {
if( ! $order_id ) return;
$order = wc_get_order( $order_id );
// 2. Initialize $cat_in_order variable
$cat_in_order = false;
// 3. Get order items and loop through them...
// ... if product in category, edit $cat_in_order
$items = $order->get_items();
foreach( $items as $item ) {
$product_id = $item->get_product_id();
if( has_term( array( 'videos-personalizaveis', 'fotos-personalizaveis', 'audios-personalizaveis', 'produtos-pessoais' ), 'product_cat', $product_id ) ) {
$cat_in_order = true;
break;
}
}
if ( $cat_in_order ) {
$order->update_status( 'aguardando-envio' );
}
}
The code goes in the functions.php file of active theme/child theme. Tested and works.
I have a virtual product and I want it to change status after payment is completed.
The following code changes all purchases to "completed", but I want to change only one of my products, not all of them.
My product is a variable product and has 4 items,
add_action( 'woocommerce_thankyou', 'custom_woocommerce_auto_complete_order' );
function custom_woocommerce_auto_complete_order( $order_id ) {
if ( ! $order_id ) {
return;
}
$order = wc_get_order( $order_id );
if( $order->has_status( 'processing' ) ) {
$order->update_status( 'completed' );
}
}
I searched a lot but did not find an answer. please help me. Thank You
You can target specific product(s) Id(s) from order items, to make your code work only for them as follows:
add_action( 'woocommerce_thankyou', 'custom_woocommerce_auto_complete_order' );
function custom_woocommerce_auto_complete_order( $order_id ) {
if ( ! $order_id ) {
return;
}
$order = wc_get_order( $order_id );
$product_ids = array('23'); // Here set your targeted product(s) Id(s)
$product_found = false;
// Loop through order items
foreach ( $order->get_items() as $item ) {
if( array_intersect( $product_ids, array($item->get_product_id(), $item->get_variation_id()) ) ) {
$product_found = true;
break;
}
}
if( $order->has_status( 'processing' ) && $product_found ) {
$order->update_status( 'completed' );
}
}
If you want to target those products exclusively, then you will use the following instead:
add_action( 'woocommerce_thankyou', 'custom_woocommerce_auto_complete_order' );
function custom_woocommerce_auto_complete_order( $order_id ) {
if ( ! $order_id ) {
return;
}
$order = wc_get_order( $order_id );
$product_ids = array('23'); // Here set your targeted product(s) Id(s)
$product_found = true;
// Loop through order items
foreach ( $order->get_items() as $item ) {
if( ! array_intersect( $product_ids, array($item->get_product_id(), $item->get_variation_id()) ) ) {
$product_found = false;
break;
}
}
if( $order->has_status( 'processing' ) && $product_found ) {
$order->update_status( 'completed' );
}
}
Code goes in functions.php file of the active child theme (or active theme). It should works.
Addition: Avoid multiple notifications
You should better use woocommerce_payment_complete_order_status hook instead like in WooCommerce: Auto complete paid orders answer, to avoid multiple notifications.
So the code is going to be:
add_action( 'woocommerce_payment_complete_order_status', 'wc_auto_complete_paid_order', 10, 3 );
function wc_auto_complete_paid_order( $status, $order_id, $order ) {
$product_ids = array('23'); // Here set your targeted product(s) Id(s)
$product_found = false;
// Loop through order items
foreach ( $order->get_items() as $item ) {
if( array_intersect( $product_ids, array($item->get_product_id(), $item->get_variation_id()) ) ) {
$product_found = true;
break;
}
}
return $product_found ? 'completed' : $status;
}
Or targeting products exclusively:
add_action( 'woocommerce_payment_complete_order_status', 'wc_auto_complete_paid_order', 10, 3 );
function wc_auto_complete_paid_order( $status, $order_id, $order ) {
$product_ids = array('23'); // Here set your targeted product(s) Id(s)
$product_found = true;
// Loop through order items
foreach ( $order->get_items() as $item ) {
if( ! array_intersect( $product_ids, array($item->get_product_id(), $item->get_variation_id()) ) ) {
$product_found = false;
break;
}
}
return $product_found ? 'completed' : $status;
}
It should work...
I am trying to dynamically add to a products price when a user checks out based on what they have entered in the product page. The setting of the price is only working on a non variation product.
I need to be able to set the price on the variations of the products as well.
The code that I am using:
function add_cart_item_data( $cart_item_meta, $product_id, $variation_id ) {
$product = wc_get_product( $product_id );
$price = $product->get_price();
$letterCount = strlen($_POST['custom_name']);
$numberCount = strlen($_POST['custom_number']);
if($letterCount != '0') {
$letterPricing = 20 * $letterCount;
$numberPricing = 10 * $numberCount;
$additionalPrice = $letterPricing + $numberPricing;
$cart_item_meta['custom_price'] = $price + $additionalPrice;
}
return $cart_item_meta;
}
function calculate_cart_total( $cart_object ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
return;
}
foreach ( $cart_object->cart_contents as $key => $value ) {
if( isset( $value['custom_price'] ) ) {
$price = $value['custom_price'];
$value['data']->set_price( ( $price ) );
}
}
}
I have completely revisited your code:
// Set custom data as custom cart data in the cart item
add_filter( 'woocommerce_add_cart_item_data', 'add_custom_data_to_cart_object', 30, 3 );
function add_custom_data_to_cart_object( $cart_item_data, $product_id, $variation_id ) {
if( ! isset($_POST['custom_name']) || ! isset($_POST['custom_number']) )
return $cart_item_data; // Exit
if( $variation_id > 0)
$product = wc_get_product( $variation_id );
else
$product = wc_get_product( $product_id );
$price = $product->get_price();
// Get the data from the POST request and calculate new custom price
$custom_name = sanitize_text_field( $_POST['custom_name'] );
if( strlen( $custom_name ) > 0 )
$price += 20 * strlen( $custom_name );
$custom_number = sanitize_text_field( $_POST['custom_number'] );
if( strlen( $custom_number ) > 0 )
$price += 10 * strlen( $custom_number );
// Set new calculated price as custom cart item data
$cart_item_data['custom_data']['price'] = $price;
return $cart_item_data;
}
// Set the new calculated price of the cart item
add_action( 'woocommerce_before_calculate_totals', 'set_new_cart_item_price', 50, 1 );
function set_new_cart_item_price( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
foreach ( $cart->get_cart() as $cart_item ) {
if( isset( $cart_item['custom_data']['price'] ) ) {
// Get the new calculated price
$new_price = (float) $cart_item['custom_data']['price'];
// Set the new calculated price
$cart_item['data']->set_price( $new_price );
}
}
}
This code goes on function.php file of your active child theme (or theme).
Tested and works as well with product variations.
Q: I want to have my customer to have access to the downloadable products when the status of his order is 'on-hold'.
When a customer places an order for a downloadable product AND a physical product, I sometimes want to place the order in the on-hold status (by hand). But then the customer can not download the downloadable product.
The grant access is regulated in woocommerce/includes/wc-order-functions.php (2.2)
/**
* Order Status completed - GIVE DOWNLOADABLE PRODUCT ACCESS TO CUSTOMER.
*
* #access public
* #param int $order_id
*/
function wc_downloadable_product_permissions( $order_id ) {
if ( get_post_meta( $order_id, '_download_permissions_granted', true ) == 1 ) {
return; // Only do this once
}
$order = wc_get_order( $order_id );
if ( $order && $order->has_status( 'processing' ) && get_option( 'woocommerce_downloads_grant_access_after_payment' ) == 'no' ) {
return;
}
if ( sizeof( $order->get_items() ) > 0 ) {
foreach ( $order->get_items() as $item ) {
$_product = $order->get_product_from_item( $item );
if ( $_product && $_product->exists() && $_product->is_downloadable() ) {
$downloads = $_product->get_files();
foreach ( array_keys( $downloads ) as $download_id ) {
wc_downloadable_file_permission( $download_id, $item['variation_id'] > 0 ? $item['variation_id'] : $item['product_id'], $order, $item['qty'] );
}
}
}
}
update_post_meta( $order_id, '_download_permissions_granted', 1 );
do_action( 'woocommerce_grant_product_download_permissions', $order_id );
}
add_action( 'woocommerce_order_status_completed', 'wc_downloadable_product_permissions' );
add_action( 'woocommerce_order_status_processing', 'wc_downloadable_product_permissions' );
What do I have to change to accomplish this?
14-07-2016 UPDATE
I have paid a coder for helping me out. This is the code where I was looking for. Add this code to your functions.php:
function add_onhold_status_to_download_permission($data, $order) {
if ( $order->has_status( 'on-hold' ) ) { return true; }
return $data;
}
add_filter('woocommerce_order_is_download_permitted', 'add_onhold_status_to_download_permission', 10, 2);
There is an easier way to do this, I just contacted Woocommerce support and there is a box to check in Woocommerce > Settings > Products > Downloadable Products called "Grant access to downloadable products after payment", which allows users to access their downloadable products after their purchase.