Using filters and actions to customize the options

Hi there, you awesome people

As a quick reference, here are the 3 subscription modes my client's website offers:
1) 2-day subscription ($5) (one-time, cannot re-subscribe to this)
2) 1-month subscription ($50) (renewable)
3) 1-year subscription ($500) (renewable)

Okay, so earlier on I had asked a question about enabling a one-time trial subscription, and you guys had helpfully pointed out that I could create a trial level within my other main subscriptions.

However, this doesn't quite work for what I wanted, since the client doesn't want the customer to auto-upgrade to one of the main plans.

So I nosed around a bit in the model classes and found that the Membership plugin helpfully extends all sorts of actions and filters. So my plan of attack was something like this:

1) When a subscription is created, if the subscription ID is that of the 2-day trial, add an entry in usermeta for this.

2) When a subscription is dropped/expired, if the subscription ID is that of the 2-day trial, add an entry in usermeta for this.

3) When the user attempts to resubscribe, use the 'membership_override_subscriptions' filter, check for the existing usermeta row from steps 1 and 2 and filter out the trial option if user has previously subscribed to this trial subscription.

----------------------------------

QUESTIONS:
-----------

1) Is there a simple way of finding out if a user has subscribed to a particular subscription before? After it expires I noticed that the entry in m_membership_relationships gets deleted, so I can't check it. That's why I was using the usermeta as described above.

2) For the actions that I need to listen to for steps 1 and 2, I identified the following:
- 'membership_add_subscription'
- 'membership_drop_subscription'
- 'membership_expire_subscription'
- 'membership_move_subscription'

Should I be listening for any other actions?

3) I don't know if I'm triggering an unnatural scenario, but when I go into the m_membership_relationships table and manually update the startdate, updateddate, and expiredate to past dates for user X, and browse as user X in the frontend, I notice that the row in m_membership_relationships gets deleted, the 'start_current_{subid}' entry in user X's usermeta gets removed, etc. All signs of normal subscription expiration, but here's the thing: it doesn't seem to be triggering 'membership_drop_subscription','membership_expire_subscription',or 'membership_move_subscription'. Which leads me to ask, is there another action I've missed?

    Ash

    Totally forgot the second part of my question. I blame the coffee.

    Anyway, the second part is:

    The 1-month and 1-year subscriptions were supposed to be renewable subscriptions, with the user getting notification emails to renew their subscriptions near the end of the subscription period. If I'm right, this would mean that the subscription mode needs to be set to 'finite'.

    Right now, it's been set to 'serial', because when I first set up the system I didn't understand the difference very clearly. If I'm guessing right, the system now auto-renews the monthly and yearly subscriptions at no additional cost from the user (since the payment gateway used is PayPal Single Payments).

    Am I correct in these 2 assumptions? :

    1) If I want it to be a manually-renewed subscription, I should change subscription mode to 'finite'. Both the PayPal Single Payments and PayPal Standard Payments gateways will work for this.

    2) If I want to set up auto-renewal and auto-billing, I should set the subscription mode as 'serial', and use the PayPal Standard Payments gateway.

    Rheinard

    Hi @Ash,

    I'll start with the second part first. Yes, your assumptions are correct. Finite will run out until a user renews manually. Serial with create a subscription with PayPal that will auto-renew.

    To answer your first question the actions you identified should be enough to do what you want to do. I'm adding a snippet below that will help in capturing the subscriptions in the user meta. The first part of the snippet also shows how you can get the information out.

    // Hook into the action
    add_action( 'membership_add_subscription', 10, 4 );
    
    // The hook
    function record_subscription ( $subscription_id, $level_id, $level_order, $user_id ) {
    
    	// Using get_user_option instead of get_user_meta to accommodate for multisite
    	// This is also how you can get it to check later
    	$current_subscriptions = get_user_option( '_current_subscriptions', $user_id );
    
    	// The user meta may not exist, so lets deal with it
    	if( ! $current_subscriptions ) {
    		$current_subscriptions = array();
    		$current_subscription_ids = array();
    	} else {
    		$current_subscription_ids = array_keys( $subscription );
    	}
    
    	// Time to record or update the subscription information
    	if( ! in_array( $subscription_id, $current_subscription_ids ) ) {
    		$current_subscriptions[ $subscription_id ] = array( $level_id, $level_order );
    	} elseif( $level_id != $current_subscriptions[ $subscription_id ][0] ) {
    		$current_subscriptions[ $subscription_id ][0] = $level_id;
    		$current_subscriptions[ $subscription_id ][1] = $level_order;
    	}
    
    	// If last flag is true it will add your database and blog prefix (use for multisite)
    	// If last flag is false then it wont add any prefixes which is fine for single site.
    	// You can leave this true as long as you are using the get_user_option and update_user_option functions.
    	update_user_option( $user_id, '_current_subscriptions', $current_subscriptions, true );
    
    }

    There is a lot of code in here that will help you with the other hooks too. You could when a subscription gets dropped create a new option '_past_subscriptions' and then add it in the same way as in this snippet. To remove a subscription from '_current_subscriptions' you can use unset( $current_subscriptions[ $subscription_id ] ).

    I hope this helps you get started with your integration. I think its a pretty good idea to deal with trials this way.

    Cheers,
    Rheinard

    Ash

    Hi @Rheinard,

    Thanks for the awesome answer, implementing it as we speak.

    With regards to the action hooks to use, can I confirm the following:

    1) 'membership_add_subscription' is triggered when:
    - a user subscribes to a new subscription / upgrades his earlier subscription. Regardless of payment gateway option.
    - admin manually adds a new subscription for this user

    2) 'membership_drop_subscription' is triggered when:
    - a user's subscription expires naturally (i.e. the time period runs out in a finite subscription)
    - admin manually drops this user's subscription

    My chief concern is number 2, as I'm not sure if I have to also listen for 'membership_expire_subscription' in addition to 'membership_drop_subscription'.

    Thanks for the help! You win all the cat videos.

    -----------------------

    EDIT:

    Also, I think there's a tiny typo in your code.

    $current_subscription_ids = array_keys( $subscription );

    Should be

    $current_subscription_ids = array_keys ( $current_subscriptions );

    no?

    Ash

    Okay so quick follow up to my last comment:

    I did a bit of experimenting with debug logs, it turns out my second assumption was incorrect. 'membership_drop_subscription' is only triggered when the subscription is manually dropped. For subscriptions that expire naturally, 'membership_expire_subscription' is called. Based on the naming, this probably shouldn't surprise me

    One thing I noticed, however, was that I'm not able to listen to 'membership_expire_subscription' from my theme's functions.php file. I can confirm that Membership_Model_Member::expire_subscription() is being called, and the do_action('membership_expire_subscription') line is called.

    In my functions.php, I have the following code:

    function my_membership_expire_subscription($sub_id, $user_id){
      error_log('membership expire event received');
    }
    
    add_action('membership_expire_subscription','my_membership_expire_subscription',10,2);

    The add_action() method call is directly in the functions.php's runtime, not within any other hook. Is there something that I'm doing incorrectly? I can use this same approach successfully for 'membership_add_subscription' and 'membership_drop_subscription' without issues.

    Rheinard

    Hi @Ash,

    Were you able to successfully use this hook yet? There is not much difference in this action than the other two hooks.

    To check this however, there is a filter right at the start of the expiration function. You might not want to use this for the functionality you want, but it will be useful to test. Try the following code in your functions.php file to see if it fires when a user expires.

    function my_membership_expire_subscription_filter($continue, $sub_id, $user_id){
    
      error_log('membership expire event received');
    
      return $continue;
    }
    
    add_filter( 'pre_membership_expire_subscription', 'my_membership_expire_subscription_filter', 10, 3 );

    The other thing that is worth noting, is that the this action and filter executes on a cron event. This may mean depending on your setup that error_log() might not be writing to where you think it is writing to. If you're expecting output in the browser you won't get anything.

    Try the following debug settings in your wp-config.php file:

    // Enable WP_DEBUG mode
     define('WP_DEBUG', true);
    
     // Enable Debug logging to the /wp-content/debug.log file
     define('WP_DEBUG_LOG', true);
    
     // Disable display of errors and warnings
     define('WP_DEBUG_DISPLAY', false);
    
     @ini_set('display_errors',0);
    
     // Use dev versions of core JS and CSS file
     define('SCRIPT_DEBUG', true);

    Your errors should now be logged to debug.log in your /wp-content/ folder.

    Cheers,
    Rheinard