aldeed:plans

v0.0.2Published 7 years ago

This package has not had recent updates. Please investigate it's current state before committing to using it in your project.

Build Status

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