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.
rails g model order item_id:integer donor_id:integer
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
be rake db:migrate
# 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
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.
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']
export BRAINTREE_MERCHANT_ID="your_merchant_id"
export BRAINTREE_PUBLIC_KEY="your_public_key"
export BRAINTREE_PRIVATE_KEY="your_private_key"
rails server
12. Generate a transactions controller that will be responsible for handling Braintree operations. In terminal :
rails g controller transactions new
resources :transactions, only: [:new, :create]
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?%>
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
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>
<%= 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.