October 11, 2016
Whilst you are writing your SaaS application, you are most likely driving towards what is probably the holy grail of payments, monthly subscriptions and recurring revenue. Of course this issue with this is that you need to write a bunch of code to make sure that people are getting what they have paid for, and people that don't pay you, well, don't. Even at it's most simple, this is still code that needs to be written, and even worse, maintained.
For you I have some excellent news. You don't have to do this. Stripe will do this for you.
By doing your payments with Stripe you have an entire suite of subscription software already at your disposal via their API, we'll just need to store a bit of information about the subscriptions, but largely, Stripe will take care of the rest. Let's take a look at setting it up.
In order for you to have subscribers, you need to have plans for them to subscribe to. You can create this on the Stripe dashboard, but you can also do it via the API. I prefer this method of doing it as you can create your local copy of the plan in your database as well, and this can save extra unnecessary calls to Stripe for information to display during signup. A great way to do this is to use your db/seeds.rb file and a local table to hold some information for us to use.
The database migration:
class CreatePlans < ActiveRecord::Migration def change create_table :plans do |t| t.string :stripe_id, null: false t.string :name, null: false t.decimal :display_price, null: false t.timestamps end end end
and the db/seeds.rb
plan = Stripe::Plan.create( :amount => 2900, :interval => 'month', :name => 'Basic Plan', :currency => 'aud', :id => 'basic' ) Plan.create(name: plan.name, stripe_id: plan.id, display_price: (plan.amount.to_f / 100)) Stripe::Plan.create( :amount => 4900, :interval => 'month', :name => 'Gold Plan', :currency => 'aud', :id => 'gold' ) Plan.create(name: plan.name, stripe_id: plan.id, display_price: (plan.amount.to_f / 100))
Now we can run:
bundle exec rake db:seed
and the application and the Stripe account will have the information that we need to show some basics about the subscription plan to potential users who might want to sign up.
Of course that means we need to be able to keep track of our subscribers. Depending on your system this may well be users, customers, what have you, but for this example I am just going to call the class Subscriber, and used this migration to create it:
class CreateSubscribers < ActiveRecord::Migration def change create_table :subscribers do |t| t.string :email, :password_digest, null: false t.string :stripe_customer_id, null: false t.datetime :subscribed_at, :subscription_expires_at, null: false t.integer :plan_id, null: false t.timestamps end add_index :subscribers, :plan_id, name: "plans_for_subsribers" add_index :subscribers, :subscribed_at, name: "subscribed_at_for_subscribers" add_index :subscribers, :subscription_expires_at, name: "expiring_subscritions_on_subscribers" end end
And this can give us a really really simple Plan and Subscriber classes:
class Plan < ActiveRecord::Base has_many :subscribers end
class Subscriber < ActiveRecord::Base has_secure_password belongs_to :plan end
Now that the application knows about plans and knows about subscribers, we need to be able to have people sign up. This is really the same as one of my previous posts about Taking payments with Stripe but let's quickly run through it.
Add your Stripe credentials to your app, making sure to use either your test or live credentials as required, I have them in a .env file that is loaded by dotenv
STRIPE_PUBLISHABLE_KEY=pk_test_your_key_here STRIPE_SECRET_KEY=sk_test_your_key_here
And making the publishable key available to stripe.js in your application layout
<!DOCTYPE html> <html> <head> <title>StripeExample</title> <%= stylesheet_link_tag 'application', media: 'all' %> <%= javascript_include_tag 'application' %> <script type="text/javascript" src="https://js.stripe.com/v2/"></script> <script type="text/javascript"> Stripe.setPublishableKey('<%= STRIPE_PUBLIC_KEY %>'); </script> <%= csrf_meta_tags %> </head> <body> <%= yield %> </body> </html>
Which brings us to the form for subscription. First we need a simple controller for this resource:
class SubscribersController < ApplicationController def new @plan = Plan.first @subscriber = Subscriber.new end end
In your own app, you will want to have a way for the subscriber to be able to choose what plan they wish to sign up to, but I don't care what Plan they are signing up to in this example, just that they can.
Now onto the form. This is the simplest form I could make, didn't even style it, but it will let us create a form at subscribers/new
<h2>Subscribe to the <%= @plan.name %></h2> <%= form_for @subscriber do |f| %> <p> <%= f.label :email %> <%= f.text_field :email %> </p> <p> <%= f.label :password %> <%= f.password_field :password %> </p> <p> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation %> </p> <p> <%= label_tag "Amount" %> <%= number_to_currency(@plan.display_price) %> </p> <span class="payment-errors"></span> <p> <%= label_tag "Card Number"%> <%= text_field_tag nil, nil, data: { stripe: "number" } %> </p> <p> <%= label_tag "Expiration (MM/YY)" %> <%= text_field_tag nil, nil, data: { stripe: "exp_month" }, size: 4 %> <span>/</span> <%= text_field_tag nil, nil, data: { stripe: "exp_year" }, size: 4 %> </p> <p> <%= label_tag "CVC"%> <%= text_field_tag nil, nil, data: { stripe: "cvc" }, size: 4 %> </p> <%= f.submit "Submit Payment", class: "submit" %> <% end %>
As in the previous post, we have to hijack the form submission, have stripe try to return a token and then continue on to submitting the form to the subscribers controller. Here is the JS again, but for a more detailed look at it, refer back to Taking a payment with Rails and Stripe, this is in subscribers.js
$(function() { var $form = $('#new_subscriber'); $form.submit(function(event) { // Disable the submit button to prevent repeated clicks: $form.find('.submit').prop('disabled', true); // Request a token from Stripe: Stripe.card.createToken($form, stripeResponseHandler); // Prevent the form from being submitted: return false; }); }); function stripeResponseHandler(status, response) { // Grab the form: var $form = $('#new_subscriber'); if (response.error) { // Problem! // Show the errors on the form: $form.find('.payment-errors').text(response.error.message); $form.find('.submit').prop('disabled', false); // Re-enable submission } else { // Token was created! // Get the token ID: var token = response.id; // Insert the token ID into the form so it gets submitted to the server: $form.append($('<input type="hidden" name="stripeToken">').val(token)); // Submit the form: $form.get(0).submit(); } };
Once the signup form has made it off to Stripe, returned us a token and made it back to us, we need a create action in our SubscribersController to be able to handle it, like so:
class SubscribersController < ApplicationController def new @plan = Plan.first @subscriber = Subscriber.new end def create @plan = Plan.first @subscriber = Subscriber.new(permitted_params) stripe_token = params[:stripeToken] if @subscriber.save_and_make_payment(@plan, stripe_token) redirect_to subscribers_path else render :new end end private def permitted_params params.require(:subscriber).permit(:email, :password, :password_confirmation) end end
Which then allows our Subscriber class to work with Stripe:
class Subscriber < ActiveRecord::Base has_secure_password belongs_to :plan def save_and_make_payment(plan, card_token) self.plan = plan if valid? begin customer = Stripe::Customer.create( source: card_token, plan: plan.stripe_id, email: email, ) self.stripe_customer_id = customer.id save(validate: false) rescue Stripe::CardError => e errors.add :credit_card, e.message false end else false end end end
In this code we are assigning our plan, to be saved locally in the app for reference, then we are using our card_token that we have gotten from stripe.js and creating a customer, saving the token as the payment source, and saving the stripe plan id as well. If this is successful Stripe will return us a customer object with a bunch of information, but what we want is the customer.id part, and saving this on our subscriber will allow us to update our subscriber record with any information that Stripe sends us about them. How do they do that? I'm glad you asked.
Stripe can send your application a whole host of information about your account, it's subscribers, events to do with them, loads of stuff. For us right now what we are interested in subscription expiration dates in case we need to restrict access to parts of the application based on subscription status. To do this we need to setup a webhook in your Stripe account. You can do that here. When you get there click on Add Endpoint, fill in the url:
https://example.com/stripe/webhooks
Choose test mode and just all events for now.
So now we need to setup this webhook to receive information from Stripe. First it needs a route:
Rails.application.routes.draw do resources :subscribers post '/stripe/webhooks', to: "stripe#webhooks" end
And also a controller that will receive the info:
class StripeController < ApplicationController protect_from_forgery :except => :webhooks def webhooks event_json = JSON.parse(request.body.read) event = Stripe::Event.retrieve(event_json["id"]) if event.type =~ /^customer.subscription/ subscriber = Subscriber.find_by(stripe_customer_id: event.data.object.customer) subscriber.subscribed_at = Time.at(event.data.object.start).to_datetime subscriber.subscription_expires_at = Time.at(event.data.object.current_period_end).to_datetime subscriber.save end render nothing: true end end
As this request is coming from Stripe and not our own application, we can skip the protect_from_forgery for our webhooks. This is not something that I would usually suggest, or recommend, but as we can receive the data from Stripe but then call back to them to make sure that this is indeed a Stripe Event, I feel a little better about this. That is done with this code:
event_json = JSON.parse(request.body.read) event = Stripe::Event.retrieve(event_json["id"])
Once we have verified that it is indeed a Stripe event we can do some stuff with the data. My code is checking for event.type as we are only interested in subscription events at this time. If it is our subscription event, we can go ahead and look up the Subscriber by their Stripe customer id and update them with the time they subscribed at, and when that subscription is going to expire. We can then use that subscription expiration date combined with the plan that they are on to decide what they have access to inside the application.
What we've built here is the very simple foundation of a Stripe subscription and some data syncing via webhooks. The control you can have over the webhooks is quite fine grained, and you can go quite far with setting up several different hooks to deal with different events that might be relevant to your applications, e.g. receiving invoices for customers, deleting plans and customers (deactivating?) if they are deleted in the Stripe dashboard and many many more. Whilst there are many different webhooks you could build, they all follow the same principle.
I hope that this was helpful, and if you have any questions, comments, feedback, please leave a comment, or if you'd prefer send me and email or hit me up on twitter.