Subscriptions Documentation
Installing and Updating
Installing
- Within the /system/expressionengine/third_party/ directory, copy the subs folder into your sites ./system/expressionengine/third_party/ directory.
- Within the /themes/third_party/ directory, copy the subs folder into your sites./themes/third_party/ directory.
- 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
- 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.
- 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.
- 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:
- Subscriptions - See a list of your current subscriptions. Clicking on the subscription will give you ability to manage the subscription.
- Customers - List of members with subscriptions. Clicking on a customer will give you access to transaction history and list of this members subscriptions.
- Coupons - Discount system for subscriptions. Provide discounts once, multiple intervals or for every recurring transaction.
- Vouchers - Sell or provide subscriptions for later use by code (Similar to gift cards). Generate codes that can be used to create subscriptions.
- Invoices - View invoices for transactions.
- Logs - Extensive logging for all triggers and actions. Great for debugging or seeing what is "happening" in the system.
- 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.
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.
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.
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); } |