Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save strangerstudios/6169167 to your computer and use it in GitHub Desktop.
Save strangerstudios/6169167 to your computer and use it in GitHub Desktop.
Change PMPro membership cancellation to set expiration date for next payment instead of cancelling immediately.
/*
Change cancellation to set expiration date for next payment instead of cancelling immediately.
Assumes orders are generated for each payment (i.e. your webhooks/etc are setup correctly).
Since 2015-09-21 and PMPro v1.8.5.6 contains code to look up next payment dates via Stripe and PayPal Express APIs.
*/
//before cancelling, save the next_payment_timestamp to a global for later use. (Requires PMPro 1.8.5.6 or higher.)
function my_pmpro_before_change_membership_level($level_id, $user_id) {
//are we on the cancel page?
global $pmpro_pages, $wpdb, $pmpro_stripe_event, $pmpro_next_payment_timestamp;
if($level_id == 0 && (is_page($pmpro_pages['cancel']) || (is_admin() && (empty($_REQUEST['from']) || $_REQUEST['from'] != 'profile')))) {
//get last order
$order = new MemberOrder();
$order->getLastMemberOrder($user_id, "success");
//get level to check if it already has an end date
if(!empty($order) && !empty($order->membership_id))
$level = $wpdb->get_row("SELECT * FROM $wpdb->pmpro_memberships_users WHERE membership_id = '" . $order->membership_id . "' AND user_id = '" . $user_id . "' ORDER BY id DESC LIMIT 1");
//figure out the next payment timestamp
if(empty($level) || (!empty($level->enddate) && $level->enddate != '0000-00-00 00:00:00')) {
//level already has an end date. set to false so we really cancel.
$pmpro_next_payment_timestamp = false;
} elseif(!empty($order) && $order->gateway == "stripe") {
//if stripe, try to use the API
if(!empty($pmpro_stripe_event)) {
//cancel initiated from Stripe webhook
if(!empty($pmpro_stripe_event->data->object->current_period_end)) {
$pmpro_next_payment_timestamp = $pmpro_stripe_event->data->object->current_period_end;
}
} else {
//cancel initiated from PMPro
$pmpro_next_payment_timestamp = PMProGateway_stripe::pmpro_next_payment("", $user_id, "success");
}
} elseif(!empty($order) && $order->gateway == "paypalexpress") {
//if PayPal, try to use the API
if(!empty($_POST['next_payment_date']) && $_POST['next_payment_date'] != 'N/A') {
//cancel initiated from IPN
$pmpro_next_payment_timestamp = strtotime($_POST['next_payment_date'], current_time('timestamp'));
} else {
//cancel initiated from PMPro
$pmpro_next_payment_timestamp = PMProGateway_paypalexpress::pmpro_next_payment("", $user_id, "success");
}
} else {
//use built in PMPro function to guess next payment date
$pmpro_next_payment_timestamp = pmpro_next_payment($user_id);
}
}
}
add_action('pmpro_before_change_membership_level', 'my_pmpro_before_change_membership_level', 10, 2);
//give users their level back with an expiration
function my_pmpro_after_change_membership_level($level_id, $user_id) {
global $pmpro_pages, $wpdb, $pmpro_next_payment_timestamp;
if($pmpro_next_payment_timestamp !== false && //this is false if the level already has an enddate
$level_id == 0 && //make sure we're cancelling
(is_page($pmpro_pages['cancel']) || (is_admin() && (empty($_REQUEST['from']) || $_REQUEST['from'] != 'profile')))) { //on the cancel page or in admin/adminajax/webhook and not the edit user page
/*
okay, let's give the user his old level back with an expiration based on his subscription date
*/
//get last order
$order = new MemberOrder();
$order->getLastMemberOrder($user_id, "cancelled");
//can't do this if we can't find the order
if(empty($order->id))
return false;
//get the last level they had
$level = $wpdb->get_row("SELECT * FROM $wpdb->pmpro_memberships_users WHERE membership_id = '" . $order->membership_id . "' AND user_id = '" . $user_id . "' ORDER BY id DESC LIMIT 1");
//can't do if we can't find an old level
if(empty($level))
return false;
//last payment date
$lastdate = date("Y-m-d", $order->timestamp);
/*
next payment date
*/
//if stripe or PayPal, try to use the API
if(!empty($pmpro_next_payment_timestamp)) {
$nextdate = $pmpro_next_payment_timestamp;
} else {
$nextdate = $wpdb->get_var("SELECT UNIX_TIMESTAMP('" . $lastdate . "' + INTERVAL " . $level->cycle_number . " " . $level->cycle_period . ")");
}
//if the date in the future?
if($nextdate - time() > 0) {
//give them their level back with the expiration date set
$old_level = $wpdb->get_row("SELECT * FROM $wpdb->pmpro_memberships_users WHERE membership_id = '" . $order->membership_id . "' AND user_id = '" . $user_id . "' ORDER BY id DESC LIMIT 1", ARRAY_A);
$old_level['enddate'] = date("Y-m-d H:i:s", $nextdate);
//disable this hook so we don't loop
remove_action("pmpro_after_change_membership_level", "my_pmpro_after_change_membership_level", 10, 2);
remove_filter('pmpro_cancel_previous_subscriptions', 'my_pmpro_before_change_membership_level', 10, 2);
//disable the action to set the default level on cancels
remove_action('pmpro_after_change_membership_level', 'pmpro_after_change_membership_level_default_level', 10, 2);
//change level
pmpro_changeMembershipLevel($old_level, $user_id);
//add the action back just in case
add_action("pmpro_after_change_membership_level", "my_pmpro_after_change_membership_level", 10, 2);
add_filter('pmpro_cancel_previous_subscriptions', 'my_pmpro_before_change_membership_level', 10, 2);
//add the action back to set the default level on cancels
remove_action('pmpro_after_change_membership_level', 'pmpro_after_change_membership_level_default_level', 10, 2);
//change message shown on cancel page
add_filter("gettext", "my_gettext_cancel_text", 10, 3);
}
}
//clear up this global in case we're changing many levels at once (e.g. expiration script running)
unset($pmpro_next_payment_timestamp);
}
add_action("pmpro_after_change_membership_level", "my_pmpro_after_change_membership_level", 10, 2);
//this replaces the cancellation text so people know they'll still have access for a certain amount of time
function my_gettext_cancel_text($translated_text, $text, $domain) {
if(($domain == "pmpro" || $domain == "paid-memberships-pro") && $text == "Your membership has been cancelled.") {
global $current_user;
$translated_text = "Your recurring subscription has been cancelled. Your active membership will expire on " . date(get_option("date_format"), pmpro_next_payment($current_user->ID, "cancelled")) . ".";
}
return $translated_text;
}
//want to update the cancellation email as well
function my_pmpro_email_body($body, $email) {
if($email->template == "cancel") {
global $wpdb;
$user_id = $wpdb->get_var("SELECT ID FROM $wpdb->users WHERE user_email = '" . esc_sql($email->email) . "' LIMIT 1");
if(!empty($user_id)) {
$expiration_date = pmpro_next_payment($user_id);
//if the date in the future?
if($expiration_date - time() > 0) {
$body .= "<p>Your access will expire on " . date(get_option("date_format"), $expiration_date) . ".</p>";
}
}
}
return $body;
}
add_filter("pmpro_email_body", "my_pmpro_email_body", 10, 2);
@mircobabini
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment