Another monstrous blog post. I promise these will get shorter in the future.
For my Bloc project challenge, I was tasked with creating a simple social wiki application that, among other things, allowed users to select either a free or premium plan upon signup. As this suggests, free users have limited access to certain features, and premium users have to enter their credit card information and be charged at a monthly rate to access expanded features. Bloc recommended using Stripe, an awesome payments solution for SaaS apps.
I’ve never done anything like this before, so there was a fair amount of struggle involved. I’m going to cover the solution I implemented.
So first, I knew every user would belong to one of two plans, Free or Premium. Thus, I knew I would have to create a Plans table in my database with the appropriate relations between it and users. In Rails, I can simply run
rails g model Plan and pass in the data columns I want, in this case:
name:string price:decimal. This produces the migration:
1 2 3 4 5 6 7 8 9 10
And a model,
1 2 3
Now, how to associate? Rails makes this really easy by letting you declare very intuitive associations in the model itself. As I said before, many users will belong to a plan, thus a plan has many users. How do you let your application know this? By adding one line:
So now, plan.rb:
1 2 3 4
I want to go ahead and set the specific plans in my app, and I can do this by seeding the database. All that’s needed is to specify the plans in seeds.rb:
And now inside my user model, I also need to let my app know that a user
belongs_to :plan. Now some of you might have read this and thought to yourself, “Each user has one plan,” and other Rails beginners know that
has_one is a valid association. So can I use
has_one in this situation?
Not necessarily. There’s an important difference that Rails defines between these two, and the Rails docs explain it perfectly:
If you want to set up a one-to-one relationship between two models, you’ll need to add belongs_to to one, and has_one to the other. How do you know which is which? The distinction is in where you place the foreign key (it goes on the table for the class declaring the belongs_to association), but you should give some thought to the actual meaning of the data as well. The has_one relationship says that one of something is yours – that is, that something points back to you.
Once you think of it like this, it becomes clear why you’d need to declare that a user
belongs_to :plan, because the plan doesn’t just point back to your account, it points anyone with that account.
Notice I’m using Devise for authentication:
1 2 3 4 5 6 7 8 9 10 11 12
I won’t cover the wiki associations, but you’ll see that those are in there too. Can you guess what those associations do?
Now, my user model will know that each user belongs to a plan. To actually connect the user to the plan, as the passage from the Rails docs said, I need to store a foreign key relating to the specific plan on the user. To do that, I create a simple migration:
rails g migration add_plan_to_user plan_id:integer, which generates:
1 2 3 4 5
Now I have a column in the users database that references which plan the user belongs to.
Once I had this set up and working, I began thinking about how I wanted users to sign up.
Devise gives you views that you can customize to your liking, including registration views. I wanted the free form to not display any credit card information (and not interact with Stripe at all), and the premium form to display credit card info and subscribe the user to my premium plan.
At this point, I knew I was going to have to do a bit of research. Reading Stripe’s docs is always good, but for something a little quicker and easier to digest, I watched Ryan Bates’ Railscast on using Stripe. This provided a great starting point for me to start using Stripe and understanding all that was going on. I recommend watching the episode, because Ryan and Railscasts are awesome, and because I’ll skip over some of the finer details that he covers well.
In Ryan’s solution, he provides a handy jQuery function (written in CoffeeScript) to obtain credit card values that looks like this:
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
Check out the Railscast to learn exactly what this does. In the most basic sense, once the page has loaded, Stripe is prepared to accept a new payment. Once a user enters her credit card info and hits submit, the code processes the card, creating a token that Stripe needs to create a new customer. And there are error handlers inside the code as well.
To save the user to Stripe, and to let our application know the user is now a paying customer, we create a special save method that saves the payment information to our Stripe account, and Stripe gives us a customer ID that we can then save to our database. In Ryan’s example, he doesn’t save users, just subscriptions, and this is where things diverged for me. In my case, I had a user model generated with the Devise library, which comes with all of its own CRUD actions.
To accomplish saving the new subscriptions, Ryan created a
save_with_payment method in his subscription model:
1 2 3 4 5 6 7
And in his subscriptions controller, he can simply call this method in place of the normal
But I need to call this action when saving a user, which is being handled by Devise. So I’m going to need to override some of Devise’s methods.
To do this, I need to create my own registrations controller that inherits from the Devise registrations controller. I can set this up like this:
1 2 3
Devise provides default views for signing in, registrations and editing profiles. Because I want people to select a plan upon sign up, my form needed to allow the user to select a plan, and if it is the free plan, to not show credit card information, and if it’s the premium plan, to display the credit card fields.
To make this easier, I followed how many SaaS apps integrate plan selection with the sign-up process. I decided to put two buttons on my home page that would pass in the specific plan number to the sign in form:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
I defined those instance variables in the pages controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
You’ll notice I created a private method that loops over each plan, assigning them to either
@premium_plan, which I can then include in the
Once they select a plan that they want to sign up with, it takes them to a the default Devise registration form that includes the user’s selected plan in a hidden field tag and some conditional logic that prompts the user to input credit card information if the premium plan is selected.
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
At first, I only had one form and had simply put an if statement around the credit card fields. However, this produced a problem: Each time I tried signing up for a free account, it would fire off the Stripe confirmation. That’s because the registration form, whether free or premium, was given the same default ID generated by Devise. To account for this, I simply created two forms in the same view, specifying my own ID for the free plan form so that it wouldn’t activate the Stripe confirmation. It felt like a bit of an ugly solution, and a bit against the DRY principle, but it worked.
Now, to handle the specific sign ups, I knew I’d have to tweak Devise’s default controller actions. After researching the Devise source code.
In Devise’s registrations controller, they define a
build_resource method that builds a User model inside the
create actions. To override Devise’s methods, all you need to do is define methods with the same name and include whatever code you want to be added. I tweaked the
build_resource method a bit to specify a plan attribute for the user resource inside the new and create actions:
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
super to access all of the regular Devise code and then add my specifications on top. If plan parameters are passed in to the registration form, it sets the resource’s plan ID, and if the plan ID is ‘2’, then it calls the
save_with_payment method on the resource. Otherwise, it simply saves the user with a free plan.
I also added code to Devise’s
new method that ensures the plan parameters passed are only of plan ID “1” or “2” so that a person cannot pass into the URL a plan number that doesn’t correspond with any of the plans I’ve specified.
The next challenge was to allow free users to upgrade to a paid plan, and conversely, to allow premium users to downgrade to free.
First, upgrading to premium: Devise provides an edit user page. To handle this, I included a second form in the page allowing users to upgrade their plan (the form is the last code block below). Because free users aren’t signed up with Stripe at all, I decided I could simply use the same
save_with_payment method I had defined before.
I then needed to define an action to handle updating the plan. I placed this in the registrations controller, and specified in my form (last code block below) to put the data into the
1 2 3 4 5 6 7 8 9 10 11
To ensure that this works correctly, the if statement checks that a Stripe card token is generated and that the plan selected is only 2. If that checks out, the user’s attributes are updated and I call the
save_with_payment method on the user.
I also created a CoffeeScript file to handle the Stripe response, using the same code for the sign up action, but tweaking it to correspond to the update plan form.
I’m curious if there’s a better way to do this because technically, this goes against the principle that controller actions should be RESTful. I decided to just consider it semi-RESTful (it’s an update action after all).
Then, to downgrade from paid to free, I consulted the Stripe docs for canceling subscriptions and created a
cancel_user_plan method in the user model. This is simple enough – all that’s required is telling Stripe to retrieve the customer ID, and call Stripe’s
cancel_subscription method on the customer. So in the User model, I added a
cancel_user_plan method, passing in the user’s customer ID as an argument:
1 2 3 4 5 6 7 8
I added a simple form with the user’s customer token included in a hidden field. When the user hits the ‘Cancel Subscription’ button, it passes the customer token to the
cancel_plan action, which I added to the registrations controller:
1 2 3 4 5 6 7 8 9 10 11
Notice I pass
@user.cancel_user_plan. This is because I specified that the method would take an argument – the user’s Stripe customer token.
And here’s the form that handles these actions:
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