Subscriptions Documentation

Subscriptions Documentation

Installing and Updating

Installing

  1. Within the /system/expressionengine/third_party/ directory, copy the subs folder into your sites ./system/expressionengine/third_party/ directory.
  2. Within the /themes/third_party/ directory, copy the subs folder into your sites./themes/third_party/ directory.
  3. Go to the Modules area in the ExpressionEngine control panel, find Subscriptions in the list, and click Install.

Example Templates

Subscriptions includes a full set of templates for both your subscriptions control panel and emails. We HIGHLY recommend you install these. Developing the site just from tag documentation is extremely difficult. You will find the two groups inside the subscriptions folder /system/expressionengine/third_party/subs/templates/. Copy these two groups to your templates folder and synchronize them with your installation. For more information on synchronizing your templates visit the EllisLab Docs

Updating

  1. Within your sites ./system/expressionengine/third_party/ directory, delete the subscriptions folder and upload the new one from the /system/expressionengine/third_party/ directory in your download package.
  2. Within your sites ./themes/third_party/ directory, delete the subs folder and upload the new one from the /themes/third_party/ directory in your download package.
  3. Go to the Modules area in the ExpressionEngine control panel and click the Run Module Updates button.

Overview of Functionality and Tags

Subscriptions is a stand-alone system for managing recurring payments and functionality for ExpressionEngine. The idea is you create a member in ExpressionEngine, add subscription levels and incur a recurring payment that allows actions to take place at trigger points. Actions consist of many different activities including sending emails, changing member groups, HipChat message and call a url(template).  Triggers exist in nearly 20 different points and give a vast amount of flexibility to your subscription system.

Tags

The templates for subscription are quite simple and in some situations only a minimal of tags are needed. Over half of the tags provide functionality to update subscription information. The only required tag is the subscribe tag which allows the member to activate a subscription. Only if you wish to add additional functionality to manage the subscription, canceling, adding cards, etc would additional tags need to be added. 

The subscriptions tag is also a valuable tag as it allows members to have multiple subscriptions and circumvent the standard member group style subscription systems of the past. This subscription tag allows a members to have subscriptions outside of the “member group” levels in ExpressionEngine.

Control Panel Overview

The Dashboard

The dashboard provides a quick view on the success of your subscriptions site. Providing you with quick snapshots of revenue and other activities in your site. The dashboard also includes seven buttons that provide setup and functionality for your subscriptions system.

The different sections of the dashboard control panel are:

  1. Subscriptions - See a list of your current subscriptions. Clicking on the subscription will give you ability to manage the subscription.
  2. Customers - List of members with subscriptions. Clicking on a customer will give you access to transaction history and list of this members subscriptions.
  3. Coupons - Discount system for subscriptions. Provide discounts once, multiple intervals or for every recurring transaction.
  4. Vouchers - Sell or provide subscriptions for later use by code (Similar to gift cards). Generate codes that can be used to create subscriptions.
  5. Invoices - View invoices for transactions.
  6. Logs - Extensive logging for all triggers and actions. Great for debugging or seeing what is "happening" in the system.
  7. Configuration - Settings area for getting subscriptions up and running.

 

Settings

Setting up Subscriptions is critical to its success and proper functionality. A requirement is to provide a Cron Job that fires the Subscriptions ACT URL.  In the image above you will see the ACT URL as: http://www.site.com/index.php?ACT=56, this entire URL must be fired at regular intervals. We recommend first testing this URL. Copy and paste the URL into your browser. When you visit the page you should see the view in following image.

You will need to contact your hosting provider on how to setup a Cron Job for your server. Nearly every host provides this service, as well as other third party cron job providers. Without the cron job subscriptions will not work!

Test Members

Subscriptions gives you the ability to setup test members. We highly recommend entering the super admin here. If you work in a studio (office) environment you can add an IP as well so that multiple test members can be made rapidly.

Please Note: If you test with the Super Admin and you have an "Action" setup to change the member group, completing a subscription could possibly move your Super Admin group level to a lower level making it impossible for you to access your ExpressionEngine control panel. Please do not use the Super Admin account for testing group level subscriptions.

Other Settings

Subscriptions requires a few more settings to get ready. Several will already have default choices in them.

Renew Settings - This is the count and hours to wait to attempt a renew if the payment method fails. If you enter 3 for the count and 24 hours for the timeout, Subscriptions will attempt to charge the subscribed customer 3 more attempts each 24 hours apart. At the end of the 3rd attempt Subscriptions will automatically trigger "Subscription Canceled".  

Payments & Reports - Subscriptions supports four different currencies. Current supported are Dollars (USD), Euros (EURO), Pounds (GBP) and Canadian Dollars (CAD).

Email Configuration - Setup for your email notifications. Enter the email addresses you wish your notificaitons to come from, reply to and other standard email options.

PDF - When printing invoices you have the option to convert to PDF. Subscriptions comes with a built-in PDF template for your use, however if you wish to use a template of your own you can set the template choice here.

Gateways

Subscription currently supports two different gateways, Stripe and Authorize.net with CIM. Subscriptions does not store any Credit Card data locally except the expiration date for protection to your customers. Subscriptions only will support gateways that provide full API access to the credit card data can be added. Currently we are in process of alpha testing Paypal, it is not support at this time.

Creating Plans, Triggers and Actions

Plans

When visiting Configuration -> Plans you are creating and editing your “Subscription” Plans.

Plans used for subscriptions are absolute, meaning each new recurring transaction has its own subscription amount. For example if you sold 10 subscriptions at $9.99 a month, then change your subscription rate plan to $12.99, all previous recurring transactions will still take place at $9.99 and not the new price of $12.99.

There are five setup options for creating a subscription.

General - Simply put this is the name of your subscription. For example "Monthly Subscription" or "Annual Service" would be entered into the label. You may also select currency here, however we STRONGLY suggest you keep it the same as your default currency. If you have a different currency here reports will not work for this subscription.

Trial - Subscriptions can have a trial period where the customers card is not charged. You can also make the trial recurring. For example you could have a 3 month trial for a monthly product.  For that you would choose 1 month and 3 cycles. At the end of the 3rd cycle Subscriptions would charge the card on file the amount of the subscription rate and any conditionals.

Recurring - The heart of the module and where you choose the cost of the subscription.  You enter price, the interval, if you wish it to auto renew and the max cycles the subscription will run. For example if you would like to have a quarterly subscription that runs for 2 years you would choose every 3 months for a max cycle of 8. (1 year being 4 quarters)

Miscellaneous - This is additional text that will show on the credit card receipt for this subscription. Since subscriptions are independent charges and not a cart full of products, each subscription will charge on its own. So if a customer has 3 subscriptions, he would receive 3 separate transactions on their card.

Conditionals - This allows customer information to increase or decrease the price of your subscription. For example the customer is in Canada and you are shipping a product to the customer each month. This would allow for an additional conditional charge if the customer chooses Canada in their customer information.

Triggers

What subscriptions do are called Triggers with Actions. In many other member systems you only have the ability to change member groups, with Subscriptions it is different. Changing member groups is just one action that can take place. Actions are started by Triggers. We have established nearly 20 different Triggers that take place in Subscriptions to connect Actions to. The following image shows the Actions control panel. This is found by going to Configuration -> Actions in the dashboard.

The following are Triggers in Subscriptions:

  • Subscribed - Occurs when a customer starts a subscription.
  • Subscription Renewed - Occurs when a subscription is renewed via the Cron Job.
  • Subscription Canceled - Occurs when a subscription is canceled.
  • Subscription Expired - Occurs when a subscription hits the max cycle count. (Max Cycles are setup in the Subscription Plan) 
  • New Invoice - Occurs when either a subscription is started or renewed.
  • New Voucher - Occurs when a voucher is created with the template tag.
  • Redeemed Voucher - Occurs when a voucher is redeemed with the template tag.
  • Used Coupon - Occurs when a coupon is used.
  • Coupon Limit - Occurs when a coupon has hit its max usage.
  • New Card Added - Occurs when a new credit card is added with the template tag.
  • Card Updated - Occurs when a credit card is updated with the template tag.
  • Card Deleted - Occurs when a credit card is deleted.
  • Card Charged - Occurs when a credit card is charged for any transaction.
  • Card Charge Declined - Occurs when a credit card is declined for any transaction.
  • Card Expiring - Occurs 30 days before the credit card is set to expire.
  • Card Expired - Occurs when the credit card expires.

 

Creating an Action

By clicking on a Trigger will bring up an actions control panel.

Once clicking the Action you want you are giving configurable options for that action. Subscription provides an email template for nearly every trigger. The template group is called subs_emails in the ExpressionEngine Template manager.

Payment Gateway: Stripe

As of version 1.5.0 the Stripe javascript helper code will not be automatically injected. We will support this but now you need to add it to your subscribe page manually.

Required HTML Inputs

1
2
3
4
5
{if subs:test_mode == 'yes'}<input type="hidden" class="stripe_public_key" value="YOUR_TEST_STRIPE_PUBLIC_KEY">{/if}
{if subs:test_mode == 'no'}<input type="hidden" class="stripe_public_key" value="YOUR_LIVE_STRIPE_PUBLIC_KEY">{/if}
<input type="hidden" name="card_token" class="stripe_token">
 
<div class="stripe_error"></div>

Required Javascript Library

1
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>

Recommended Stripe JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
<script>
;(function(global, $){
    //es5 strict mode
    "use strict";
 
    var inputs = $('input.stripe_public_key');
    if (inputs.length === 0) return;
 
    inputs.each(function(i, elem){
        $(elem).closest('form').bind('submit', catchFormSubmission);
    });
 
    var formElem;
    var errorHolder;
    var submitBtn;
    var submitVal;
 
    // Todo multiple forms on one page
 
    // ----------------------------------------------------------------------
 
    function catchFormSubmission(e) {
        formElem = $(e.target);
 
        // Any predefined cards?
        var predef = formElem.find(':input[name=card_hash]:first');
        var cardHash = null;
 
        if (predef.length > 0 && predef[0].nodeName == 'SELECT') {
            cardHash = formElem.find(':input[name=card_hash]').val();
        } else if (predef.length > 0 && predef[0].nodeName == 'INPUT') {
            cardHash = formElem.find(':input[name=card_hash]:checked').val();
        }
 
        if (cardHash) return true;
 
        errorHolder = formElem.find('.stripe_error, [data-stripe=error]');
 
        e.preventDefault();
 
        // Set the publish key
        Stripe.setPublishableKey(formElem.find('.stripe_public_key').val());
 
        // Remove the error message
        formElem.find('.stripe_error, [data-stripe=error]').hide().empty();
 
        // Set
        submitBtn = formElem.find('.stripe_submit, input[type=submit]');
 
        if (submitBtn.length == 0) {
            alert("Submit button not found (use class 'stripe_submit' on the submit button)");
        }
 
        var loadingMsg = submitBtn.data('loadingmsg') ? submitBtn.data('loadingmsg') : 'Verifying...';
 
        if (submitBtn[0].nodeName == 'INPUT') {
            submitVal = submitBtn.val();
            submitBtn.attr('disabled', 'disabled').addClass('disabled').attr('value', 'Verifying...');
        } else {
            submitVal = submitBtn.html();
            submitBtn.attr('disabled', 'disabled').addClass('disabled').html('Verifying...');
        }
 
        // Params
        var params = {};
        var tempVals = {};
        tempVals['number'] = formElem.find('.stripe_number, [data-stripe=number]').val();
        tempVals['exp_month'] = formElem.find('.stripe_exp_month, [data-stripe=exp_month]').val();
        tempVals['exp_year'] = formElem.find('.stripe_exp_year, [data-stripe=exp_year]').val();
        tempVals['cvc'] = formElem.find('.stripe_cvc, [data-stripe=cvc]').val();
        tempVals['name'] = formElem.find('.stripe_name, [data-stripe=name]').val();
        tempVals['address_line1'] = formElem.find('.stripe_address_line1, [data-stripe=address_line1]').val();
        tempVals['address_line2'] = formElem.find('.stripe_address_line2, [data-stripe=address_line2]').val();
        tempVals['address_city'] = formElem.find('.stripe_address_city, [data-stripe=address_city]').val();
        tempVals['address_state'] = formElem.find('.stripe_address_state, [data-stripe=address_state]').val();
        tempVals['address_zip'] = formElem.find('.stripe_address_zip, [data-stripe=address_zip]').val();
        tempVals['address_country'] = formElem.find('.stripe_address_country, [data-stripe=address_country]').val();
 
        for (var key in tempVals) {
            if (tempVals[key] == undefined || tempVals[key] == 'undefined') continue;
            params[key] = tempVals[key];
        }
 
        try {
            Stripe.card.createToken(params, stripeResponseHandler);
        } catch (e) {
            stripeResponseHandler(null, {
                error: {
                    message: e.message
                }
            });
        }
 
    }
 
    // ----------------------------------------------------------------------
 
    function stripeResponseHandler(status, response) {
        if (response.error) {
            errorHolder.show().html(response.error.message);
 
            if (submitBtn[0].nodeName == 'INPUT') {
                submitBtn.removeAttr('disabled').removeClass('disabled').attr('value', submitVal);
            } else {
                submitBtn.removeAttr('disabled').removeClass('disabled').html(submitVal);
            }
        } else {
 
            formElem.find('.stripe_token, .stripe_card_token').attr('value', response['id']);
 
            if (typeof formElem[0].submit == 'object') {
                HTMLFormElement.prototype.submit.call(formElem[0]);
            } else {
                formElem[0].submit();
            }
        }
    }
 
    // ----------------------------------------------------------------------
 
}(window, jQuery));
</script>

Example Subscribe Form

1
 

Extension Hooks

subs_subscription_created

1
2
3
4
5
6
7
8
9
10
/**
 * subs_subscription_created hook
 * - After the subscription has been inserted to DB
 *
 * @param   $sub object Subscription Model Object
 * @since   1.5.0
 */
if (ee()->extensions->active_hook('subs_subscription_created')) {
    $foo = ee()->extensions->call('subs_subscription_created', $sub);
}

subs_subscription_updating

1
2
3
4
5
6
7
8
9
10
11
/**
 * subs_subscription_updating hook
 * - Just before the an existing subscription info is updated
 *
 * @param   $sub    object  Subscription Model Object
 * @param   $dirty  array   Array of changed keys
 * @since   1.5.0
 */
if (ee()->extensions->active_hook('subs_subscription_updating')) {
    $foo = ee()->extensions->call('subs_subscription_updating', $sub, $sub->getDirty());
}

subs_subscription_updated

1
2
3
4
5
6
7
8
9
10
/**
 * subs_subscription_updated hook
 * - After an existing subscription info has been updated
 *
 * @param   $sub    object  Subscription Model Object
 * @since   1.5.0
 */
if (ee()->extensions->active_hook('subs_subscription_updated')) {
    $foo = ee()->extensions->call('subs_subscription_updated', $sub);
}

subs_subscription_deleted

1
2
3
4
5
6
7
8
9
10
/**
 * subs_subscription_deleted hook
 * - After an existing subscription has been deleted
 *
 * @param   $sub    object  Subscription Model Object
 * @since   1.5.0
 */
if (ee()->extensions->active_hook('subs_subscription_deleted')) {
    $foo = ee()->extensions->call('subs_subscription_deleted', $sub);
}

subs_subscription_bought

1
2
3
4
5
6
7
8
9
10
11
12
/**
 * subs_subscription_bought hook
 * - After an subscription has been bought by a user,
 * actions/emails have already been triggered
 *
 * @param   $sub    object  Subscription Model Object
 * @param   $charge mixed   FALSE if no charge (example: trial) otherwise a Charge Object
 * @since   1.5.0
 */
if (ee()->extensions->active_hook('subs_subscription_bought')) {
    $foo = ee()->extensions->call('subs_subscription_bought', $sub, $charge);
}

subs_subscription_canceled

1
2
3
4
5
6
7
8
9
10
11
12
/**
 * subs_subscription_canceled hook
 * - After an subscription has been canceled
 * actions/emails have not been triggered yet
 *
 * @param   $sub        object   Subscription Model Object
 * @param   $cancelNow  bool     TRUE/FALSE if the subscription will be canceled right away
 * @since   1.5.0
 */
if (ee()->extensions->active_hook('subs_subscription_canceled')) {
    $res = ee()->extensions->call('subs_subscription_canceled', $subs, $cancelNow);
}

Support

Having problems setting up and/or using Subscriptions? Support is offered from 10am to 4pm EST weekdays. Send us an email at help@eeharbor.com and we will respond as quickly as we can.