I am in need of some help temporarily removing the cancel button in the "My Subscription" page within "My Account". I would like to hide the cancel button until it has been at least 3 months (or 90days) since the user has subscribed. Where after they have been subscribed for 3 months, the cancel button will appear again.
Using: Woocommerce together with Woo-subscriptions and Woo-memberships
I have found another question where this has been answered, but I cannot seem to get the code to work no matter how much I edit it (Disable Cancel Subscription for single subscription in WooCommerce). The first snippet of code below is from the link.
function sv_edit_my_memberships_actions( $actions ) {
// Get the current active user
$user_id = wp_get_current_user();
if(!$user_id) // No valid user, abort
return $actions;
// Only query active subscriptions
$memberships_info = wc_memberships_get_user_active_memberships($user_id, array(
'status' => array( 'active' ),
));
// Loop through each active subscription
foreach ($memberships_info as $membership) {
$subscription_start_date = date("Y/m/d", strtotime($membership->get_start_date()));
//$subscription_end_date = date("Y/m/d", strtotime($membership->get_end_date()));
//$subscription_name = $membership->get_plan()->get_name();
//$subscription_id = $membership->get_plan()->get_id();
if($subscription_id == 'YOUR_ID') { // Active subscription
// Compare the starting date of the subscription with the current date
$datetime1 = date_create($subscription_start_date);
$datetime2 = date_create(date(time()));
$interval = date_diff($datetime1, $datetime2);
if($interval->format('%m') <= 11) {
// remove the "Cancel" action for members
unset( $actions['cancel'] );
}
}
}
return $actions;
}
I have been able to hide the cancel button with my code below, however it hides it indefinitely:
function remove_cancel_button( $actions, $subscription ) {
foreach ( $actions as $action_key => $action ) {
switch ( $action_key ) {
case 'cancel': // Remove the cancel button
unset( $actions[ $action_key ] );
break;
default:
error_log( '-- $action = ' . print_r( $action, true ) );
break;
}
}
return $actions;
}
add_filter( 'wcs_view_subscription_actions', 'remove_cancel_button', 100, 2);
I've read the related developer documentation found here and altered your code so that it uses the current sites date and compares it to the subscription date.
If there is less then a 3 months difference the cancel button will stay hidden until there is a at least a 3 month difference.
Note that I used 'last_payment' date to compare with, other possible options to use are 'start', 'trial_end', 'next_payment', 'last_payment' or 'end'.
Read more about it here.
/**
* Remove cancel button ( When last payment was less then 3 months ago )
*
* #param array $actions, action array.
* #param int $subscription_id, the id of the current subscription.
* #return array $actions, the altered action array.
*/
function remove_cancel_button( $actions, $subscription_id ) {
// Gets the subscription object on subscription id
$subscription = new WC_Subscription( $subscription_id );
// Get last payment date from subscription object, uses the sites timezone setting
$last_payment_date = $subscription->get_date( 'last_payment', 'site' );
$last_payment_date = new DateTime( $last_payment_date );
// The current date/time, uses the sites timezone setting
$today = new DateTime( current_time('mysql') );
// Get the difference in date
$interval = $today->diff( $last_payment_date );
// Check if interval is less then 3 months
if( $interval->m < 3 ){
unset( $actions['cancel'] );
}
// Return the actions
return $actions;
}
add_filter( 'wcs_view_subscription_actions', 'remove_cancel_button', 100, 2);
Hopefully this helps you out, let me know if anything is unclear.
Related
Here is my custom ajax request callback.
I use some data with wp_remote_post and getting json results about my own woocommerce payment gateway installments.
/**
* Check avaliblity for installments via WordPress Ajax
*
* #return void
*/
function check_installment() {
if ( isset($_REQUEST) ) {
$action = data_get($_REQUEST, 'action');
if($action == 'check_installment')
{
$cart_data = WC()->session->get('cart_totals');
$binNumber = data_get($_REQUEST, 'bin');
if(!$binNumber)
{
return;
}
$_initGateway = new Woo_Ipara_Gateway();
$_initGateway = $_initGateway->checkInstallment($binNumber);
$data = [
'cardFamilyName' => data_get($_initGateway, 'cardFamilyName'),
'supportedInstallments' => data_get($_initGateway, 'supportedInstallments')
];
echo json_encode(getInstallmentComissions(data_get($_initGateway, 'cardFamilyName')));
}
}
die();
}
add_action( 'wp_ajax_check_installment', 'check_installment');
add_action( 'wp_ajax_nopriv_check_installment', 'check_installment');
Right now, payment provider, have different comissions for spesific credit card. So it means, i want to change order total after this request, when user's selected installment value.
I also found some filter, about calculated total woocommerce_calculated_total, but how to trigger this, after ajax request and user, selected installment choice?
add_filter( 'woocommerce_calculated_total', 'custom_calculated_total', 10, 2 );
function custom_calculated_total( $total, $cart ){
// some magic.
}
Any helps ? Thanks.
First of all, filter method was wrong, what i wanted to try woocommerce_calculated_total.
It must be, woocommerce_checkout_order_processed
Other issue is, WC()->cart->add_fee( "text", $fee, false, '' ); not really work correctly.
You should use, new WC_Order_Item_Fee() class in your action directly.
Here is the some part of code in my case ;
add_action('woocommerce_checkout_order_processed', 'addFeesBeforePayment', 10, 3);
function addFeesBeforePayment( $order_id, $posted_data, $order ) {
// some logic about bin verification and remote fetch about installment comissions.
$cartTotal = WC()->cart->cart_contents_total;
$newCartTotal = $cartTotal + ( $cartTotal * $defaultComission / 100 );
$installmentFee = $cartTotal * ($defaultComission / 100 );
$item_fee = new WC_Order_Item_Fee();
$item_fee->set_name( 'Kredi Kartı Komisyonu ('.$defaultInstallment.' Taksit)' ); // Generic fee name
$item_fee->set_amount( $installmentFee ); // Fee amount
$item_fee->set_tax_class( '' ); // default for ''
$item_fee->set_tax_status( 'none' ); // or 'none'
$item_fee->set_total( $installmentFee ); // Fee amount
$order->add_item( $item_fee );
$order->calculate_totals();
$order->add_order_note( 'Bu sipariş için ödeme '. $defaultInstallment . ' taksit seçeneği seçilerek oluşturuldu. Toplam taksit komisyonu, sipariş tutarına ek olarak ' . $installmentFee. ' TL karşılığında eklendi.' );
}
Happy coding.
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.
Trying to create new fields for my form dynamically, as I'm getting json from 3rd party API. Based on this json, I need a number of fields added to my form - not fixed number. So, I'm doing this, hooking it onto gform_pre_render:
add_filter( 'gform_pre_process', 'create_products_gforms' );
add_filter( 'gform_admin_pre_render', 'create_products_gforms' );
add_filter( 'gform_pre_render', 'create_products_gforms' );
function create_products_gforms( $form ) {
$helper = new NSHelper();
$state_name = $_POST['input_7'] ?? '';
$code_value = $helper->get_state_code_by_name( $state_name );
// Only fetch products if current form id is 33, state code is defined
// and if there are products for this state.
if ( $form['id'] != 33 || !$code_value ) {
return $form;
}
// Get product list from NetSuit API based on state code
$products_json_data = get_products_data( $code_value );
$products = json_decode( json_decode( $products_json_data ) );
$new_field_id = 0;
foreach( $form['fields'] as $field ) {
if( $field->id > $new_field_id ) {
$new_field_id = $field->id;
}
}
$new_field_id++;
foreach ( $products as $product_object ) {
// Prepare field properties
$props = array(
'id' => $new_field_id,
'type' => 'singleproduct',
'label' => $product_object->onlinedisplayname,
'basePrice' => floatval( $product_object->price ),
'enableCalculation' => true
);
// Create new gravity forms field and add it to the form object
$nf = GF_Fields::create( $props );
// Hack - insert into array at specific position
// Needed to display product fields before other fields
// in the form
array_splice( $form['fields'], 11, 0, array($nf) );
$new_field_id++;
}
GFAPI::update_form( $form );
$form['dynamic_fields_ids'] = $added_fields_ids;
return $form;
}
This works, ie, it shows fields properly on the frontend. Now, the issue with this is that, once form is submitted, all the fields except these dynamically added ones are in the submission. But these are not. I assumed this has to do that these fields aren't registered in the form, so I also tried GFAPI::update_form( $form );, but that didn't help with submission part, though it udpates my form with new fields in the backend too.
Any ideas?
Based on your use-case, Milos, I would suggest using the gform_form_post_get_meta filter:
https://docs.gravityforms.com/gform_form_post_get_meta/
This will fire every time the form is fetched from the database and the most sure-fire way of guaranteeing your fields will be present.
If you prefer to be more surgical and stick with the gform_pre_render approach, you'll want to apply the same function on a couple other filters:
gform_pre_process
gform_admin_pre_render
Simple Abandoned Cart
https://github.com/gregbast1994/msp-abandoned-cart
This plugin simply skims emails at checkout and saves the cart session data. Then we set a cron job to check if the session became an order, if not set another for tomorrow to email the user. Simple abandoned cart functionality.
I've checked the list of cron jobs and can see the 'await_order_for_cart_..' job. I've also dumped the $wp_filter to check if I can find my function connected to a hook anywhere. I've tried making the functions attached to the job static.
The job dumped looks like
[1566572607] => Array
(
[await_order_for_cart_04ff0471d799d19b53034ef078a600d8] => Array
(
[e0f32ed5d8b7b8d89f1d8f832df788f2] => Array
(
[schedule] =>
[args] => Array
(
[hash] => 04ff0471d799d19b53034ef078a600d8
[item_count] => 1
[customer_id] => aeac13344550563f2be4186690b44ef5
)
)
)
)
First setup the first job to check if the session becomes an order.
public function get_data( $email = '' )
{
...
$this->set_wait_for_order_cron();
}
public function set_wait_for_order_cron()
/**
* Creates a unique hook using cart hash, schedules a check on cart
*/
{
if( empty( $this->cart['hash'] ) ) return;
$hook = 'await_order_for_cart_' . $this->cart['hash'];
$this->hooks['before'] = $hook;
$one_hour = time() + 30;
if( ! wp_next_scheduled( $hook ) ){
wp_schedule_single_event( $one_hour, $hook, $this->cart );
add_action( $hook, array( $this, 'check_order_for_cart' ) );
}
}
I do not think the check_order_for_cart ever runs because I never see the 'email_customer_abandoned_cart_...' job.
public static function get_session( $customer_id )
{
$handler = new WC_Session_Handler();
return $handler->get_session( $customer_id );
}
public static function check_order_for_cart( $cart )
{
/**
* Splits up the session cookie, uses the first part to grab the cart session
* If session is false, that means the cart expired or was order.
*/
$session = $this->get_session( $cart['customer_id'] );
if( false !== $session ){
$hook = 'email_customer_abandoned_cart_' . $cart['hash'];
$this->hooks['after'] = $hook;
wp_schedule_single_event( time() + DAY_IN_SECONDS, $hook, $cart );
add_action( $hook, array( $this, 'email_customer_abandoned_cart' ) );
}
// kill action
remove_action( $this->hooks['before'], array( $this, 'check_order_for_cart' ) );
}
Well, I expect that after the first job runs, the second job is created and in the cron queue. I expect when that second job runs that my email is sent and my customer comes back and buys lots of dildo's from me.
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?