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"
crossorigin="anonymous"></script>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" crossorigin="anonymous">
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" crossorigin="anonymous">
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" 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..
Comments for Payments in Rails with Braintree