Since I will be using offline payments (bank billets - standard in Brazil). What I am trying to achieve is to auto-cancel "on-hold" orders after 9 days, which is when the billet expires. I found a few references of code: one from woocommerce github and another from stackoverflow. What the code does (kinda messy in my opinion) is mirror the "pending" cancelation to "on hild". On github they are saying that it's important to use date_modified arguments to pull orders from the last hour. I have tested this out but it's not working. Do not know what the problem is.
<?php
function wc_foo_cancel_unpaid_onhold_orders() {
global $wpdb;
$held_duration = get_option('woocommerce_hold_stock_minutes');
if ( $held_duration < 1 || 'yes' !== get_option( 'woocommerce_manage_stock')) {
return;
}
$unpaid_orders = wc_foo_cancel_unpaid_onhold_orders( strtotime( '-' . absint( $held_duration) . ' MINUTES'. current_time( 'timestamp')));
if ( $unpaid_orders) {
foreach ( $unpaid_orders as $unpaid_orders ){
$order = wc_get_order( $unpaid_order);
if ( apply_filters( 'woocommerce_cancel_unpaid_order', 'checkout' == $order->get_created_via(), $order ) ) {
$order ->update_status( 'cancelled', _( 'Unpaid order cancelled - time limite reached.', 'woocommerce'));
}
}
}
}
add_action( 'woocommerce_cancel_unpaid_orders', 'wc_foo_cancel_unpaid_onhold_orders');
function wc_foo_get_unpaid_onhold_orders( $date ){
global $wpdb;
$args = array(
'date_modified' => '>' . ( time() - HOUR_IN_SECONDS ),
'status' => 'on-hold',);
$orders = wc_get_orders( $args );
$unpaid_orders = $wpdb->get_col( $wpdb->prepare( "
SELECT posts.id
FROM {$wpdb->posts} AS posts
WHERE posts.posts_type IN ('" . implode( "','", wc_get_order_types()). "')
AND posts.post_status = 'wc-on-hold'
AND posts.date_modified < %s
", date( 'Y-m-d H:i:s', absint( $date)) ) );
}
?>
So I had a little bit of help form #danaharrison at Wordpress.org to achieve this. So the code only applies to 'on-hold' orders.
// To change the amount of days just change '-7 days' to your liking.
function get_unpaid_submitted() {
global $wpdb;
$unpaid_submitted = $wpdb->get_col( $wpdb->prepare( "
SELECT posts.ID
FROM {$wpdb->posts} AS posts
WHERE posts.post_status = 'wc-on-hold'
AND posts.post_date < %s
", date( 'Y-m-d H:i:s', strtotime('-7 days') ) ) );
return $unpaid_submitted;
}
// This excludes check payment type.
function wc_cancel_unpaid_submitted() {
$unpaid_submit = get_unpaid_submitted();
if ( $unpaid_submit ) {
foreach ( $unpaid_submit as $unpaid_order ) {
$order = wc_get_order( $unpaid_order );
$cancel_order = True;
foreach ( $order->get_items() as $item_key => $item_values) {
$manage_stock = get_post_meta( $item_values['variation_id'], '_manage_stock', true );
if ( $manage_stock == "no" ) {
$payment_method = $order->get_payment_method();
if ( $payment_method == "cheque" ) {
$cancel_order = False;
}
}
}
if ( $cancel_order == True ) {
$order -> update_status( 'cancelled', __( 'Pagamento não identificado e cancelado.', 'woocommerce') );
}
}
}
}
add_action( 'woocommerce_cancel_unpaid_submitted', 'wc_cancel_unpaid_submitted' );
/* End of code. */
Related
I would like to add a quanity eg 2 which users are allowed to buy within its timeframe. Also the error notice message is showing on the product page. How would I go about letting the product added to cart and the error message showing on the cart page?
Based on Allow customers to buy only one product from defined WooCommerce product category answer code, here is my code attempt:
// Based partially on wc_customer_bought_product(), will return a boolean value based on orders count (false for O orders and true when there is at least one paid order)
function has_bought( $value = 0 ) {
if ( ! is_user_logged_in() && $value === 0 ) {
return false;
}
global $wpdb;
// Based on user ID (registered users)
if ( is_numeric( $value ) ) {
$meta_key = '_customer_user';
$meta_value = $value == 0 ? (int) get_current_user_id() : (int) $value;
}
// Based on billing email (Guest users)
else {
$meta_key = '_billing_email';
$meta_value = sanitize_email( $value );
}
$paid_order_statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
$count = $wpdb->get_var( $wpdb->prepare("
SELECT COUNT(p.ID) FROM {$wpdb->prefix}posts AS p
INNER JOIN {$wpdb->prefix}postmeta AS pm ON p.ID = pm.post_id
WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $paid_order_statuses ) . "' )
AND p.post_type LIKE 'shop_order'
AND pm.meta_key = '%s'
AND pm.meta_value = %s
AND UNIX_TIMESTAMP(p.post_date) >= (UNIX_TIMESTAMP(NOW()) - (86400 * 1))
LIMIT 1
", $meta_key, $meta_value ) );
// Return a boolean value based on orders count
return $count > 0 ? true : false;
}
function filter_woocommerce_add_to_cart_validation( $passed, $product_id, $quantity, $variation_id = null, $variations = null ) {
// Set categories
$categories = array ( 'kitchen' );
// If passed & has category
if ( $passed && has_term( $categories, 'product_cat', $product_id ) ) {
// Initialize
$value = '';
// User logged in
if ( is_user_logged_in() ) {
// Get the current user's ID
$value = get_current_user_id();
} else {
// Get billing_email
$value = WC()->customer->get_billing_email();
// When empty
if ( empty ( $value ) ) {
// Get account email
$value = WC()->customer->get_email();
}
}
// NOT empty
if ( ! empty ( $value ) ) {
if ( has_bought( $value ) ) {
// Display an error message
wc_add_notice( __( 'My custom error message', 'woocommerce' ), 'error' );
// False
$passed = false;
}
}
}
return $passed;
}
add_filter( 'woocommerce_add_to_cart_validation', 'filter_woocommerce_add_to_cart_validation', 10, 5 );
on my website I will like to display a custom message on the checkout page if a user have previously purchased the product they are about to buy. so I came across this code that actually displays a custom message if a user already bought the product, the code only display on the single product page. please how can I display this message on the checkout page?
add_action( 'woocommerce_before_add_to_cart_form', 'user_logged_in_product_already_bought', 30 );
function user_logged_in_product_already_bought() {
global $product;
if ( ! is_user_logged_in() ) return;
if ( wc_customer_bought_product( '', get_current_user_id(), $product->get_id() ) ) {
echo '<div>You purchased this in the past. Buy again?</div>';
}
}
As #Dmitry suggested you can use the woocommerce_before_checkout_form action hook. but you can't access global $product; on the checkout page. try the below code.
function user_logged_in_product_already_bought() {
global $woocommerce;
if ( ! is_user_logged_in() ) return;
// check if current user in specific role.
$user = wp_get_current_user();
if ( in_array( 'customer', (array) $user->roles ) ) {
}
$items = $woocommerce->cart->get_cart();
$has_bought = false;
foreach($items as $item => $values) {
if ( has_bought_items( get_current_user_id(), $values['data']->get_id() ) ) {
$has_bought = true;
break;
}
}
if( $has_bought ){
wc_print_notice( "You purchased this in the past. Buy again?", 'success' );
}
}
add_action( 'woocommerce_before_checkout_form', 'user_logged_in_product_already_bought' );
Tested and works
As per #7uc1f3r comment, I will like to prevent the double purchase of product per user. you can use the woocommerce_add_to_cart_validation action hook to prevent adding products from the cart.
function prevent_from_adding_cart_if_user_product_already_bought( $passed, $product_id, $quantity ) {
if ( has_bought_items( get_current_user_id(), $product_id ) ) {
wc_add_notice( __( "You purchased this in the past. Buy again?", 'woocommerce' ), 'error' );
$passed = false;
}
return $passed;
}
add_filter( 'woocommerce_add_to_cart_validation', 'prevent_from_adding_cart_if_user_product_already_bought', 10, 3 );
Tested and works
You can use this awesome function made by LoicTheAztec to check if a user has purchased specific products.
function has_bought_items( $user_var = 0, $product_ids = 0 ) {
global $wpdb;
// Based on user ID (registered users)
if ( is_numeric( $user_var) ) {
$meta_key = '_customer_user';
$meta_value = $user_var == 0 ? (int) get_current_user_id() : (int) $user_var;
}
// Based on billing email (Guest users)
else {
$meta_key = '_billing_email';
$meta_value = sanitize_email( $user_var );
}
$paid_statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
$product_ids = is_array( $product_ids ) ? implode(',', $product_ids) : $product_ids;
$line_meta_value = $product_ids != ( 0 || '' ) ? 'AND woim.meta_value IN ('.$product_ids.')' : 'AND woim.meta_value != 0';
// Count the number of products
$count = $wpdb->get_var( "
SELECT COUNT(p.ID) FROM {$wpdb->prefix}posts AS p
INNER JOIN {$wpdb->prefix}postmeta AS pm ON p.ID = pm.post_id
INNER JOIN {$wpdb->prefix}woocommerce_order_items AS woi ON p.ID = woi.order_id
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS woim ON woi.order_item_id = woim.order_item_id
WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $paid_statuses ) . "' )
AND pm.meta_key = '$meta_key'
AND pm.meta_value = '$meta_value'
AND woim.meta_key IN ( '_product_id', '_variation_id' ) $line_meta_value
" );
// Return true if count is higher than 0 (or false)
return $count > 0 ? true : false;
}
UPDATE as per OP request.
You can use the below code for check if the current user is in a specific role.
$user = wp_get_current_user();
if ( in_array( 'customer', (array) $user->roles ) ) {
}
and only products with the status of 'processing in has_bought_items function change the value of the $paid_statuses variable. as per your need.
My goal is to send an email to the customer containing custom text if the order status is on-hold and if the order creation time is 48 hours or more old.
order is 48 hours old or more
send email to customer
ask customer to pay
include a link to the order (to my account payment page)
I'm trying to use the code from an answer to one of my previous questions about Automatically cancel order after X days if no payment in WooCommerce.
I have lightly changes the code:
add_action( 'restrict_manage_posts', 'on_hold_payment_reminder' );
function on_hold_payment_reminder() {
global $pagenow, $post_type;
if( 'shop_order' === $post_type && 'edit.php' === $pagenow
&& get_option( 'unpaid_orders_daily_process' ) < time() ) :
$days_delay = 5;
$one_day = 24 * 60 * 60;
$today = strtotime( date('Y-m-d') );
$unpaid_orders = (array) wc_get_orders( array(
'limit' => -1,
'status' => 'on-hold',
'date_created' => '<' . ( $today - ($days_delay * $one_day) ),
) );
if ( sizeof($unpaid_orders) > 0 ) {
$reminder_text = __("Payment reminder email sent to customer $today.", "woocommerce");
foreach ( $unpaid_orders as $order ) {
// HERE I want the email to be sent instead <=== <=== <=== <=== <===
}
}
update_option( 'unpaid_orders_daily_process', $today + $one_day );
endif;
}
This is the email part that I want to sync with the above (read the code comments):
add_action ('woocommerce_email_order_details', 'on_hold_payment_reminder', 5, 4);
function on_hold_payment_reminder( $order, $sent_to_admin, $plain_text, $email ){
if ( 'customer_on_hold_order' == $email->id ){
$order_id = $order->get_id();
echo "<h2>Do not forget about your order..</h2>
<p>CUSTOM MESSAGE HERE</p>";
}
}
So how can I send an email notification reminder for "on-hold" orders with a custom text?
The following code will be triggered once daily and will send an email reminder with a custom message on unpaid orders (for "on-hold" order status):
add_action( 'restrict_manage_posts', 'on_hold_payment_reminder' );
function on_hold_payment_reminder() {
global $pagenow, $post_type;
if( 'shop_order' === $post_type && 'edit.php' === $pagenow
&& get_option( 'unpaid_orders_reminder_daily_process' ) < time() ) :
$days_delay = 2; // 48 hours
$one_day = 24 * 60 * 60;
$today = strtotime( date('Y-m-d') );
$unpaid_orders = (array) wc_get_orders( array(
'limit' => -1,
'status' => 'on-hold',
'date_created' => '<' . ( $today - ($days_delay * $one_day) ),
) );
if ( sizeof($unpaid_orders) > 0 ) {
$reminder_text = __("Payment reminder email sent to customer $today.", "woocommerce");
foreach ( $unpaid_orders as $order ) {
$order->update_meta_data( '_send_on_hold', true );
$order->update_status( 'reminder', $reminder_text );
$wc_emails = WC()->mailer()->get_emails(); // Get all WC_emails objects instances
$wc_emails['WC_Email_Customer_On_Hold_Order']->trigger( $order->get_id() ); // Send email
}
}
update_option( 'unpaid_orders_reminder_daily_process', $today + $one_day );
endif;
}
add_action ( 'woocommerce_email_order_details', 'on_hold_payment_reminder_notification', 5, 4 );
function on_hold_payment_reminder_notification( $order, $sent_to_admin, $plain_text, $email ){
if ( 'customer_on_hold_order' == $email->id && $order->get_meta('_send_on_hold') ){
$order_id = $order->get_id();
$order_link = wc_get_page_permalink('myaccount').'view-order/'.$order_id.'/';
$order_number = $order->get_order_number();
echo '<h2>'.__("Do not forget about your order.").'</h2>
<p>'.sprintf( __("CUSTOM MESSAGE HERE… %s"),
'<a href="'.$order_link.'">'.__("Your My account order #").$order_number.'<a>'
) .'</p>';
$order->delete_meta_data('_send_on_hold');
$order->save();
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
I want to get daily order count of daily whenever an order is posted. I wrote the below code to give a notification on slack whenever there is an order. As a result of displaying $result it showing the current order amount rather than the total order count of the day.
function wp_slack_woocommerce_order_status_completed2( $events ) {
$events['woocommerce_order_status_processing'] = array(
// Action in WooCommerce to hook in to get the message.
'action' => 'woocommerce_order_status_processing',
// Description appears in integration setting.
'description' => __( 'Whenever we receive an order', 'slack-woocommerce' ),
// Message to deliver to channel. Returns false will prevent
// notification delivery.
'message' => function( $order_id ) {
$order = wc_get_order( $order_id );
$date = is_callable( array( $order, 'get_date_completed' ) )
? $order->get_date_completed()
: $order->completed_date;
$url = add_query_arg(
array(
'post' => $order_id,
'action' => 'edit',
),
admin_url( 'post.php' )
);
$user_id = is_callable( array( $order, 'get_user_id' ) )
? $order->get_user_id()
: $order->user_id;
if ( $user_id ) {
$user_info = get_userdata( $user_id );
}
if ( ! empty( $user_info ) ) {
if ( $user_info->first_name || $user_info->last_name ) {
$username = esc_html( ucfirst( $user_info->first_name ) . ' ' . ucfirst( $user_info->last_name ) );
} else {
$username = esc_html( ucfirst( $user_info->display_name ) );
}
} else {
$billing_first_name = is_callable( array( $order, 'get_billing_first_name' ) )
? $order->get_billing_first_name()
: $order->billing_first_name;
$billing_last_name = is_callable( array( $order, 'get_billing_last_name' ) )
? $order->get_billing_last_name()
: $order->billing_last_name;
if ( $billing_first_name || $billing_last_name ) {
$username = trim( $billing_first_name . ' ' . $billing_last_name );
} else {
$username = __( 'Guest', 'slack-woocommerce' );
}
}
global $wpdb;
$date_from = '2018-02-27';
$date_to = '2018-02-28';
$post_status = implode("','", array('wc-processing', 'wc-completed') );
$result = $wpdb->get_results( "SELECT count(*) as total FROM $wpdb->posts
WHERE post_type = 'shop_order'
AND post_status IN ('{$post_status}')
AND post_date BETWEEN '{$date_from} 00:00:00' AND '{$date_to} 23:59:59'
");
// Remove HTML tags generated by WooCommerce.
add_filter( 'woocommerce_get_formatted_order_total', 'wp_strip_all_tags', 10, 1 );
$total = html_entity_decode( $order->get_formatted_order_total() );
remove_filter( 'woocommerce_get_formatted_order_total', 'wp_strip_all_tags', 10 );
//$data2=WC_API_Reports::get_sales_report( );
// Returns the message to be delivered to Slack.
return apply_filters( 'slack_woocommerce_order_status_completed_message',
sprintf(
__( 'Reveived new order with amount *%1$s*. Made by *%2$s* on *%3$s*. <%4$s|See detail> %s', 'slack-woocommerce' ),
$total,
$username,
$date,
$url,
$resullt
),
$order
);
},
);
return $events;
}
add_filter( 'slack_get_events', 'wp_slack_woocommerce_order_status_completed2' );
To get the daily order count you can use this custom function that will return the order count for the current day or the order count for a specific defined date:
function get_daily_orders_count( $date = 'now' ){
if( $date == 'now' ){
$date = date("Y-m-d");
$date_string = "> '$date'";
} else {
$date = date("Y-m-d", strtotime( $date ));
$date2 = date("Y-m-d", strtotime( $date ) + 86400 );
$date_string = "BETWEEN '$date' AND '$date2'";
}
global $wpdb;
$result = $wpdb->get_var( "
SELECT DISTINCT count(p.ID) FROM {$wpdb->prefix}posts as p
WHERE p.post_type = 'shop_order' AND p.post_date $date_string
AND p.post_status IN ('wc-on-hold','wc-processing','wc-completed')
" );
return $result;
}
Code goes in function.php file of your active child theme (or theme). Tested and works.
USAGE:
1) To get the orders count for the current date:
$orders_count = get_daily_orders_count();
2) To get the orders count for a specific date (with a format like 2018-02-28):
// Get orders count for february 25th 2018 (for example)
$orders_count = get_daily_orders_count('2018-02-25');
I'm working with Wordpress, Woocommerce & Eventon plugin in order to create a selling tickets for events site. Users can order tickets for different dates.
I'm trying to implement a sortable column wich shows for every ticket sold the date for the event (stored in woocommerce_order_itemmeta table as meta_value of meta_key 'Event-Time'). So first of all I created and populated that column like this:
add_filter( 'manage_edit-shop_order_columns', 'wooc_set_custom_column_order_columns');
//create custom woocommerce columns
function wooc_set_custom_column_order_columns($columns) {#
$nieuwearray = array();
foreach($columns as $key => $title) {
if ($key=='billing_address') { // in front of the Billing column
$nieuwearray['order_product'] = __( 'Products', 'woocommerce' );
$nieuwearray['Event-Time'] = __( 'Reservation Date', 'woocommerce' );
}
$nieuwearray[$key] = $title;
}
return $nieuwearray ;
}
//populate custom woocommerce columns
add_action( 'manage_shop_order_posts_custom_column' , 'custom_shop_order_column', 10, 2 );
function custom_shop_order_column( $column ) {
global $post, $woocommerce, $the_order;
switch ( $column ) {
case 'order_product' :
$terms = $the_order->get_items();
if ( is_array( $terms ) ) {
foreach($terms as $term)
{
echo $term['item_meta']['_qty'][0] .' x ' . $term['name'] .'<br />';
}
} else {
_e( 'Unable get the product', 'woocommerce' );
}
break;
case 'Event-Time' :
$terms = $the_order->get_items();
if ( is_array( $terms ) ) {
foreach($terms as $term) {
if(array_key_exists( 'Event-Time', $term ) ){
$date_event = substr($term['item_meta']['Event-Time'][0], 0, 10);
echo $date_event . '<br />';
} else {
echo '<b>' . __('No existe fecha reserva?', 'woocommerce') . '</b>';
}
}
} else {
_e( 'Unable get the product', 'woocommerce' );
}
break;
}
}
In order to make Event-Time column sortable, I added this code:
add_filter( "manage_edit-shop_order_sortable_columns", 'sort_columns_woocommerce' );
function sort_columns_woocommerce( $columns ) {
$custom = array(
//'order_producten' => 'MY_COLUMN_1_POST_META_ID',
'Event-Time' => 'Event-Time'
);
return wp_parse_args( $custom, $columns );
}
but nothing happened, so after a few hours looking it up on Google, I found similar solution that I tried to adapt to my case:
function order_by_event_time_join($query) {
global $wpdb;
if ( is_admin() && (isset($_GET['post_type']) && $_GET['post_type'] === 'shop_order') && (isset($_GET['orderby']) && $_GET['orderby'] === 'Event-Time') ) {
$first_item_in_order = "SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = $wpdb->posts.ID AND order_item_type = 'line_item' LIMIT 0, 1";
$query .= "LEFT JOIN {$wpdb->prefix}woocommerce_order_items AS items ON $wpdb->posts.ID = items.order_id AND items.order_item_id = ($first_item_in_order) ";
$query .= "LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS itemmeta ON items.order_item_id = itemmeta.order_item_id AND (itemmeta.meta_key = 'Event-Time') ";
}
return $query;
}
add_filter('posts_join', 'order_by_event_time_join');
function order_by_event_time_where($where) {
global $wpdb;
if( is_admin() && $_GET['post_type'] === 'shop_order' && (isset($_GET['orderby']) && $_GET['orderby'] === 'Event-Time') ) {
if(strpos($where, 'shop_webhook')) {
return " AND $wpdb->posts.post_type = 'shop_order' AND itemmeta.meta_key = 'Event-Time'";
}
}
return $where;
}
add_filter('posts_where', 'order_by_event_time_where');
But no luck... What I understand the problem is I'm not able to sort columns by the meta_value 'Event-Time' in the ticket-orders section of woocommerce...
I can show Event-Time data but impossible to sort/filter it.