Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save dwanjuki/e17e9bbab43f1f07e9782d052ec50161 to your computer and use it in GitHub Desktop.

Select an option

Save dwanjuki/e17e9bbab43f1f07e9782d052ec50161 to your computer and use it in GitHub Desktop.
<?php
/**
* Deferred-payment promo: when a member of the eligible level checks out for
* the level using the promo discount code, push their first recurring payment
* out to 1 year after their existing subscription's next payment date.
* They still pay the initial payment now.
*
* You can add this recipe to your site by creating a custom plugin
* or using the Code Snippets plugin available for free in the WordPress repository.
* Read this companion article for step-by-step directions on either method.
* https://www.paidmembershipspro.com/create-a-plugin-for-pmpro-customizations/
*/
/**
* EDIT HERE: the discount code that triggers the promo.
*/
if ( ! defined( 'MY_PMPRO_PROMO_DISCOUNT_CODE' ) ) {
define( 'MY_PMPRO_PROMO_DISCOUNT_CODE', 'ANNUAL99' );
}
/**
* EDIT HERE: the membership level ID the promo applies to. The member must
* already hold this level, and be checking out for it again, to qualify.
*/
if ( ! defined( 'MY_PMPRO_PROMO_LEVEL_ID' ) ) {
define( 'MY_PMPRO_PROMO_LEVEL_ID', 1 );
}
/**
* Resolve the discount code applied to a level object.
*
* @param object $level Level object.
* @return string The applied discount code, or '' if none.
*/
function my_pmpro_get_level_discount_code( $level ) {
if ( ! empty( $level->discount_code ) ) {
return $level->discount_code;
}
if ( ! empty( $level->code_id ) ) {
global $wpdb;
return (string) $wpdb->get_var(
$wpdb->prepare( "SELECT code FROM {$wpdb->pmpro_discount_codes} WHERE id = %d LIMIT 1", $level->code_id )
);
}
return '';
}
/**
* Determine whether the promo applies to this level for the current user and,
* if so, return the deferred first-payment date (UTC Y-m-d H:i:s).
*
* @param object $level The level being checked out / previewed.
* @return string|false UTC datetime string, or false if the promo doesn't apply.
*/
function my_pmpro_promo_get_deferred_startdate( $level ) {
// Bail if PMPro isn't loaded.
if ( ! function_exists( 'pmpro_getMembershipLevelForUser' ) ) {
return false;
}
// Only applies when checking out for the promo level (MY_PMPRO_PROMO_LEVEL_ID).
if ( empty( $level ) || (int) $level->id !== (int) MY_PMPRO_PROMO_LEVEL_ID ) {
return false;
}
// Only when the promo discount code is applied. Bail if the constant was
// overridden with an empty value, otherwise every code-less checkout would match.
$promo_code = trim( (string) MY_PMPRO_PROMO_DISCOUNT_CODE );
if ( '' === $promo_code ) {
return false;
}
if ( strcasecmp( my_pmpro_get_level_discount_code( $level ), $promo_code ) !== 0 ) {
return false;
}
// The level being purchased must be recurring for a deferral to mean anything.
if ( ! pmpro_isLevelRecurring( $level ) ) {
return false;
}
$user_id = get_current_user_id();
if ( ! $user_id ) {
return false;
}
// Member must currently hold the promo level too (i.e. this is a renewal)...
$current_level = pmpro_getMembershipLevelForUser( $user_id );
if ( empty( $current_level ) || (int) $current_level->id !== (int) MY_PMPRO_PROMO_LEVEL_ID ) {
return false;
}
// ...and that current membership must itself be recurring.
if ( ! pmpro_isLevelRecurring( $current_level ) ) {
return false;
}
// Anchor the deferral to the member's existing subscription next payment date.
$next_payment = pmpro_next_payment( $user_id, 'success', 'timestamp' );
if ( empty( $next_payment ) ) {
// No scheduled next payment to anchor to (e.g. subscription already ended) —
// fall back to 1 year from checkout so the promo still applies.
$next_payment = time();
}
// Push the first recurring payment to 1 year after the existing next payment date.
$deferred = strtotime( '+1 year', (int) $next_payment );
// Gateways limit how far out the first payment can be scheduled: PayPal
// clamps profile start dates to 1 year from now, and Stripe's maximum
// trial period is 730 days. Clamp so checkout doesn't fail and the date
// we display matches what the gateway will actually do.
if ( in_array( pmpro_getGateway(), array( 'paypal', 'paypalexpress', 'paypalstandard' ), true ) ) {
$max_deferred = strtotime( '+1 year' );
} else {
$max_deferred = time() + 730 * DAY_IN_SECONDS;
}
return gmdate( 'Y-m-d H:i:s', min( $deferred, $max_deferred ) );
}
/**
* Defer the first recurring payment at checkout.
*/
function my_pmpro_promo_defer_checkout_startdate( $checkout_level ) {
$startdate = my_pmpro_promo_get_deferred_startdate( $checkout_level );
if ( $startdate ) {
$checkout_level->profile_start_date = $startdate;
}
return $checkout_level;
}
add_filter( 'pmpro_checkout_level', 'my_pmpro_promo_defer_checkout_startdate' );
/**
* Show the expected next payment date in the level cost text so customers know.
*/
function my_pmpro_promo_show_startdate_in_cost_text( $cost, $level ) {
// Prefer an already-set profile_start_date (full checkout); otherwise only
// compute it during the discount-code preview request.
$startdate = ! empty( $level->profile_start_date ) ? $level->profile_start_date : false;
if ( empty( $startdate ) && ! empty( $_REQUEST['action'] ) && 'applydiscountcode' === $_REQUEST['action'] ) {
$startdate = my_pmpro_promo_get_deferred_startdate( $level );
}
if ( ! empty( $startdate ) ) {
// profile_start_date is stored in UTC; convert to local time for display.
$start_date = get_date_from_gmt( $startdate, get_option( 'date_format' ) );
$cost .= sprintf( ' <small>(%s %s)</small>', 'Next payment on', esc_html( $start_date ) );
}
return $cost;
}
add_filter( 'pmpro_level_cost_text', 'my_pmpro_promo_show_startdate_in_cost_text', 10, 2 );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment