Using Braintree hosted fields with Ruby on Rails

Using Braintree Hosted Fields with Rails

Archived from the original post (2017-09-27). Lightly converted to Markdown; links and examples preserved where possible.

While it is possible to create your own credit card process in a Ruby on Rails app, due to the PCI compliance rules, it is just safer to use something like Stripe or Braintree.

I have used Stripe before and it is very easy to set up and use. Stripe now has elements which can be used to create a custom credit card form that can be styled to look exactly like your app.

Previous to this, the dropin solutions were good but just did not look like the rest of the application.

After reading up a bit, I decided that I wanted to try out Braintree hosted fields.

Below is the finished form that I created. I wanted to be able to get more user information at the same time as charging the credit card.

TL/DR For the app that I made for this tutorial, it is at https://github.com/brobertsaz/rails_braintree_hosted_fields

Braintree Setup

First thing that you need to do is head on over to Braintree and sign up for sandbox account. Don’t worry, it’s free.

After you sign up you will need to copy this info:

Braintree::Configuration.environment = :sandbox
Braintree::Configuration.merchant_id = 'qwertyqwerty'
Braintree::Configuration.public_key = 'qwertyqwerty'
Braintree::Configuration.private_key = 'qwertyqwertyqwerty'

Next, create a braintree.rb file in config/initializers/ directory and add these environment variables:

Braintree::Configuration.environment = :sandbox
Braintree::Configuration.logger = Logger.new('log/braintree.log')
Braintree::Configuration.merchant_id = ENV['BRAINTREE_MERCHANT_ID']
Braintree::Configuration.public_key = ENV['BRAINTREE_PUBLIC_KEY']
Braintree::Configuration.private_key = ENV['BRAINTREE_PRIVATE_KEY']

In order to use these environment variables, we will use the Figaro gem. Add to Gemfile:

gem 'figaro'

You will need to run bundle to install figaro and then bundle exec figaro install to install it. This will create a new file config/application.yml. This is where you will set your keys for Braintree.

# Braintree configuration keys
BRAINTREE_MERCHANT_ID: ''
BRAINTREE_PUBLIC_KEY: ''
BRAINTREE_PRIVATE_KEY: ''

Make sure that you add config/application.yml to your .gitignore file as this holds all of your Braintree keys.

Lastly, add the Braintree Ruby gem to your Gemfile,

gem 'braintree', '~> 2.77'

and make sure that you run bundle to install it.

Note: If you are in newer version of Rails, jQuery is no longer included automatically so you will need to add

gem 'jquery-rails'

to your Gemfile, and run bundle to install it.

Customers Setup

For the checkout form that I wanted to use, I needed a Customer that would be saved to the database.

rails g model Customers

Now open up the db/migrate/new-migration-file and we will add our fields

class CreateCustomers < ActiveRecord::Migration[5.1]
  def change
    create_table :customers do |t|
      t.string :first_name
      t.string :last_name
      t.string :email
      t.string :phone
      t.string :address_1
      t.string :address_2
      t.string :city
      t.string :state
      t.string :zipcode
      t.integer :braintree_customer_id
      t.datetime :last_visit
      t.timestamps null: false
    end
  end
end

We will use the braintree_customer_id so that in the future if we make a charge for that same customer, we can access the save data.

As we created the model for Customer, we can create a new customer without having to create a Customer controller. This will be done in our Checkouts controller. Let’s create that now. Create new file at app/controllers/checkouts_controller.rb and add the following:

class CheckoutsController < ApplicationController
  def new
    @customer = Customer.new
    @client_token = Braintree::ClientToken.generate
  end

  private
  def customer_params
    params.require(:customer).permit(:first_name, :last_name, :address_1, :address_2, :city, :state, :zipcode, :email, :phone)
  end
end

In the new method, we are creating a new customer that we will use in the checkout form. We also need to create a @client_token using the built in Braintree function Braintree::ClientToken.generate and we added the strong parameters which we will use later.

Now, let’s make sure that we have our routes setup correctly.

Rails.application.routes.draw do
  resources :customers
  root 'checkouts#new'
  resources :checkouts,  only: [:new, :create, :show]
end

We are going to route our app to checkouts#new which will take us to the views/checkouts/new.html.erb so lets create that file now.

There are two parts to the new view; there is the HTML markup for the form and the Javascript section that is used by Braintree to create a iframe for the credit card information.

For the sake of the demo, I just used Bootstrap CDN link in the view itself. I also used the simple_form ruby gem. Add the gem 'simple_form' to Gemfile and run bundle. Then you will need to run rails generate simple_form:install --bootstrap to install simple_form.

Form setup

HTML — add the following to the views/checkouts/new.html.erb:

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
...
<div class="panel panel-default bootstrap-basic">
  <div class="panel-heading">
    <h3 class="panel-title">Enter Card Details</h3>
  </div>
  <div class="panel-body">
    <%= simple_form_for @customer, url: {action: 'create'}, method: 'post' do |f| %>
      ... inputs ...
    <% end %>
  </div>
</div>

Javascript — add to the same view:

<script src="https://js.braintreegateway.com/js/beta/braintree-hosted-fields-beta.16.min.js"></script>
<script id="braintree-client-token" type="application/json"><%= @client_token %></script>
<script>
var clientToken = document.getElementById('braintree-client-token').innerHTML;

braintree.setup(clientToken, 'custom', {
  id: 'new_customer',
  hostedFields: {
    styles: {},
    number: { selector: '#credit-card-field', placeholder: '4111 1111 1111 1111' },
    cvv: { selector: '#security-code-field', placeholder: '123' },
    expirationDate: { selector: '#expiration-field', placeholder: 'MM/YYYY' },
    postalCode: { selector: '#postal-code-field', placeholder: '12345' }
  }
});
</script>

Transaction

When we submit the form it will go to the checkouts#create method. When this happens, the Braintree form will create a nonce which is basically a tokenized string that contains the credit card data. This data is encrypted when it is sent to the backend and will be decrypted by Braintree on their side.

Let’s start to add the create method in controllers/checkouts_controller.rb:

  def create
    amount = params["amount"] ||= 200
    nonce = params["payment_method_nonce"]
    result = Braintree::Transaction.sale(
      amount: amount,
      payment_method_nonce: nonce,
    )
  end

Now the full create method:

  def create
    amount = params["amount"] ||= 200
    nonce = params["payment_method_nonce"]
    result = Braintree::Transaction.sale(
      amount: amount,
      payment_method_nonce: nonce,
      customer: {
        first_name: customer_params[:first_name],
        last_name: customer_params[:last_name],
        email: customer_params[:email],
        phone: customer_params[:phone]
      },
      options: { store_in_vault: true }
    )

    if result.success? || result.transaction
      @customer = Customer.create customer_params
      @customer.braintree_customer_id = result.transaction.customer_details.id
      @customer.save
      redirect_to checkout_path(result.transaction.id)
    else
      error_messages = result.errors.map { |error| "Error: #{error.code}: #{error.message}" }
      flash[:error] = error_messages
      redirect_to new_checkout_path
    end
  end

Thanks — helpful resources:

  • https://www.sitepoint.com/integrate-braintree-payments-rails/
  • https://github.com/braintree/braintree_rails_example
  • https://developers.braintreepayments.com/start/example-integrations
  • https://developers.braintreepayments.com/guides/hosted-fields/overview/javascript/v3

Demo App: https://github.com/brobertsaz/rails_braintree_hosted_fields

Feedback: If I missed something or made any errors, please let me know so that I can get this updated.

Share:
Pay it forward. If this helped you, consider sharing it with a colleague, mentoring someone, or contributing a tip to the #payitforward page.