Skip to content

Instantly share code, notes, and snippets.

@ideadude
Last active August 15, 2023 14:29
Show Gist options
  • Save ideadude/efe7bb5c8edffb79ef9512ce1f283fb7 to your computer and use it in GitHub Desktop.
Save ideadude/efe7bb5c8edffb79ef9512ce1f283fb7 to your computer and use it in GitHub Desktop.
Override the default proration behavior in the PMPro Proration Add On
/*
1. Make sure PMPro and PMPro Proration are both active.
2. Edit the pmpro_checkout_level_custom_prorating_rules function below to your needs.
3. Then add this code into a custom plugin for your site.
*/
/**
* Swap in our custom prorating function.
*/
function init_custom_prorating_rules() {
remove_filter( 'pmpro_checkout_level', 'pmprorate_pmpro_checkout_level', 10, 1 );
add_filter( 'pmpro_checkout_level', 'pmpro_checkout_level_custom_prorating_rules', 10, 1 );
}
add_action( 'init', 'init_custom_prorating_rules');
/**
* Our custom prorating function
* Edit this function to prorate per your needs.
* There are 3 main sections below in the if,elseif,else check
* Change those to set the rules for downgrading, upgrading with same billing period,
* or upgrading with different billing periods.
* Generally, you should be setting the initial_payment value on the $level object
* and potentially setting up a hook to update the profile start date.
* See the proration add on code for help.
*/
function pmpro_checkout_level_custom_prorating_rules( $level ) {
// can only prorate if they already have a level
if ( pmpro_hasMembershipLevel() ) {
global $current_user;
$clevel = $current_user->membership_level;
$morder = new MemberOrder();
$morder->getLastMemberOrder( $current_user->ID, array( 'success', '', 'cancelled' ) );
// no prorating needed if they don't have an order (were given the level by an admin/etc)
if ( empty( $morder->timestamp ) ) {
return $level;
}
// different prorating rules if they are downgrading, upgrading with same billing period, or upgrading with a different billing period
if ( pmprorate_isDowngrade( $clevel->id, $level->id ) ) {
// below if the default code from the proration add on as of version .3
// you can change this section to change the downgrade logic
/*
Downgrade rule in a nutshell:
1. Charge $0 now.
2. Allow their current membership to expire on their next payment date.
3. Setup new subscription to start billing on that date.
4. Other code in this plugin handles changing the user's level on the future date.
*/
$level->initial_payment = 0;
global $pmpro_checkout_old_level;
$pmpro_checkout_old_level = $clevel;
} elseif( pmprorate_have_same_payment_period( $clevel->id, $level->id ) ) {
// below if the default code from the proration add on as of version .3
// you can change this section to change the logic when upgrading between levels
// with the same billing period
/*
Upgrade with same billing period in a nutshell:
1. Calculate the initial payment to cover the remaining time in the current pay period.
2. Setup subscription to start on next payment date at the new rate.
*/
$payment_date = pmprorate_trim_timestamp( $morder->timestamp );
$next_payment_date = pmprorate_trim_timestamp( pmpro_next_payment( $current_user->ID ) );
$today = pmprorate_trim_timestamp( current_time( 'timestamp' ) );
$days_in_period = ceil( ( $next_payment_date - $payment_date ) / 3600 / 24 );
//if no days in period (next payment should have happened already) return level with no change to avoid divide by 0
if ( $days_in_period <= 0 ) {
return $level;
}
$days_passed = ceil( ( $today - $payment_date ) / 3600 / 24 );
$per_passed = $days_passed / $days_in_period; //as a % (decimal)
$per_left = 1 - $per_passed;
/*
Now figure out how to adjust the price.
(a) What they should pay for new level = $level->billing_amount * $per_left.
(b) What they should have paid for current level = $clevel->billing_amount * $per_passed.
What they need to pay = (a) + (b) - (what they already paid)
If the number is negative, this would technically require a credit be given to the customer,
but we don't currently have an easy way to do that across all gateways so we just 0 out the cost.
This is the method used in the code below.
An alternative calculation that comes up with the same number (but may be easier to understand) is:
(a) What they should pay for new level = $level->billing_amount * $per_left.
(b) Their credit for cancelling early = $clevel->billing_amount * $per_left.
What they need to pay = (a) - (b)
*/
$new_level_cost = $level->billing_amount * $per_left;
$old_level_cost = $clevel->billing_amount * $per_passed;
$level->initial_payment = min( $level->initial_payment, round( $new_level_cost + $old_level_cost - $morder->subtotal, 2 ) );
//just in case we have a negative payment
if ( $level->initial_payment < 0 ) {
$level->initial_payment = 0;
}
//make sure payment date stays the same
add_filter( 'pmpro_profile_start_date', 'pmprorate_set_startdate_to_next_payment_date', 10, 2 );
} else {
// below if the default code from the proration add on as of version .3
// you can change this section to change the logic when upgrading between levels
// with different billing periods
/*
Upgrade with different payment periods in a nutshell:
1. Apply a credit to the initial payment based on the partial period of their old level.
2. New subscription starts today with the initial payment and will renew one period from now based on the new level.
*/
$payment_date = pmprorate_trim_timestamp( $morder->timestamp );
$next_payment_date = pmprorate_trim_timestamp( pmpro_next_payment( $current_user->ID ) );
$today = pmprorate_trim_timestamp( current_time( 'timestamp' ) );
$days_in_period = ceil( ( $next_payment_date - $payment_date ) / 3600 / 24 );
//if no days in period (next payment should have happened already) return level with no change to avoid divide by 0
if ( $days_in_period <= 0 ) {
return $level;
}
$days_passed = ceil( ( $today - $payment_date ) / 3600 / 24 );
$per_passed = $days_passed / $days_in_period; //as a % (decimal)
$per_left = 1 - $per_passed;
$credit = $morder->subtotal * $per_left;
$level->initial_payment = round( $level->initial_payment - $credit, 2 );
//just in case we have a negative payment
if ( $level->initial_payment < 0 ) {
$level->initial_payment = 0;
}
}
}
return $level;
}
@CrandellWS
Copy link

it should be

function pmpro_checkout_level_custom_prorating_rules( $level ) {...}

the function part was left out...

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