DEV Community

Cover image for Integrate Stripe in your Ruby on Rails app
Oksana Ivanchenko
Oksana Ivanchenko

Posted on

Integrate Stripe in your Ruby on Rails app

Creating a workspace

We will start by creating a Ruby on Rails application

$ rails new tuto
$ cd tuto
$ bundle install

Then, we will generate a controller named Billing with the help of

rails generate

$ rails generate controller Billing

After that, we're going to add route in config/routes.rb :

root 'billing#index', as: :billing 

To finish, we have to create an empty action index in app/controllers/billing_controller.rb:

class BillingController < ApplicationController

  def index
  end

end

In app/views/billing we will create a file index.html.erb where we will add a button that will show the "Stripe Checkout" modal, that will let us link a credit card to a user. For this tutorial I will be using (as always) the bootstrap library. If you want to use it too, don't forget to add it in your app. If you want to learn how to do it, you can read this article. So in app/views/billing/index.html.erb:

<div class="container">
  <div class="row">
    <h1 class="col-md-12 mt-5 text-center">
      Are you ready to pay?
    </h1>
  </div>
  <div class="row">
    <div class="col-md-12 mt-4 text-center">
      <button class="btn btn-primary">Let's go </button>
    </div>
  </div>
</div>

For now, we will leave our button like this and later we will change it using a Rails syntax.

Creating a User with Devise

Now, we will implement basic authentication with the help of the "devise" gem. We want only connected users to be able to access the billing page. Start by adding the following line in your Gemfile:

gem 'devise'

To setup devise we have to type this in the terminal:

$ bundle install
$ rails g devise:install

After running these two commands, the terminal will display instructions to configure devise. It says that we have to add this line of code in config/environments/development.rb:

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

The next step is to create a User model:

$ rails g devise User
$ rails db:migrate

That's it. We added authentication with devise. Now we are ready to create our first user. We need to run 'rails s' in terminal and go to the following page: localhost:3000/users/sign_up. After we created the account it will redirect us automatically to the root page at localhost:3000.

We now want that only signed-in users have access to localhost:3000. For this in app/controllers/billing_conroller.rb we need to add:

class BillingController < ApplicationController
  before_action :authenticate_user!

  def index
   @user=current_user.email
  end

end

So before any action in billing controller it will check if user is signed in. Also in the index action I added a global variable @user which takes email of current user. I will use it in app/views/billing/index.html.erb:

<div class="container">
  <div class="row">
    <div class="col-md-12 mt-4 text-center">
      <h1> Welcome  <%= @user %></h1>
    </div>
  </div>
  <div class="row">
    <h2 class="col-md-12 mt-5 text-center">
      Are you ready to pay?
    </h2>
  </div>
  <div class="row">
    <div class="col-md-12 mt-4 text-center">
      <button class="btn btn-primary">Let's go </button>
    </div>
  </div>
</div>

To check if everything works we will go to localhost:3000 and clear our cookies. After refreshing page you should be automatically redirected to the Sign in page.

Integrating Stripe

Adding the Stripe credentials

Firstly, you have to sign up on Stripe.
Then, you can find the development and production keys for Stripe on your dashboard, under developers => API keys. The keys that you will see now are for production. To see development keys click to 'view testing data'.
alt text

Now we will add the keys to the rails encrypted credentials. To do this, I use the GNU nano editor which is installed by default on MacOS and Linux.

$ EDITOR=nano rails credentials:edit

alt text

Here we add our keys for development and production.

stripe:
  development:
    publishable_key: 'pk_test_...'
    secret_key: 'sk_test_...'
  production:
    publishable_key: 'pk_live_...'
    secret_key: 'sk_live_...'

Installing Stripe

You need also to add the stripe checkout javascript in app/views/layouts/application.html.erb:

<%= javascript_include_tag 'https://checkout.stripe.com/checkout.js' %>

Also in config/enviroments/development.rb we add:

config.stripe.secret_key = Rails.application.credentials.stripe[:development][:secret_key]
config.stripe.publishable_key = Rails.application.credentials.stripe[:development][:publishable_key]

And in config/enviroments/production.rb:

config.stripe.secret_key = Rails.application.credentials.stripe[:production][:secret_key]
config.stripe.publishable_key = Rails.application.credentials.stripe[:production][:publishable_key]

Finally, we will add the stripe gem in our Gemfile

gem 'stripe-rails'

And run 'bundle install' in terminal.

Setting up the route

Now we are returning to config/routes.rb and add to the file:

get '/card/new' => 'billing#new_card', as: :add_payment_method`

Linking a card to a user

In app/controllers/billing_controller.rb we are creating new action:

def new_card
    respond_to do |format|
      format.js
    end
  end

It sends us to new_card.js.erb which we will create in app/views/billing. So in app/views/billing/new_card.js.erb :

var handler = StripeCheckout.configure({
    key: '<%= Rails.application.credentials.stripe[Rails.env.to_sym][:publishable_key] %>',
    //get a publishable key that we put in editor depending on environment: production or development
    locale: 'auto',
    //handle translation
    name: "upload.express",
    description: "Add your credit card information",
    email: "<%= current_user.email %>",
    panelLabel: "Add payment method",
    allowRememberMe: false,
    token: function (token) {
        var form = document.getElementById('billing-create-payment-method');
        //we will create element with this id in the next step
        var hiddenInput = document.createElement('input');
        hiddenInput.setAttribute('type', 'hidden');
        hiddenInput.setAttribute('name', 'stripeToken');
        hiddenInput.setAttribute('value', token.id);
        //creating an <input type="hidden" name="stripeToken" value="<id>"/>. We will need this information in the next steps to link a user to his card 
        form.appendChild(hiddenInput);
        //adding this input when we use a form.
        form.submit();
    }
});

handler.open();

window.addEventListener('popstate', function() {
    handler.close();
});

Then we change our button in app/views/billing/index.html.erb into a form with id "billing-create-payment-method":

<%= form_tag id: "billing-create-payment-method" do  %>
        <%= link_to "Let's go", add_payment_method_path, remote: true, class: "btn btn-primary" %>
      <% end %>

After that in terminal we run 'rails s' and after clicking on our button we should have something like this:
alt text

Almost there! Now we need to associate the current user to the given card. To do this we have to add a new column stripe_id of type string in the User model by running this in terminal:

$ rails g migration AddStripeIdToUsers stripe_id:string
$ rails db:migrate

In config/routes.rb we are creating a new route:

post "/card" => "billing#create_card", as: :create_payment_method

Now in controller in app/controllers/billing_controller.rb we are creating action create_card:

def create_card 
    respond_to do |format|
      if current_user.stripe_id.nil?
        customer = Stripe::Customer.create({"email": current_user.email}) 
        #here we are creating a stripe customer with the help of the Stripe library and pass as parameter email. 
        current_user.update(:stripe_id => customer.id)
        #we are updating current_user and giving to it stripe_id which is equal to id of customer on Stripe
      end

      card_token = params[:stripeToken]
      #it's the stripeToken that we added in the hidden input
      if card_token.nil?
        format.html { redirect_to billing_path, error: "Oops"}
      end
      #checking if a card was giving.

      customer = Stripe::Customer.new current_user.stripe_id
      customer.source = card_token
      #we're attaching the card to the stripe customer
      customer.save

      format.html { redirect_to success_path }
    end
  end

We are changing a bit our app/views/billing/index.html.erb in order to call an action create_card when we submit our form.

<%= form_tag create_payment_method_path, id: "billing-create-payment-method" do  %>
        <%= link_to "Let's go", add_payment_method_path, remote: true, class: "btn btn-primary" %>
      <% end %>

Quickly, we are going to add the success page. Create a route, empty action and view.

In config/routes.rb:

get '/success' => 'billing#success', as: :success

In app/controllers/billing_controller.rb:

def success
end

In app/views/billing/success.html.erb:

<div class="container">
  <div class="row">
    <div class="col-md-12 mt-4 text-center">
      <h2>You successfully linked a credit card to your account</h2>
    </div>
  </div>
</div>

Now run in terminal 'rails s'. Click on 'Let's go' on the website. For testing purposes you can use the card 4242 4242 4242 4242 with random CVC code and date. After submitting we should be redirected to our success page. And if we go on Stripe in Customers (don't forget to switch to test data) we should see an email and card info of our user.
alt text

Now we need make customers to actually pay after registering a card. It means that we have to subscribe our customer.

Subscription

First, we need to create a product. The easiest ways to do it is through the Stripe dashboard. Then, we will attach 2 pricing plans to the product. For example in our product upload.express you can pay 7€ per month, or you can subscribe yearly for 60€. We need to go to our Stripe dashboard => Billing => Products and click on the button "New". Create your product with 2 pricing plan. I suggest you to choose explicit definition like "monthly" and "yearly" for your pricing plan nickname. If you add a yearly plan, don't forget to set the interval on 'yearly'. To add a pricing plan, you need to click on the product you just created and then click on the button "add a pricing plan".

Now we want to fetch the pricing plans we created in our application. We will be doing it on our page "success". So in app/controllers/billing_controller.rb in success:

def success
    @plans = Stripe::Plan.list.data
  end

Now we need to subscribe our user to the plan that he will choose. So we need to create a new action which will link our customer to the plan and create a subscription.

In config/root.rb:

post '/subscription' => 'billing#subscribe', as: :subscribe

In app/views/billing/success.html.erb we add:

<div class="row">
  <div class="col-md-12 mt-3 text-center">
    <h3>Now you need to choose your plan</h3>
  </div>
</div>
<!-- creating a form -->
<%=form_tag subscribe_path, method: :post do %>
<div class="row">
  <div class="col-md-4"></div>
  <div class="col-md-4 text-center">
    <div class="form-group">
      <select class="form-control" name="plan_id">
        <% @plans.each do |plan| %>
        <option value="<%= plan.id %>"><%= plan.amount/100 %>/€ <%= plan.nickname %></option>
        <!-- we pass id chosen by custmer as a value  to use it for subscription -->
        <%end%>
      </select>
    </div>
  </div>
  <div class="col-md-4"></div>
</div>
<div class="row">
  <div class="col-md-12 mt-2 text-center">
    <%= submit_tag 'Save changes', class: "btn btn-primary" %>
  </div>
</div>
<% end %>

In app/controllers/billnig_controller.rb:

def subscribe
      if current_user.stripe_id.nil?
        redirect_to success_path, :flash => {:error => 'Firstly you need to enter your card'}
        return
      end
      #if there is no card

      customer = Stripe::Customer.new current_user.stripe_id
      #we define our customer

      subscriptions = Stripe::Subscription.list(customer: customer.id)
      subscriptions.each do |subscription|
        subscription.delete
      end
      #we delete all subscription that the customer has. We do this because we don't want that our customer to have multiple subscriptions

      plan_id = params[:plan_id]
      subscription = Stripe::Subscription.create({
                                                     customer: customer,
                                                     items: [{plan: plan_id}], })
   #we are creating a new subscription with the plan_id we took from our form

      subscription.save
      redirect_to success_path
    end
  end

It's ready. Now we will check our app. Choose a pricing plan, submitting it.
To check everything worked, go to Stripe, Dashboard => Billing => Subscriptions. Our subscription should be here. If you change your plan and take a look at Stripe, the subscription should change as well.

That's it. You integrated Stripe on your Rails app. Congratulations!!!

P.S. It's my first technical article, I hope it was understandable and you enjoyed it!

Top comments (13)

Collapse
 
yverbytskyi profile image
Yurii Verbytskyi

Hi, what's the point of defining instance variable in BillingController

  @user=current_user.email
Enter fullscreen mode Exit fullscreen mode

if you can use

current_user.email
Enter fullscreen mode Exit fullscreen mode

directly in views using Rails?
P.S. It's strange to see this part of code in turorials:

  subscriptions.each do |subscription|
    subscription.delete
  end
Enter fullscreen mode Exit fullscreen mode

Instead of this one:

  subscriptions.each(&:delete)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
wbouvier profile image
Wbouvier

[gestionstock]$rails server
=> Booting WEBrick
=> Rails 4.1.16 application starting in development on 0.0.0.0:3000
=> Run rails server -h for more startup options
=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)
=> Ctrl-C to shutdown server
Exiting
/Users/wbouvier/Sites/Rails/gestionstock/config/environments/development.rb:33:in block in <top (required)>': undefined methodcredentials' for #Gestionstock::Application:0x007fb2... (NoMethodError)

Collapse
 
daneb profile image
Dane Balia • Edited

@ksushiva - thank you so much for this article. Was very valuable in a project I was working on.

PS. There is a small typo (as per below) and an extra "end" for the subscribe method:

In config/root.rb:
Collapse
 
wbouvier profile image
Wbouvier

Bonjour,
Je dois intégrer Stripe à un site déjà existant. En suivant les étapes, après le 1er lancement du rails s (After that in terminal we run 'rails s' and after clicking on our button we should have something like this: )
j'ai me message d'erreur en pièce jointe.
Pourriez-vous me donner un coup de main ?
Merci.
Wilfried.

Collapse
 
ksushiva profile image
Oksana Ivanchenko

Hello, votre version de rails est 4.1.1. Les "encrypted credentials" de rails ne sont disponibles qu'avec rails 5.2+. Vous pouvez utiliser des variables d'environnement à la place pour enregistrer vos informations d'identification stripe.

Hi, Your rails version is 4.1.1. The rails encrypted credentials are only available with rails 5.2+. You can use environment variables instead to save your stripe credentials.

Collapse
 
wbouvier profile image
Wbouvier

Hi,

cela fonctionne.
Merci beaucoup, je continue les étapes.

Bon week-end.
Wilfried

Collapse
 
jalovatt profile image
Adam Lovatt

Great tutorial. One problem - this line in "Linking a card to a user" didn't work for me:

<%= form_tag create_payment_method_path, id: "billing-create-payment-method" do %>

It ended up creating a form with action=/id.... :( This worked:

<%= form_tag create_payment_method_path, :id => "billing-create-payment-method" do %>

Collapse
 
ammar299 profile image
ammar299

i want to get subscription please help.. here is my code
module BillingHelper
def check_if_user_already_subscribe(plan_id)
customer = Stripe::Customer.new current_user.stripe_id
subscriptions = Stripe::Subscription.list(customer: customer.id)
plan_ids = get_from_subcriptions
if plan_ids.include?(plan_id)
true
else
false
end
end
end

Collapse
 
francelwebdev profile image
Francel

Bonsoir Oksana Ivanchenko,
Merci baucoup pour ton tutoriel. Merci encore, je suis africain pasionné par le développement web. j'ai appris ruby et rails par moi même et j'ai seulement les bases de rails. pourrai tu m'epprendre les techniques avancés de rails ? SVP, apprenez moi quelques technique avancés sur rails afin que je puisse acquérir plus de connaissance et me perfectionner.
Merci encore Oksana Ivanchenko,
Francel

Collapse
 
david_j_eddy profile image
David J Eddy

Excellent article Oksana. I will actually have to do this exact thing very soon.

Collapse
 
booz profile image
bo-oz

Any thoughts on if/how to store transactions in a local Rails model? Or do you totally depend on those objects/records living in Stripe?

Collapse
 
aonomike profile image
Mike Aono

Thanks so much for this tutorial, really great @ksushiva ! Could you know of a step to step implementation to have this implemented for the current SCA improvements. Apologies for my english

Collapse
 
eugenedm profile image
eugenedm

Hi Oksana! What happens if the person cancels the payment / card for the upcoming month? How do you handle that?

Thanks for your article! :) Good work.