Skip to content

Instantly share code, notes, and snippets.

@anaclair
Last active March 22, 2018 10:45
Show Gist options
  • Save anaclair/bd0abbbbbe7607867a0b to your computer and use it in GitHub Desktop.
Save anaclair/bd0abbbbbe7607867a0b to your computer and use it in GitHub Desktop.
Implementing Braintree Drop-in UI Payment

How does Braintree work?
-> Your project backend generates a client token using the Ruby SDK for the frontend that initializes the JavaScript SDK using the client token.
--> The Braintree-provided JavaScript library encrypts sensitive data using the public key and communicates with Braintree before the form is ever posted to your server.
---> Once the data reaches Braintree’s servers, it is decrypted using the keypair’s private key, then returns a payment method nonce to your client code. Your code relays this nonce to your server.
----> Your server-side code provides the payment method nonce to the Ruby SDK to perform Braintree operations.

1. In terminal, generate the Order model with two foreign keys :
rails g model order item_id:integer donor_id:integer
2. In the create_orders migration, add an index combining the foreign keys to the migration :
class CreateOrders < ActiveRecord::Migration
  def change
    create_table :orders do |t|
      t.integer :item_id
      t.integer :donor_id
      t.timestamps
    end
    add_index :orders, [:item_id, :donor_id], unique: true
  end
end
3. Migrate the database. In terminal :

be rake db:migrate

4. Add associations to the models :
# Item Model
has_many :orders
has_many :donors, through: :orders

# User Model
has_many :orders, foreign_key: :donor_id
has_many :items, through: :orders

# Order Model
belongs_to :item
belongs_to :donor, class_name: 'User'
5. In the User model, add a method cart_total_price, which returns the total price of items in the cart, and get_cart_items which returns the items in the cart using the SMEMBERS Redis command :
def cart_total_price
  total_price = 0
  get_cart_items.each { |item| total_price+= item.price }
  total_price
end

def get_cart_items
  cart_ids = $redis.smembers "cart#{id}"
  Item.find(cart_ids)
end
6. Also in the User model, add the following methods :
def purchase_cart_items! # Add cart items to items collection and then clear the cart
  get_cart_items.each { |item| purchase(item) }
  $redis.del "cart#{id}"
end

def purchase(item) # Add item to items if not in items already 
  items << item unless purchase?(item)
end

def purchase?(item) # Check if item is in items (a collection belonging to the user through orders) 
  items.include?(item)
end
7. Log in to your Braintree sandbox account. You’ll be redirected to a dashboard which enables you to take advantage of the payment gateway and vault. It also provides PCI compliant credit card processing, storage, and retrievals. You’ll find here your application keys and configurations that are added to your application initializers.
8. Add the braintree gem to the project's Gemfile (remember to bundle install after you do this) :
gem 'braintree'
9. Create the new initializer file /config/initializers/braintree.rb and add the basic Braintree configuration keys as environment variables to securely hide them from version control. In braintree.rb :
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']
10. In terminal, export the variables to be held in ENV :
export BRAINTREE_MERCHANT_ID="your_merchant_id"
export BRAINTREE_PUBLIC_KEY="your_public_key"
export BRAINTREE_PRIVATE_KEY="your_private_key"
rails server
11. Restart servers. When in doubt, restart servers.
12. Generate a transactions controller that will be responsible for handling Braintree operations. In terminal :

rails g controller transactions new

13. In routes.rb, restrict transactions to the new and create actions only :
resources :transactions, only: [:new, :create]
14. In TransactionsController, add a method to check if cart contains items :
class TransactionsController < ApplicationController
  before_action :authenticate_user! # Check that user is signed in 
  before_action :check_cart # Check that cart has items 

  # Other Code

  private
  def check_cart
    if current_user.get_cart_items.blank?
      redirect_to root_url, alert: "Please add some items to your cart before processing your transaction!"
    end
  end
end
15. Now we want to generate the client token in TransactionsController#new then pass it as a variable to the Braintree JS SDK initializer. To quickly and easily pass this variable from our Rails app to JavaScript, we will use a gem called Gon. Add this to the Gemfile and run bundle install :
gem 'gon' 
16. Update the layout file by adding a helper method that will include Gon in our app. In application.html.erb :
<%= Gon::Base.render_data({}) %>
17. Generate a Braintree client token and set it to client_token on the Gon variable. InTransactionsController#new :
def new
  gon.client_token = generate_client_token
end

private
def generate_client_token
  Braintree::ClientToken.generate
end
18. It's a web app so JavaScript is our client. Setup the Braintree JavaSript SDK by including this in application.html.erb:
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
19. We will integrate Braintree's Drop-in UI which gives us a full-featured checkout with a PayPal option. Configure the form where the UI will add the payment method nonce. In views/transactions/new.html.erb :
<div class="form-container radius-box glassy-bg small-10 small-centered medium-8 large-6 columns">
  <h2 class="mbs">New Transaction</h2>
  <%= form_tag transactions_path do%>
    <p>Please enter your payment details:</p>
    <div id="dropin"></div>
    <%=submit_tag "Pay #{current_user.cart_total_price}$", class: "button mt1" %>
  <%end%>
</div>
20. Initialize the Braintree Javascript SDK. Give it the client_token we generated using Gon, and specify dropin integration to add the payment UI into the form. In app/assets/javascripts/transactions.coffee :
$ ->
  unless typeof gon is 'undefined'
    braintree.setup(gon.client_token, 'dropin', { container: 'dropin' });
21. Add a checkout button inside the carts/show.html.erb view that redirects users to the new transaction form :
<%=link_to "Checkout", new_transaction_path, class: "button" unless current_user.get_cart_items.blank?%>
22. Update TransactionsController to submit a BrainTree Transaction :
class TransactionsController < ApplicationController
  before_action :authenticate_user!
  before_action :check_cart

  def new
    gon.client_token = generate_client_token
  end

  def create
    @result = Braintree::Transaction.sale(
              amount: current_user.cart_total_price,
              payment_method_nonce: params[:payment_method_nonce])
    if @result.success? # If validations do not deny the transaction from being authorized on Braintree 
      current_user.purchase_cart_items!
      redirect_to root_url, notice: "Your donation was successful. Thank you for being an amazing person!"
    else
      flash[:alert] = "Something went wrong while processing your transaction. Please try again!"
      gon.client_token = generate_client_token
      render :new
    end
  end

  private

  def generate_client_token
    Braintree::ClientToken.generate
  end

  def check_cart
    if current_user.get_cart_items.blank?
      redirect_to root_url, alert: "Please add some items to your cart before processing your transaction!"
    end
  end

end
23. Now, when you visit your Braintree sandbox account and click on the Transactions link under Advanced Search, you can search and browse all transactions created by your app.
24. Checking CVV in your payment UI is recommended as a preliminary method to help prevent fraud. To add it, you have to enable and configure its rules in your Braintree sandbox account. In your Braintree dashboard, hover on the Settings dropdown menu and click on the Processing option. You can then enable and configure CVV verification rules. After finishing & saving your configuration, those fields will be appear in the UI and their validations will work as you configured them.
25. Braintree offers Vault to handle associating users' credit cards with their accounts so they don’t have to add card details everytime they want to checkout. Creating a new customer in the Vault will return an ID that is used to access that customer’s information. So, we need to add braintree_customer_id to the User model. In terminal :
rails g migration add_braintree_customer_id_to_users braintree_customer_id:integer
be rake db:migrate
26. In the User model, add a method that checks if the user has payment info stored already :
def has_payment_info?
  braintree_customer_id
end
27. Create a partial form views/transactions/_customer_form.html.erb for customer details which we will store in the Vault with payment information :
<p>Please enter your personal info:</p>
<div class="mb1">
  <%= text_field_tag :first_name, "",placeholder: "First Name", class: "radius" %>
</div>
<div class="mb1">
  <%= text_field_tag :last_name, "",placeholder: "Last Name", class: "radius" %>
</div>
28. Render it conditionally inside the views/transactions/new.html.erb form :
<%= form_tag transactions_path do%>
  <%= render 'customer_form' unless current_user.has_payment_info? %>
  <p>Please enter your payment details:</p>
  <div id="dropin"></div>
  <%=submit_tag "Pay #{current_user.cart_total_price}$", class: "button mt1" %>
<%end%>
29. Go back to TransactionsController and change the generate_client_token private method to identify an existing customer by including the customer_id :
private
def generate_client_token
  if current_user.has_payment_info?
    Braintree::ClientToken.generate(customer_id: current_user.braintree_customer_id)
  else
    Braintree::ClientToken.generate
  end
end
30. Update TransactionsController#create to store the new customer in the Vault in case the current_user doesn’t have payment info :
def create
  unless current_user.has_payment_info?
    @result = Braintree::Transaction.sale(
                amount: current_user.cart_total_price,
                payment_method_nonce: params[:payment_method_nonce],
                customer: {
                  first_name: params[:first_name],
                  last_name: params[:last_name],
                  email: current_user.email,
                },
                options: {
                  store_in_vault: true
                })
  else
    @result = Braintree::Transaction.sale(
                amount: current_user.cart_total_price,
                payment_method_nonce: params[:payment_method_nonce])
  end

  if @result.success?
    current_user.update(braintree_customer_id: @result.transaction.customer_details.id) unless current_user.has_payment_info?
    current_user.purchase_cart_items!
    redirect_to root_url, notice: "Your donation was successful. Thank you for being an amazing person!"
  else
    flash[:alert] = "Something went wrong while processing your transaction. Please try again!"
    gon.client_token = generate_client_token
    render :new
  end
end

If the Braintree Client SDK succeeds with a payment method nonce, that nonce will then be linked with the identified customer! A user can still enter new credit card details and these will also be linked to the customer in the Braintree Vault, accessible for future transactions.

31. The end!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment