WooCommerce Subscriptions: change renewal dates after checkout - php

I have 1 subscription with 3 variations.
I'm trying to force the first renewal date depending on the variation ID that was purchased.
Variation 1 (876) set to renew every day - I want first re-bill date to be 11/15/2020 12:00AM
Variation 2 (877) set to renew every 2 days - I want first re-bill date to be 2/15/2021 12:00AM
Variation 3 (878) set to renew every 3 days - I want first re-bill date to be 8/15/2021 12:00AM
I thought the below code worked. After the order is created the next bill date DOES show one of the above dates, but it must not be registering with WooCommerce or something, because the renewals are triggering regardless of the above dates.
For my test, I create an order for Variation 1, the next payment date is showing 11/15/2020, but it's renewing the next day.
Hoping to get some insight from someone smarter than me. Again, the sub's next bill date is showing the above dates, but renewals are still happening early/before the above dates.
function nextpaymentdatechange( $order_id ){
$order = wc_get_order( $order_id );
$items = $order->get_items();
foreach ( $items as $item_id => $item ) {
$product_id = $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id();
if ( $product_id === 876 ) {
$subid = $order_id + 1;
$nextdate = get_post_meta( $subid, '_schedule_next_payment', true );
$new_date = date( 'Y-m-d H:i:s', strtotime( '2020-11-15 07:00:00', strtotime( $nextdate )) );
update_post_meta( $subid , '_schedule_next_payment', $new_date);
}
else{ if ( $product_id === 877 ) {
$subid = $order_id + 1;
$nextdate = get_post_meta( $subid, '_schedule_next_payment', true );
$new_date = date( 'Y-m-d H:i:s', strtotime( '2021-02-15 07:00:00', strtotime( $nextdate )) );
update_post_meta( $subid , '_schedule_next_payment', $new_date);
}
else{
if ( $product_id === 878 ) {
$subid = $order_id + 1;
$nextdate = get_post_meta( $subid, '_schedule_next_payment', true );
$new_date = date( 'Y-m-d H:i:s', strtotime( '2021-08-03 07:00:00', strtotime( $nextdate )) );
update_post_meta( $subid , '_schedule_next_payment', $new_date);
}
}
}
}
} ```

To make that work you need first to get WC_Subscription Objects from the order using:
$subscriptions = wcs_get_subscriptions_for_order( $order_id );
that gives an array of the WC_Subscription Object(s) for the Order, from an order Id.
Now you can use the WC_Subscription method update_dates($dates), that requires to set an array of all dates types from a subscription that are 'start', 'trial_end', 'next_payment', 'last_payment' and 'end'.
To get a specific date from a subscription, we use WC_Subscription method get_date($date_type).
Additionally I use WC_Subscription method can_date_be_updated($date_type), to check if a date type can be updated.
I am not sure about which ones dates require to be updated as they are likely related.
You can try the following that should update the related(s) subscription(s) dates from an order, based on your code (the code doesn't throw errors).
I thin that you need to change 'next_payment', 'last_payment' and 'end' dates.
Using the methods update_dates() and save() at the end of the code, allows to change dates, save all required data to database refreshing cached data.
The function code:
function nextpaymentdatechange( $order_id ){
// YOUR SETTINGS: Set in this array your desired dates (value(s)) by product Id (key)
$dates_for_product = array(
876 => array(
'next_payment' => '2020-11-15 07:00:00',
'last_payment' => '2020-11-16 07:00:00',
),
877 => array(
'next_payment' => '2021-02-15 07:00:00',
'last_payment' => '2021-02-16 07:00:00',
),
878 => array(
'next_payment' => '2021-08-03 07:00:00',
'last_payment' => '2021-08-04 07:00:00',
),
);
// The date types for subscriptions
$suscription_date_types = array('start', 'trial_end', 'next_payment', 'last_payment', 'end');
// Get the subscriptions from the current order Id
$subscriptions = wcs_get_subscriptions_for_order( $order_id );
// Loop through subscriptions
foreach ( $subscriptions as $subscription_id => $subscription ) {
// Loop through items in this subscription
foreach ( $subscription->get_items() as $item_id => $item ) {
$product = $item->get_product();
// Loop through defined products dates
foreach( $dates_for_product as $product_id => $new_dates ) {
// If current subscription product id matches
if ( $product->get_id() == $product_id ) {
$current_product_id = $product_id; // Set current product id
break; // Stop the current loop
}
}
if ( isset($current_product_id) ) {
break; // Stop the current loop
}
}
// Updating subscription dates
if ( isset($current_product_id) ) {
$updated_dates = array(); // Initializing
// Loop through subscription date types
foreach( $suscription_date_types as $date_type ) {
$date = $subscription->get_date($date_type);
// For 'next_payment' and 'last_payment' dates
if( isset($new_dates[$date_type]) && $subscription->can_date_be_updated($date_type) ) {
$updated_dates[$date_type] = $new_dates[$date_type];
}
// For 'end' date
elseif ( $date_type === 'end' && $subscription->can_date_be_updated($date_type) ) {
$updated_dates[$date_type] = $new_dates['last_payment']; // ??? Or may be instead: $date; … (or simply: 0;)
}
// Other dates
else {
$updated_dates[$date_type] = $date;
}
}
// Update subscription date, save to database and refresh cached data
$subscription->update_dates($updated_dates);
$subscription->save();
}
}
}
Code goes in functions.php file of your active child theme (or active theme). It should works.

if i understand, this :
function nextpaymentdatechange( $order_id ){
// YOUR SETTINGS: Set in this array your desired dates (value(s)) by product Id (key)
$dates_for_product = array(
588 => array(
'end' => '2020-12-05 07:00:00',
),
);
// The date types for subscriptions
$suscription_date_types = array('start', 'trial_end', 'next_payment', 'last_payment', 'end');
Set automaticaly a end date 05 of december for my product 588 (it's a simple subscription)?

Related

PHP: determination of the date minus weekends and holidays

There is a WordPress and ACF field with a date in the format 2022-04-30. From this date need to calculate 2 other dates, -2 days and +25 days. The problem is that it is necessary to take into account only working days, i.e. check weekends and holidays.
For example, we set the date May 3, 2022, which is Tuesday. From this date, I need to subtract 2 days, i.e. May 1, 2022, but this is Sunday, so we have to return to the first working day before May 1, i.e. Friday April 29, 2022. It's the same with holidays.
At the moment I have this code:
$setDate = get_field('set_date'); // ACF field, set May 3, 2022 (2022-05-03)
$offDate = wp_date('j F Y', strtotime('-2 days', strtotime($setDate)));
echo $offDate; // returns Sunday, May 1, 2022
I found holidays and weekends in json https://github.com/d10xa/holidays-calendar/blob/master/json/consultant2022.json
So I need to compare the given date with the dates from json and if there is a match, then minus one day and check the received date again. If there is a match, again minus one day and so on until no matches are found. Am I thinking correctly and can you tell me how to implement it? I am a very bad programmer, but there is a task)
At the moment I was only able to compare the dates and return the result found/not found. But I can't figure out how to take days off on the basis of this and send it for verification again(
$setDate = '2022-05-01';
$file = file_get_contents('https://raw.githubusercontent.com/d10xa/holidays-calendar/master/json/consultant2022.json', true);
$data = json_decode($file);
$found = array_search($setDate, $data->holidays);
if ($found === False) {
echo 'Not Found';
} else {
echo 'found';
}
The following has been tested on a few dates and I think it works as it should.
/*
JSON file is saved locally to a sub-directory
for the current working script directory.
This is to avoid unneccessary hits to the
remote site.
*/
$format='Y-m-d';
$url='https://raw.githubusercontent.com/d10xa/holidays-calendar/master/json/consultant2022.json';
$setDate = '2022-05-01';
$filepath=sprintf('%s/json/%s', __DIR__, basename( $url ) );
if( !file_exists( $filepath ) ){
file_put_contents( $filepath, file_get_contents( $url ) );
}
# read the file and generate JSON
$json=json_decode( file_get_contents( $filepath ) );
$hols=$json->holidays;
# create the initial DateTime object and find which weekday we are dealing with
# where 1 (for Monday) through 7 (for Sunday)
$obj=new DateTime( $setDate );
$date=$obj->format( $format );
$day=$obj->format('N');
# Is the given date a holiday/weekend?
if( array_search( $date, $hols ) ){
if( $day > 5 ){
# Date is either Sat or Sun... go back to previous working day
$subtract = 2 - ( 7 - $day );
$int=new DateInterval( sprintf('P%sD', $subtract ) );
$obj=new DateTime( $setDate );
$previous=$obj->sub( $int );
}else{
$previous=$obj->sub( new DateInterval('P2D') );
}
# create the future date ( add 25 days )
$int=new DateInterval('P25D');
$obj=new DateTime( $setDate );
$future=$obj->add( $int );
if( array_search( $future->format( $format ), $hols ) ){
# Future date is a holiday... go back to previous working day
$day=$future->format('N');
$subtract = 2 - ( 7 - $day );
$int=new DateInterval( sprintf('P%sD',$subtract ) );
$future=$future->sub( $int );
}
}else{
# Given date is NOT a holiday...
# take a copy of the original DateTime object for generating future date.
$ref=new DateTime( $setDate );
$int=new DateInterval( 'P2D' );
$previous=$obj->sub( $int );
$day=$previous->format('N');
# Is this a holiday?
if( $day > 5 ){
# yes - go back to previous working day
$subtract = 2 - ( 7 - $day );
$int=new DateInterval( sprintf('P%sD',$subtract ) );
$previous=$previous->sub( $int );
}
$int=new DateInterval('P25D');
$future=$ref->add( $int );
$day=$future->format('N');
# Is this a holiday?
if( $day > 5 ){
$subtract = 2 - ( 7 - $day );
$int=new DateInterval( sprintf('P%sD',$subtract ) );
$future=$future->sub( $int );
}
}
printf(
'<pre>
Given date: %s
Previous (-2): %s
Future (+25): %s
</pre>',
$date,
$previous->format( $format ),
$future->format( $format )
);
Which yields:
Given date: 2022-05-01
Previous (-2): 2022-04-29
Future (+25): 2022-05-26

WooCommerce Conditional Delivery Notice based on weekdays and time

Based on Conditional Delivery Notice based on Time and Date in Woocommerce answer code, I'm trying to display a shipping message with the following lightly modified code:
I try this code without finding the result I hope :
add_action( 'woocommerce_before_customer_login_form', 'next_day_delivery' );
add_action( 'woocommerce_before_customer_login_form', 'next_day_delivery' );
add_action( 'woocommerce_before_checkout_form', 'next_day_delivery' );
add_action( 'woocommerce_before_shop_loop', 'next_day_delivery' );
add_action( 'woocommerce_before_single_product_summary', 'next_day_delivery' );
add_action( 'woocommerce_before_cart', 'next_day_delivery' );
function next_day_delivery() {
if( WC()->cart->is_empty() )
return; // Exit
// Set the time zone
date_default_timezone_set('Europe/Paris');
// From Monday to Thursday
$is_week_days = in_array( date('w'), array( 3, 4, 5, 6, 0 ) ) ? true : false;
$is_monday = date('w') == 1 ? true : false; // Monday
$is_tuesday = date('w') == 2 ? true : false; // Tuesday
$end_time = mktime('21', '00', '00', date('m'), date('d'), date('Y'));
$now_time = time();
$after_tomorow = date('l', strtotime('+2 days'));
$dateDiff = intval(($end_time - $now_time)/60);
$diff_hours = intval($dateDiff/60);
$diff_minutes = $dateDiff%60;
$hours_label = _n( 'heure', 'heures', $diff_hours, 'wooocommerce' );
$minutes_label = _n( 'minute', 'minutes', $diff_minutes, 'wooocommerce' );
if ( $is_monday && $now_time or $is_tuesday && $now_time < $end_time ) {
// print the information notice
$message = sprintf( __( '%s left to be delivered tomorrow!', 'woocommerce' ),
$diff_hours.' '.$hours_label.' and '.$diff_minutes.' '.$minutes_label);
}
elseif ( $end_time <= $now_time && $is_week_days ) {
$message = sprintf( __( 'Your order will be delivered this %s.', 'woocommerce' ), $after_tomorow );
} else {
$message = __( 'Your order will be prepared and shipped next upcoming monday and delivered on tuesday.', 'woocommerce' );
}
wc_print_notice( $message, 'success' );
}
But I cannot make it work for my needs…
We deliver our product each thursday, friday and saturday in different local pickups. So I'd like to have the delivery message that uses the following logic :
the orders made each week before tuesday 9pm (21h00 in TimeZone Europe/Paris), it should say "%s left to be delivered from thursday !"
the orders made each week after tuesday 9pm (21h00 in TimeZone Europe/Paris), it should say "Your order will be prepared and shipped from next upcoming wednesday and delivered from next thursday."
Any help will be very much appreciated.

How to limit the number of orders per customer via a time limit in WooCommerce

I want to prevent selling each 24 hour by a customer.
check if there are other purchases from that costumer at the past 24hr and display an error before payment and ask to return later
What I have tried so far
function prevent_repeat_order() {
$last_24_hours_from_order_results = wc_get_customer_last_order($user_id);
(array( 'date_created' => '>=' . (time() - 86400), // time in seconds 'paginate' => true // adds a total field to the results ));
if ( $last_24_hours_from_last_order->total > 1 ) {
wc_add_notice('Too many orders in the last 24 hours. Please return later.', 'error');
}
}
add_action('woocommerce_checkout_process', 'prevent_repeat_order', 10, 0);
Of course it depends on where you want to show the message. The following code shows a error message on the checkout page, and hides the proceed_to_checkout button if the condition is not met.
With https://www.php.net/manual/en/function.date.php
the seconds can be converted to hours, etc...
function new_order_allowed() {
// Only on cart and check out pages
if( ! ( is_cart() || is_checkout() ) ) return;
// Will only work for logged in users
if ( is_user_logged_in() ) {
// Get user id
$user_id = get_current_user_id();
// Get last order
$last_order = wc_get_customer_last_order( $user_id );
if ( $last_order ) {
// Get date last order created - Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
$date_created = $last_order->get_date_created()->format( 'U' );
// Get current date - Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
$current_time = current_time( 'U', true );
// 24hr in seconds
$day_in_sec = 60 * 60 * 24;
// Seconds have passed since the last order
$seconds_passed = $current_time - $date_created;
// Check
if ( $seconds_passed < $day_in_sec ) {
// Add notice
wc_add_notice( sprintf( 'New orders are only allowed %1$s seconds after your previous order, currently %2$s seconds are passed', $day_in_sec, $seconds_passed ), 'error' );
// Removing the Proceed to checkout button from the Cart page
remove_action( 'woocommerce_proceed_to_checkout','woocommerce_button_proceed_to_checkout', 20);
}
}
}
}
add_action( 'woocommerce_check_cart_items', 'new_order_allowed' );

Echo Woocommerce order date (plus dispatch days) in admin order list

I am trying to add order date + a number of working days (not in current code) in the admin order list. But I can´t get it to work with order date. It works with $order_item['quantity'].
The current code:
add_action ( 'manage_shop_order_posts_custom_column', 'dispatch');
function dispatch( $colname ) {
global $the_order; // the global order object
if( $colname == 'dispatch' ) {
// get items from the order global object
$order_items = $the_order->get_items();
if ( !is_wp_error( $order_items ) ) {
foreach( $order_items as $order_item ) {
echo $order_item['get_date_created'];
}
}
}
}
Got it figured out. Since I'm not a programmer I have no idea if this is a good solution.
// start dispatch
if( $colname == 'dispatch' ) {
// get items from the order global object
$order_itemss = $the_order->get_items();
// The orders date
$order_date = $the_order->order_date;
// The order date + 8-10 days
$order_date_8d = date_i18n( 'D j M', strtotime( $order_date ) + ( 8 * 24 * 60 * 60 ) );
$order_date_10d = date_i18n( 'D j M', strtotime( $order_date ) + ( 10 * 24 * 60 * 60 ) );
if ( !is_wp_error( $order_itemss ) ) {
foreach( $order_itemss as $order_itemm ) {
echo $order_date_8d .' - '. $order_date_10d;
}
// end foreach
}
// end if
}
// end dispatch

Wordpress | Apply function at specific times of the day

I have this function to hide a woocommerce category based on category slug. See here:
/* Exclude Category from Shop*/
add_filter( 'get_terms', 'get_subcategory_terms', 10, 3 );
function get_subcategory_terms( $terms, $taxonomies, $args ) {
$new_terms = array();
// if a product category and on the shop page
if ( in_array( 'product_cat', $taxonomies ) && ! is_admin() && is_shop() ) {
foreach ( $terms as $key => $term ) {
if ( ! in_array( $term->slug, array( 'suviche' ) ) ) {
$new_terms[] = $term;
}
}
$terms = $new_terms;
}
return $terms;
}
And I have this other code to Apply Rules If Time is within the frame marked ( 9am - 5pm )
<?php
$hr = date("H"); //get the hour in terms of double digits
$min= date("i"); //get the minutes in terms of double digits
$t = ($hr*60)+$min; //convert the current time into minutes
$f = (60*9); //calculate 9:00AM in minutes
$s = (60*17); //calculate 5:00PM in minutes
if(($t>f || $t<s)) //if the current time is between 9:00am to 5:00pm then don't apply function
{
//DO NOTHIGN
}
else //otherwise show execute function
{
//EXECUTE FUNCTION
}
?>
What I want to do is to run the filter to hide the product category if is out of the time fram ( 9am - 5pm )
Any Ideas would be great!
So far I have this but nothing:
/* Exclude Category from Shop*/
add_filter( 'get_terms', 'get_subcategory_terms', 10, 3 );
function get_subcategory_terms( $terms, $taxonomies, $args ) {
$new_terms = array();
$hr = date("H"); //get the hour in terms of double digits
$min= date("i"); //get the minutes in terms of double digits
$t = ($hr*60)+$min; //convert the current time into minutes
$f = (60*9); //calculate 9:00AM in minutes
$s = (60*17); //calculate 5:00PM in minutes
// if a product category and on the shop page
if ( ( $t>f || $t<s) && in_array( 'product_cat', $taxonomies ) && ! is_admin() ) {
foreach ( $terms as $key => $term ) {
if ( ! in_array( $term->slug, array( 'suviche' ) ) ) {
$new_terms[] = $term;
}
}
$terms = $new_terms;
}
return $terms;
}
Thanks again to whoever might help!
Hi the problem is you're not getting 9am for the day. You are just getting 9am for 1970, January 1st. Also, I wouldn't suggest working with minutes. Work with seconds.
Whenever you work with time in PHP, know that it is often beneficial to use a unix timestamp. A unix timestamp is a running count of the number of seconds that have elapsed since 1st January, 1970.
A handy converter can be found here
Try this instead.
$curTime = strtotime("now"); //get the current time
$finishTime = strtotime('9am '.date('d-m-Y')); //calculate 9:00AM in seconds
$startTime = strtotime('5pm '.date('d-m-Y')); //calculate 5:00PM in seconds
if(!($curTime > $startTime && $curTime < $finishTime)){
//If we get into here then we're outside of the time range.
}
What we're doing here is using strtotime("now") to get the current time, and date() to get todays date, which is being concatenated with the time you want(9am and 5am). We're then using strtotime, to convert the whole thing into a unix timestamp. Which you can then compare later to see if the current time is greater or lesser than.
Also, these variables need $ in front of them.
( $t>$f || $t<$s )
Hope this helps.

Categories