How to Implement Subscriptions in Stripe
Learn how to use the Stripe payment system API to create subscriptions and implement recurring charges for your application.
Guide Tasks
  • Read Tutorial

Imagine that you have an application that needs to charge users for a monthly or annual membership. You wouldn't want to force the user to login and create a new charge every month, instead you would want them to be automatically billed. In this lesson we're going to extend the work performed in our Stripe Implementation Guide and see what we need to do to implement recurring payments.

You can see the code that we'll be starting out with here.

Difference Between Charges and Subscriptions in Stripe

When building on an API, I find it helpful to visualize how the data works, especially the relationship between different API calls. Below is a diagram that clarifies the difference between Charges and Subscriptions.

large

As you can see:

  • A charge can be created on the fly for a single payment, such as we did during the Stripe implementation guide tutorial

  • A subscription needs to be associated with a plan, this is so Stripe knows how much to charge the customer.

This may seem odd if you're new to the Stripe API, so I like to think about it in real world terms. A subscription is like your mobile phone subscription, it is simply a connector between the user (you) and the monthly wireless plan that you pay for. By itself the subscription doesn't know anything about the how much you're going to pay or any details related to the product. Whereas the plan is the product itself. If you sign up for a 10GB monthly wireless plan with your cellular carrier, the plan stores the information, such as the monthly fee, the currency type, etc. The plan doesn't know anything about the user, it is a high level object that simply holds the information about itself. Here is a diagram showing how the relationships works (at a high level):

large

This isn't exactly the way the data is modeled in Stripe since Stripe combines the customer creation process with the subscription process, however it's the way I like to think about it since I feel it's important to decouple the customer from the subscription. The reason for this is because it's pretty standard for applications to have the requirement to charge customers a monthly fee, while simultaneously allowing the same customer to purchase one-off type of products. An example of this would be Amazon. Amazon charges a monthly subscription to customers for their Amazon Prime membership, while still letting the users purchase single items anytime they want.

Creating a Plan

Stripe gives a few different ways to create a plan:

  1. Creating a new plan in the dashboard by going to dashboard > plans > new and entering in the plan details.

  2. Using the API to dynamically creating plans.

Which one should you choose? It's completely based on your application's requirements. If the website has a simple membership structure and won't change regularly it would make the most sense to create the plan in the dashboard. If the application needs to give users the ability to create plans on the fly it will be necessary to build the plan generator directly into the application. Let's take two applications as case studies:

  • Hitting.com - this eCommerce application has a simple membership structure that doesn't change regularly, so there was no need to spend the time to implement the API integration.

  • DevCamp - the DevCamp platform gives instructors the ability to dynamically create and customize their own packages that they sell to students, therefore it was VERY necessary to integrate the plan generator API for this use case.

In this lesson we'll create the plan using the dashboard since to properly create a comprehensive plan/package management system would distract from building the Stripe subscription feature.

To get started, navigate to Stripe and go to dashboard > plans > new, on the new page I'm going to enter in the following information:

large

You can see that you have a few different parameters that you can associate with a plan:

  • id - this is a unique identifier

  • name - this is the short description of the plan

  • currency - dictates what type of currency the plan is going to use

  • amount - how much should be charged each time the subscription runs

  • interval - here you can select how often the subscription should run (daily, weekly, monthly, etc)

  • trial period - if you want to let users sign up for a service but not charge their card for a period of time you can place that optional parameter here

  • statement description - allows you to customize what is shown on the customer's bill

Now that we have a plan created we can have users sign up for subscriptions.

Implementing Subscriptions

Following our the Amazon example we discussed above, we're going to build in a membership feature into the application. We can easily extend the functionality of our charge process since creating subscriptions is very similar to creating charges. Some of the items we'll be putting in place are simply to mock a normal checkout process, but they aren't things that would be implemented in a production site, I'll make a note when this occurs so you can know what you can implement in a real site and what features are simply being put in place for the sake of making this example work.

Let's begin by creating a new method in our StripeTool module to managing memberships/subscriptions:

# app/models/concerns/stripe_tool.rb

  def self.create_membership(email: email, stripe_token: stripe_token, plan: plan)
    Stripe::Customer.create(
      email: email,
      source: stripe_token,
      plan: plan
    )
  end

This is very similar to the create_customer method, except this code will generate a charge and subscription, which is why it needs to be separate from the create_customer method that is passed to the create_charge method.

Now let's add in a before_action and private method to see the plan, this is mocking the behavior of setting the plan ID, usually this value would be stored in a shopping cart cookie or session value.

# app/controllers/charges_controller.rb

before_action :set_plan

private

    def set_plan
      @plan = 9999
    end

I'm hard coding in the id value for the plan we created in the Stripe dashboard, you can fill in this information with whatever ID you used.

Now let's update the view. This is also going to mock a shopping cart feature, we'll simply place a hidden field tag looking for a subscription parameter. Essentially what this will do is store a value passed to the URL:

<!-- app/views/charges/new.html.erb -->

<%= form_tag charges_path do %>
  <article>
    <% if flash[:error].present? %>
      <div id="error_explanation">
        <p><%= flash[:error] %></p>
      </div>
    <% end %>
    <label class="amount">
      <span>Amount: <%= pretty_amount(@amount) %></span>
    </label>
  </article>

  <%= hidden_field_tag :subscription, value: params[:subscription] %>

  <script src="https://checkout.stripe.com/checkout.js" class="stripe-button"
          data-key="<%= Rails.configuration.stripe[:publishable_key] %>"
          data-description="<%= @description %>"
          data-amount="<%= @amount %>"
          data-email="<%= current_user.email %>"
          data-bitcoin="true"
          data-locale="auto"></script>
<% end %>

Ugly yes, but it will let us test between charges and subscriptions without having to duplicate code. Lastly let's update the controller so it's looking for this new hidden_field and deciding between creating a charge or a subscription. Update the create action as I have below:

# app/controllers/charges_controller.rb

  def create
    if params[:subscription].include? 'yes'
      StripeTool.create_membership(email: params[:stripeEmail], 
                                   stripe_token: params[:stripeToken],
                                   plan: @plan)
    else
      customer = StripeTool.create_customer(email: params[:stripeEmail], 
                                            stripe_token: params[:stripeToken])
      charge = StripeTool.create_charge(customer_id: customer.id, 
                                      amount: @amount,
                                      description: @description)
    end

    redirect_to thanks_path
  rescue Stripe::CardError => e
    flash[:error] = e.message
    redirect_to new_charge_path
  end

I added the conditional if params[:subscription].include? 'yes' which is checking for our hidden field value and if it is set to yes than it will call the create_membership method and pass in the @plan value, if not it creates a normal charge. You can test this out by starting up the rails application and navigating to localhost:3000/charges/new and going through the ordering process like normal. If you check the stripe dashboard you'll see that everything worked like before. Now if you navigate to localhost:3000/charges/new?subscription=yes and go through the same steps you'll see that instead of creating a single charge it creates a charge and a subscription. You can confirm this by going to the Stripe dashboard and clicking on the subscription that you created earlier. You can see this below:

large

I could have made this easy and simply created a different controller or method that managed subscriptions (I was tempted to do so), however I wanted to give you the closest thing to a real world implementation. And in a production app you wouldn't want to have one checkout page for subscriptions and one for products. Hopefully this also illustrates how important it is to pull out methods like we did with our StripeTool concern. With only a few lines of code we were able to enable our form to dynamically create subscriptions or charges based on parameters we supplied it with, with very little code duplication.

Resources