Today, we will see how to incorporate payments in our Rails application.

Project

Before diving into the code, let's try to understand how our code should work. In our example, we will use Braintree, but we could use another payment service. Braintree is a payment service that allows us to integrate with Paypal and use credit card payments. It's really simple. Our code should be as generic as possible to allow us to do that. Keep this in mind.

Let's create our rails project.

rails new rails-payments

Let's see if our project works correctly. Starting our server, we should see in our page You're on Rails. And we are!

Setting up braintree

Let's add Braintree in our project.

vim Gemfile

Add the Braintree gem into our Gemfile.

gem 'braintree'

Then do a bundle install.

bundle install

Braintree has some keys. We will add the keys in a initializer.

vim config/initializers/braintree.rb

And here we will get these keys from our environment variables.

Braintree::Configuration.environment = ENV['BT_ENVIRONMENT']
Braintree::Configuration.merchant_id = ENV['BT_MERCHANT_ID']
Braintree::Configuration.public_key = ENV['BT_PUBLIC_KEY']
Braintree::Configuration.private_key = ENV['BT_PRIVATE_KEY']

And let's create our .envrc file. Remember, I'm using direnv to handle this. You can export these variables as you want.

vim .envrc

In our .envrc file, we will have the environment, merchant id and the two keys from Braintree. I've already set up my braintree application and these variables come from there.

export BT_ENVIRONMENT='sandbox'
export BT_MERCHANT_ID='merchant id'
export BT_PUBLIC_KEY='public key'
export BT_PRIVATE_KEY='private key'

This should set up Braintree in our application.

Creating our project

We will have a simple website for products. We will list our products and in the show page we will "pay" for the product.

Let's create our controller and our model. In our controller, we will also create a method show and index.

rails g controller products show index
rails g model product

We will use Postgres as our database.

Let's add postgres to the gemfile

vim Gemfile
gem 'pg'

Let's change our database.yml file, to be used with Postgres.

default: &default
adapter:  postgresql
host:     localhost
encoding: unicode
pool:     5
username: <%= ENV['PGUSER'] %>
password: <%= ENV['PGPASSWORD'] %>

staging:
  <<: *default

development:
  <<: *default
  database: rails-payments-development

test:
  <<: *default
  database: rails-payments-test

production:
  <<: *default
  database: rails-payments-production

Our migration:

class CreateProducts < ActiveRecord::Migration[5.1]
  def change
    create_table :products do |t|
      t.string :name
      t.money :amount, default: 0
      t.string :image_url
      t.timestamps
    end
  end
end

Here we are using the money type, it's possible to use this type with Postgres.For the product image, we will use some images from Amazon and our product will be from Amazon too.

I've chosen two products we will have in our website. As this is just for testing, I will put these products in our seeds file.

vim db/seeds.rb

Our products will be a Corsair Gaming Keyboard and a Digital Alarm.

Product.create(name: 'Corsair Gaming K55 RGB Keyboard, Backlit RGB LED',
               amount: 49.99,
               image_url: 'https://images-na.ssl-images-amazon.com/images/I/71pN1N3hyDL._SL1500_.jpg')

Product.create(name: 'RCA Digital Alarm Clock with Large 1.4" Display',
               amount: 10.65,
               image_url: 'https://images-na.ssl-images-amazon.com/images/I/81iIBzlq9IL._SL1500_.jpg')

Ok, let's restart our db and run our seeds. Here we will drop our database(if you didn't create it), create the database again, run all migrations and run the seeds.

rake db:drop && rake db:create && rake db:migrate && rake db:seed

Let's see if it works. Let's open rails console and check the products we have.

rails c
Product.all

And here we can see the two products we have.

Working with our controller

Let's go back to our Product Controller. For the moment, we only have two methods. They will be pretty simple. In the show method, we will just get the product by id. In the index, we will get all the products we have in our database.

class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])
  end

  def index
    @products = Product.all
  end
end

And we can create our view for those:

<div>
  <h1>All Products</h1>

  <% @products.each do |p| %>
    <div>
      <img src="<%= p.image_url %>" width="100" height="100" />
      <p>
        <%= link_to p.name, product_path(p) %>
        $<%= p.amount %>
      </p>
    </div>
  <% end %>
</div>

In our view show, we will just show the product information.

<div>
  <h1><%= @product.name %></h1>
  <div>
    <img src="<%= @product.image_url %>" width="100" height="100" />
  </div>
  <b>$<%= @product.amount %></b>
</div>

To finish that, let's change our routes:

Rails.application.routes.draw do
  resources :products, only: %i[index show]
  root to: 'products#index'
end

Let's see what it looks like. As you can see, we have our index listing the two products we have and the show for this product.

Improving the layout

As you can see, the page doesn't look great. Let's add bootstrap in our application to make it look better. We will be adding Bootstrap CDNs, we will not set up entirely the css, for the moment, we will just use the CDNs from bootstrap and use it.

  <head>
    <title>RailsPayments</title>
    <%= csrf_meta_tags %>
    <script
      src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha256-k2WSCIexGzOj3Euiig+TlR8gA0EmPjuc79OEeY5L45g="
    crossorigin="anonymous"></script>

  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

  <!-- Optional theme -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">

  <!-- Latest compiled and minified JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>

We will also add a div in our main content.

Let's refresh it and see how it looks now. It looks better!

Adding payment

Let's add the payment bits. First of all, we will need to use a variable from rails inside our JavaScript. We will do that using the gem gon.

Let's add that!

vim Gemfile
gem 'gon'

And let's do a bundle install.

We need to set up gon in our layout application. With this, all our pages can use gon.

vim app/views/layouts/application.html.erb

And we will add Gon::Base.render_data before the csrf_meta_tags from Rails.

<!DOCTYPE html>
<html>
  <head>
    <title>RailsPayments</title>
    <%= Gon::Base.render_data %>
    <%= csrf_meta_tags %>

In our product show page, we will add braintree drop in interface. The Drop In interface, as you can see in the documentation, it's something ready to work with credit card. It provides us a simple interface.

<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
<script>
 braintree.setup(gon.client_token, 'dropin', { container: 'dropin', form: 'buy_product_form' });
</script>
<div>
  <h1><%= @product.name %></h1>
  <div>
    <img src="<%= @product.image_url %>" width="100" height="100" />
  </div>
  <b>$<%= @product.amount %></b>

  <div class="form-container">
    <h2>Checkout</h2>
    <%= form_tag products_buy_path, id: 'buy_product_form' do%>
      <%= hidden_field_tag :amount, @product.amount %>
      <p>Please enter your payment details:</p>
      <div id="dropin"></div>
      <%=submit_tag "Pay #{@product.amount}$", class: "btn" %>
    <%end%>
  </div>
</div>

The drop in is something from braintree that is not very customized, but it's easy to use. We set this up using the gon.clien_token that we will set in our controller, we also set the form and we set the container div, in our case, it will be dropin. We need to pass to the request the amount of this transaction. So, we are passing as a hidden field.

We didn't implement this method in our controller yet. Let's do that!

class ProductsController < ApplicationController
  def show
    gon.client_token = generate_braintree_client_token
    @product = Product.find(params[:id])
  end

  def index
    @products = Product.all
  end

  def buy
    result = Braintree::Transaction.sale(
        amount: params[:amount],
        payment_method_nonce: params[:payment_method_nonce],
        options: {
          submit_for_settlement: true
        }
      )
    if result.success?
      redirect_back(fallback_location: root_path, notice: 'Everything was fine!')
      else
      redirect_back(fallback_location: root_path, notice: 'Something went wrong! :/')
      end
    end
  end

In our method buy, we will use the Braintree SDK and call the method sale from Transaction. We pass the amount and the payment_method_nonce that our form gives us. If the transaction was successful, we redirect to the last page, sending an appropriate message.

In our show method, we will set the gon variable.

gon.client_token = generate_braintree_client_token

Let's create our method generate_braintree_client_token. We will do that in our ApplicationController, because in the future other controllers might be using this as well.

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  def generate_braintree_client_token
    Braintree::ClientToken.generate
  end
end

Going back to our controller, we are using flash messages. Let's set up flash messages in our application layout. We will add that before the yield.

<body>
  <div class="container">
    <% flash.each do |name, msg| %>
      <%= content_tag :div, msg, class: "alert alert-info" %>
    <% end %>
    <%= yield %>
  </div>
</body>

Let's check our routes and add the buy route. This will be a post.

Rails.application.routes.draw do
  resources :products, only: %i[index show]
  post '/products/buy' => 'products#buy'
  root to: 'products#index'
end

Let's restart our server, because gon needs it to work.

Buying

Let's try to buy something. Let's go to our index, choose a product. We will use a fake card from Braintree. Once we do that, we should see a success message.

Let's do that! Let's put the card and any expiration date in the future and... Let's pay it! Boom! Let's see if this transaction is also in the sandbox from braintree. It's here!

Summary

Today we saw how to implement braintree in a Rails application. We saw how easy the implementation was..

Resources