- 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
.
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 tutorialA
subscription
needs to be associated with aplan
, 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):
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:
Creating a new plan in the dashboard by going to
dashboard > plans > new
and entering in the plan details.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:
You can see that you have a few different parameters that you can associate with a plan
:
id
- this is a unique identifiername
- this is the short description of theplan
currency
- dictates what type of currency theplan
is going to useamount
- how much should be charged each time the subscription runsinterval
- 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 herestatement 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:
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.