Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pbrocks/ed5ca2bb7ab345d09b3f589c5cc4e105 to your computer and use it in GitHub Desktop.
Save pbrocks/ed5ca2bb7ab345d09b3f589c5cc4e105 to your computer and use it in GitHub Desktop.
Change PMPro membership cancellation to set expiration date for next payment instead of cancelling immediately.
<?php
/**
* 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.)
*
* @param [type] $level_id [description]
* @param [type] $user_id [description]
* @return [type] [description]
*/
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 );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment