A Journey in Payment world – Part 4: we have a Plan

A Journey in Payment World

This is a series about integrating a payment system into your web application. While inspired by our own experience, and so Stripe and Ruby oriented, most of the problems and solutions are probably useful in other technical environments.

Should you missed the previous parts, here they are:

We have a Plan

We talked about getting a payment provider, making your first payment and knowing what is happening in your system. This part is dedicated to a more specific aspect that are recurring payments, or to use another words: Plans. A plan is typically made of an id (:gold), a name (“Gold Plan”), a recurrence (“monthly”), an amount (“99”) and a currency (“€”).

A customer with a given plan will be charged recurringly for that amount, until he quit the service, or switch to another plan. A user is typically linked to a plan using a subscription.

In Stripe’s terms:

Stripe::Plan.create(
 amount: 2000,
 interval: 'month',
 name: 'Amazing Gold Plan',
 currency: 'eur',
 id: 'gold'
)

Pretty simple – let’s just take a look at the workflow:

(credit: BrainTree documentation)

Well, maybe not that simple. Of course, we have all our events stored, so we can know what happens. Let’s examine some pitfalls.

Keep you plans closer

As with Events, while Stripe has all the information you need, you’ll probably want to store plans and subscription yourself, in order to not have to call Stripe everytime you need plan information.

Plans represent some level of access to your application, so chances are good that you’ll want to test them to allow or refuse access to a given functionality. For instance, you cannot post a job on LinkedIn if you don’t have a premium plan:

def post_job
 if(user.plan.id == :premium)
   #go ahead
 else
   warn(”Sorry, this require a premium account. Click here to upgrade”)
 end
end

A simple Plan object needs a link to the Stripe id, and could looks like this:

class Plan < ActiveRecord::Base

 attr_accessible :description, :name, :price

 def stripe_id
   name.to_s.upcase
 end

end

Of course, having a real Ruby object allows to create some syntactic sugar, especially if you don’t have a lot of different plans.

class Plan

 def self.premium
   Plan.where(name: 'premium').first
 end

 def premium?
   name == :premium
 end

end

making very easy to test access with code like:

if(user.plan.premium?)
  #go ahead
end

You probably want something more role like user.can(:post_job) but this is outside of this post.

To assign a plan to a given user:

user.plan = Plan.premium

Immutability

Stripe made the choice of making the plans immutable – once created there is no way to update them. This makes things easier as each customer linked to a given plan is paying exactly the same thing. The disadvantage is that any update you want to do to a plan (for instance increasing your pricing) require to create a new plan and migrate users on it (which is probably what you want business wise – if you increase your price, you probably want to let your user confirm his plan with the new price, or select another one).

Note that BrainTree did a different choice, with a different set of problems – while plans can be updated, the new price will only impact new subscriptions (https://support.braintreepayments.com/customer/portal/articles/1184215-recurring-billing-faq#Update).

Subscription

A user is linked to a plan using a subscription. Of course, a user may change plan at some point in time, so while it is true that a user only has one subscription at a given time, he can actually have several in absolute terms.

A Subscription object would link a user to a plan, and store a start and a (possibly nil) end date, plus any other information that can be useful.

class Subscription < ActiveRecord::Base

 belongs_to :plan
 belongs_to :user

 attr_accessor :start_date, :end_date

end

class User < ActiveRecord::Base

 has_many :subscriptions, dependent: :destroy

end

The idea behind the “has_many” is to be able to keep the history of the subscription, mostly to be able to understand any weird situation. Fortunately, we do not need to do that ourselves. A very nifty gem called paper_trail can be used to automatically save any change to the subscription, with a single line of code:

class Subscription < ActiveRecord::Base

 belongs_to :plan
 belongs_to :user

 attr_accessor :start_date, :end_date

 has_paper_trail

end

This creates a special attribute “versions” allowing you to see the list of previous states, with their relevant periods:

subscription.versions

Adding –with-changes to the db migration (rake db:migrate –with-changes) allows you to diff versions:

subscription.versions.last.changeset

This is just scratching the surface of paper_trail that has many other usages than for billing information, but it is certainly useful here.

Free plans

It can be that no all your users are paying, i.e., that some functionalities are accessible “for free”. That createS the question of what plan should have those “free” users. While you can let them have no plan at all in your application, we found useful to have everyone get a plan – even a free one – to keep everything consistent. Stripe allows you to create plan with an amount of 0. This means that no charge will ever be done, and that you don’t need any credit card information for those users.

Trial

A plan can allow for a trial period – a period where the customer is considered as active, but is not charged. The trial period should be stored with the subscription:

class Subscription < ActiveRecord::Base

 belongs_to :plan
 belongs_to :user

 attr_accessor :start_date, :end_date, :trial_start, :trial_end, :status

 ...

end

This will make easier to show the user his remaining trial time in your application (without requiring a call to Stripe). Status means keeping track of the workflow: is this user active, trialling or overdue?

Change of plans

Of course, at some point, customer will change plans, from free to premium or the other way around, or even worse, going back and forth. This can create interesting situations, notably around trial period: what to do if a customer starts a trial of the premium version then go back to free at the end, then starts again?

By default, Stripe keeps the trial period time used, so if you specify a 30 days trial, the customer can use it in one or several piece, but will only even get 30 days. This means that he may not have any access to a trial if he decideS finally to go back to the premium plan.

You of course wants to keep your situation in sync with the one in Stripe. The Subscription object is a good place to implements those changes. Let’s say we only have our two plans (free and premium), we could define the moves as upgrade (free to premium) and downgrade (premium to free):

class Subscription

 def upgrade
   customer = retrieve_stripe_customer
   return if customer.subscription.plan == Plan.premium
   response = customer.update_subscription(plan: Plan.premium.stripe_id)
   update_from_event(response)
 end

 def downgrade
   customer = retrieve_stripe_customer
   return if customer.subscription.plan == Plan.free

   response = customer.update_subscription(plan: Plan.free.stripe_id)
   update_from_event(response)
 end

 def retrieve_customer
   Stripe::Customer.retrieve(user.stripe_customer_token)
 end

 def update_from_event(subscription)
   self.status = subscription.status
   self.current_period_start = to_date_time(subscription.current_period_start)
   self.current_period_end = to_date_time(subscription.current_period_end)
   self.trial_start = to_date_time(subscription.trial_start)
   self.trial_end = to_date_time(subscription.trial_end)
   self.plan = Plan.where(stripe_id: subscription.plan.id)
   self.save!
 end

end

In other words, we call Stripe to do the change, and update our own object based on the answer, to be sure to keep in sync.

Conclusion

Again, it is mostly the workflow that is important, and knowing what you want to do. Plans are a good solution to the recurring payment problem, as long as you keep your information up to date. Audit logs are important for this, and paper_trail is a very nice and easy to implement solution. As explained about the events, what is important is to have a good reaction to standard events, while getting notifications for the ones that requires manual intervention (for example a “charge failed”

See you for part 5 to speak about… Europe.

Enhanced by Zemanta




2 thoughts on “A Journey in Payment world – Part 4: we have a Plan

  1. Scott

    Great article! With paper_trail, are you still using “user has_many subscriptions”? Or did you move to “user has_one subscription” and relying on paper_trail to track changes to the subscription?

    Reply
    1. Martin Post author

      Hi Scott,
      Thanks for your comment. You are right: as we need the past contributions only for history purpose (debugging or support), paper_trail is enough. The relation in the code is now a one to one.

      Martin

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>