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
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).
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:
Adding --with-changes to the db migration (rake db:migrate --with-changes) allows you to diff versions:
This is just scratching the surface of paper_trail that has many other usages than for billing information, but it is certainly useful here.
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.
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.
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.