aldeed:plans
STATUS: USE AT YOUR OWN RISK. STILL IN DEVELOPMENT
This Meteor package makes it super simple to define a set of subscription plans for a website or app, where a plan can be linked with an outside service plan (like a plan defined on Stripe).
So plans are like "roles" that can require payment. You can also simply use this package as a roles package.
Currently, the only outside service supported is Stripe. Add the aldeed:plans-stripe
package for this.
Installation
In your Meteor app directory:
$ meteor add aldeed:plans
Usage
To use this package, you need to define one or more plans, and then call functions like AppPlans.add
or AppPlans.set
or AppPlans.remove
to set the current plans for a user, or AppPlans.has
to check if a user has a plan.
Define Your Plans
Plans must be defined in server code and in client code. It's fine and easiest to configure in common code. Simply call AppPlans.define
for each plan. Here is an example:
1AppPlans.define('free'); 2 3// Define a "premium" plan linked with a Stripe plan. 4AppPlans.define('premium', { 5 services: [ 6 { 7 name: 'stripe', 8 planName: 'stripePlanName', 9 payOptions: {} // default options for the service's payment flow 10 } 11 ], 12 // Buying the premium plan also gives you the free plan 13 includedPlans: ['free'] 14});
This code does not need to be within a Meteor.startup
function.
Add a Plan for a User
In client code, you can add a plan for the current user only:
1AppPlans.add('premium', {service: 'stripe'}, function (error) { 2 // If no error, the plan was successfully added 3});
In server code, you can pass any user ID:
1AppPlans.add('premium', {service: 'stripe', userId: someUserId}, function (error) { 2 // If no error, the plan was successfully removed 3});
The service
option must match one of the services defined for the plan when define
was called. If you don't specify a service, the first one in the services
array is used.
The callback is optional. Without a callback, this function will log errors on the client and will execute synchronously on the server, throwing any errors.
Add a Plan for an Email Address
If you want to accept payment and assign a plan before allowing registration, you can assign a plan to an email address. The plans assigned to an email address will be transferred to a user account when someone registers with that address.
1AppPlans.add('premium', { 2 service: 'stripe', 3 email: 'foo@bar.com' 4}, function (error) { 5 // If no error, the plan was successfully added 6});
The callback is optional. Without a callback, this function will log errors on the client and will execute synchronously on the server, throwing any errors.
Set the Plan for a User or an Email Address
AppPlans.set
is called the same way as AppPlans.add
with the same arguments and options. The difference is that any current plans will be removed (and unsubscribed from on the remote service) before the new plan is added.
Remove a Plan for a User
In client code, you can remove a plan for the current user only:
1AppPlans.remove('premium', function (error) { 2 // If no error, the plan was successfully removed 3});
In server code, you can pass any user ID:
1AppPlans.remove('premium', {userId: someUserId}, function (error) { 2 // If no error, the plan was successfully removed 3});
The callback is optional. Without a callback, this function will log errors on the client and will execute synchronously on the server, throwing any errors and returning true
or false
.
Remove a Plan for an Email Address
You can remove plans assigned to an email address.
1AppPlans.remove('premium', { 2 email: 'foo@bar.com' 3}, function (error) { 4 // If no error, the plan was successfully removed 5});
The callback is optional. Without a callback, this function will log errors on the client and will execute synchronously on the server, throwing any errors and returning true
or false
.
Check Whether the Current User Has a Plan
1if (AppPlans.has('premium')) { 2 // do something 3}
When looking up by email on the client, you must provide a callback:
1AppPlans.has('premium', function (error, hasPlan) { 2 if (!error && hasPlan) { 3 // do something 4 } 5});
Check Whether the Current User Has Access to a Plan's Features
AppPlans.hasAccess
is similar to AppPlans.has
but will also return true
if the user has a plan that includes the plan you're checking.
1if (AppPlans.hasAccess('premium')) { 2 // do something 3}
When looking up by email on the client, you must provide a callback:
1AppPlans.hasAccess('premium', function (error, hasAccess) { 2 if (!error && hasAccess) { 3 // do something 4 } 5});
Get the User's Current Plans
To get the list of plans for the current user:
1var list = AppPlans.list();
To get the list of plans for any user on the server:
1var list = AppPlans.list({userId: someUserId});
To get the list of plans for an email address on the server:
1var list = AppPlans.list({email: 'foo@bar.com'});
When looking up by email on the client, you must provide a callback:
1AppPlans.list({email: 'foo@bar.com'}, function (error, list) { 2 if (!error && list) { 3 console.log(list); 4 } 5});
Get the User's Current Plan
If you're only ever assigning one plan at a time to a user, you can use AppPlans.get
instead of AppPlans.list
for convenience. It returns the first plan in the user's list. The syntax is the same as for AppPlans.list
and a callback is required when looking up by email on the client.
To get the current user's plan:
1var plan = AppPlans.get();
This will be reactive, but when looking up by email on the client, the result is not reactive.
Sync Plans List With the Linked Outside Services
It is likely that you will want to periodically query the outside service APIs to verify that plan subscriptions have not been canceled or expired. You can do this by calling AppPlans.sync
for a user ID or email address.
You can also sync a single plan with AppPlans.syncOne(planName)
.
Example:
1Accounts.onLogin(function (info) { 2 AppPlans.sync({userId: info.user._id}); 3});
Copy Plans to User Upon Registration Based on Email Address
If you are tracking plans by email address prior to registration, you will likely want to copy those plans to the user document once the user registers. Currently this package does not do this automatically because the Accounts.onCreateUser
function is meant to be called only once. You can copy the code below or add it to your existing Accounts.onCreateUser
function.
1// Upon creating a new user, we copy any plans for the registered email address 2Accounts.onCreateUser(function (options, user) { 3 // We still want the default hook's 'profile' behavior. 4 if (options.profile) { 5 user.profile = options.profile; 6 } 7 8 var email = user.emails && user.emails[0] && user.emails[0].address; 9 if (email) { 10 user.appPlans = AppPlans.pullFromEmail(email); 11 } 12 13 return user; 14});
Template Helpers
Some template helpers are available to get plan information about the current user:
{{#if AppPlans.has 'bronze'}}{{/if}} {{#if AppPlans.hasAccess 'bronze'}}{{/if}} {{#each AppPlans.list}}{{/each}} {{AppPlans.get}}
And to iterate through all defined plans:
{{#each AppPlans.listDefined}}{{/each}}
Database
Information for this package is stored in an appPlans
object with the following format:
1appPlans: { 2 list: [ 3 { 4 planName: '<planName>', 5 service: '<serviceName>', 6 subscriptionId: '<subscriptionIdFromExternalService>' 7 } 8 ], 9 <serviceName>: { 10 customerId: customerIdFromExternalService 11 } 12}
For registered users, this property is found on the user document. If you track plans by email address instead, such as for buying plans before signup, then this property is found on a document in the 'AppPlans_emailPlans' collection, which is accessible from server or client code as AppPlans.emailPlans
.
The appPlans
property on the user document for the logged in user is published to the client by default. The AppPlans.emailPlans
collection is not published to any clients.
Schema
If you use aldeed:collection2
package to attach a SimpleSchema to the Meteor.users
collection, add the following to your schema:
1appPlans: { 2 type: Object, 3 optional: true, 4 blackbox: true 5}
Adding Your Own Outside Service
TODO