When you’re working with online payments there’s little room for error, especially when your application has a large number of active users, which is certainly the case with our current project. We decided to use Stripe as a payment gateway, as it has proved its reputation many times and is thus, very developer-friendly.  Recurring payments, such as subscriptions for example, can be a very useful feature and in this blog, I’m going to explain how to integrate subscriptions with Stripe into your Rails application, as well as one way you can test them locally using ngork.

Stripe concepts

Before I demonstrate how to integrate the subscription system, let’s introduce the important Stripe concepts that we’ll be using. Namely, these are customers, products, and subscriptions.  All of these are very well documented and you may learn more about them in Stripe’s API reference, but in a nutshell: The Plan object determines the currency, price, and the billing cycle.

The Customer object allows you to bind payments to a certain user and track them and we can bind a Customer to a Plan via the Subscription object. In plain terms, the Customers subscribe to a plan. It is important to understand that Stripe Subscriptions and Charges are two different things. Charges are used to process single payments and may or may not be tied to a Product whereas Subscriptions, are used for charging fees on a scheduled basis (weekly, monthly etc.) and MUST be connected to a Plan. Also, it is noteworthy that subscriptions don’t have any information about how much the customer is going to pay, it simply represents the relationship between a Customer and a Plan.

To give this blog some context, let’s say we’re building an online learning platform, where users can register and access online courses. In addition, let’s say that basic course components, such as video lectures, are free for all users, but users that subscribe, gain access to more cool features.

First and foremost, we need to create the premium plans. This can be done in two ways, one is using the Stripe API, and the other, which I’m going to do in this example, is going to your Stripe account’s dashboard, select “Subscriptions” from the left-hand menu, then “Products”, click the “New+” button and fill in the forms that will determine the pricing plan you want to use.

Basic examples

You’ll almost always want to store information regarding subscriptions in your database, so by generating Subscription and Transaction models, we can keep track of all of the important things. Therefore, the Subscription belongs to the User, and each time a Subscription payment is made, a corresponding Transaction record is created.

We’ll also generate a Subscriptions controller with, for now, two actions: new and create.

require 'subscribe_user_to_premium'
require 'subscriptions_toolkit'

class SubscriptionsController < ApplicationController
  def new

    #Always store your API key in environment variables
    Stripe.api_key = ENV["stripe_api_key"]
    @plan = Stripe::Plan.retrieve(SubscriptionsToolkit::PREMIUM_PLAN_ID)

  def create
    response = SubscribeUserToPremium.new.call(current_user, subscription_params, SubscriptionsToolkit::PREMIUM_PLAN_ID)

    if response.success?
      flash[:notice] = 'You have successfully subscribed to our premium plan!'
      flash[:alert] = 'Ooops, something went wrong!'

    redirect_to root_path


    def subscription_params
      params[:source] = params[:stripeToken]
      params[:email] = params[:stripeEmail]

      params.permit(:source, :email)


In the new action, we retrieve the plan object via Stripe API. The parameter that we used in the retrieve function, SubscriptionsToolkit::PREMIUM_PLAN_ID, is a string constant located inside the SubsciptionsToolkit module, and its value is the plan id, in our case ‘eschool-premium’.

Additionally, notice the SubcribeUserToPremium class, here we used the service object pattern and moved all the subscription logic into this class and therefore, made our controller slim and more readable, which we will discuss, later on.

The subscription form (the view for SubscriptionsController’s ‘new’ action)  looks like this. As ugly and basic as it gets, it will serve its purpose for this demonstration.

<%= form_tag subscriptions_path do %>
    <label class="amount">
      <span>Amount: <%= @plan.amount %></span>

  <script src="https://checkout.stripe.com/checkout.js" class="stripe-button"
    data-amount="<%= @plan.amount %>"
    data-email="<%= current_user.email %>"
<% end %>

Let’s get back to our service object, the inside of our SubscribeUserToPremium looks like this:

require 'stripe'

class SubscribeUserToPremium

  def call(user, subscription_params, plan_id)


      #Always store your API key in environment variables
      Stripe.api_key = ENV["stripe_api_key"]

      customer = Stripe::Customer.create(subscription_params)
      stripe_subscription = customer.subscriptions.create({plan: plan_id})


    rescue Exception => e
      ServiceResponse.new(nil, false, 'Something went wrong!')


Moving this logic to a ServiceObject, also enables us to reuse in places other than our SubscriptionsController, such as an API endpoint that can be used by mobile apps.

ServiceResponse is a very basic class, that helps with the neat implementation of the SO pattern.

class ServiceResponse
  attr_reader :success, :result, :errors

  def initialize(result = nil, success = true, errors = [])
    @result = result
    @success = success
    @errors = errors

  def success?


And there you have it, it’s as simple as that. The code I’ve shown so far will enable subscriptions to be created on Stripe, but earlier, I said that you’d almost always want to keep important information in your database. How can we do that?

That’s where we come to Stripe webhooks. Whenever an event (such as new subscription created, new subscription payment made, subscription canceled etc.) occurs, Stripe can notify a specific endpoint that we can configure. Let’s create a new controller and an endpoint for this purpose.

Rails g controller StripeWebhook

Add this to our routes.rb

 resources :stripe_webhook, path: '/', only: [] do

    collection do

      post :stripe



And the controller itself looks like this:

require 'json'
require 'create_subscripton_record'
require 'activate_subscription'
require 'create_transaction_record'
require 'send_payment_succeeded_mail'
require 'send_payment_failed_mail'

class StripeWebhookController < ApplicationController
  def stripe

    response = JSON.parse(request.body.read)
    data_object = response["object"]

    case response["type"]
    when "customer.subscription.created"
      create_subscripton = CreateSubscriptionRecord.new.call(data_object)
      return head :ok if create_subscripton.success?
    when "invoice.payment_succeeded"

      subscription = Subscription.find_by(stripe_id: data_object["subscription"])

      ActivateSubscription.new.call(subscription) unless subscription.active?

      create_transaction = CreateTransactionRecord.new.call(data_object)

      send_mail_notification = SendPaymentSucceededMail.new.call(data_object)

      return head :ok if create_transaction.success? && send_mail_notification.success?
    when "invoice.payment_failed"

      subscription = Subscription.find_by(stripe_id: data_object["subscription"])
      send_mail_notification = SendPaymentFailedMail.new.call(subscription)

      return head :ok if send_mail_notification.success?

As you can see, we first parse the response that we get from Stripe, and after that, we handle it according to its type. You can see that we use several new service objects here. The CreateSubscriptionRecord and CreateTransactionRecords services to create a corresponding records in our database, the SendPaymetSucceeded and SendPaymentFailedMail services that invoke the Application mailer and notify our user of his transactions and the ActivateSubscription service that updates a particular SubscriptionRecord to active status. (We did this because in our implementation when we first create the subscription record, we set it’s active status to false).

The endpoint has been configured, but we need to register it on Stripe now. Go to Stripe Dashboard -> Webhooks -> +Add Endpoint. Url to be called should be in the form of yourapplication_base_url/stripe . Also check ‘Select events to send’ and then check “customer.subscription.created”, “invoice.payment_succeeded” and “invoice.payment_failed” because we need only those 3 in this example.

How to test subscriptions on localhost using ngrok

It is always important to do a local check before you commit something, but how can we test our localhost app if it’s supposed to communicate with Stripe? It’s simple. All we need is to expose the port on which our app is running locally so that Stripe can access it, and that’s where Ngrok comes in hand. Go to their website and download the .zip file. Extract the file and copy the ngrok executable to the root of your rails project and add it to the .gitignore file. Now, go to the terminal and navigate to the root of your application (where we’ve moved the ngrok executable to) and type this: ./ngrok http 3000

The output should look like this

Now that we’ve exposed our app, we will repeat the endpoint configuration step explained earlier and set the forwarding address (in this case > http://304b8be1.ngrok.io) as yourapplication_base_url.

So our endpoint, in this case, will be http://304b8be1.ngrok.io/stripe

Important: Each time you run .ngrok http 3000, a different forwarding address will be generated!

There you have it, now feel free to play around with this because that is the best way to learn how something works.

Now you are all set to go 🙂

Software DevelopmentTech Bites
February 23, 2024

Background Jobs in Elixir – Oban

When and why do we need background jobs? Nowadays, background job processing is indispensable in the world of web development. The need for background jobs stems from the fact that synchronous execution of time-consuming and resource-intensive tasks would heavily impact an application's  performance and user experience.  Even though Elixir is…
Software DevelopmentTech Bites
December 22, 2023

In-memory Caching using Redis

The importance of computer memory utilization The CPU and memory are the main components of any computer system. Computer memory stores data and program instructions, temporarily or permanently, that the CPU processes. In CPU-intensive applications with large amounts of data being processed, memory usually becomes the bottleneck, resulting in a…
JavaScript Event Loop
Software DevelopmentTech Bites
December 21, 2023

JavaScript Event Loop

Often, when switching to JavaScript from any other language, developers face many frustrations. That’s because JavaScript has some unique features that may not be obvious or intuitive to those who have not used it before. When we first start learning JavaScript, we are told it executes the code line-by-line. console.log("Beginning");…

Want to discuss this in relation to your project? Get in touch:

Leave a Reply