Search code examples
ruby-on-railsbraintreeactivemerchant

How to change normal form which is linked with active merchant to Braintree dropin-ui?


I am having a registration page where user will enter personal info along with credit card info. This credit card info is integrated with activemerchant and as well as this info is stored in the database.

This is my form:

<%= semantic_form_for(@account, :url => account_create_path, :html => { :multipart => true, :class => 'billing'}) do |f| %>

   <div class="section section-first">
      <%= f.inputs :for => :user do |u| %>
        <h3>Account Information</h3>

        <%= u.input :name, :input_html => {:placeholder => "Name", :value => @account.user.name} %>
        <%= u.input :email, :input_html => {:placeholder => "Email", :value => @account.user.email} %>
      <% end %>
    </div>
        <div class="section">
            <%= f.inputs :for => :creditcard do |c| %>
            <h3>Credit Card Information</h3>
            <%= c.input :brand, :selected => @creditcard.brand.nil? ? "visa" : @creditcard.brand, :label => "Credit Card", :as => :select, :class => 'dropkick', :include_blank => false, :collection => Saas::Config.gateway == "bogus" ? [['Bogus', 'bogus']] : [['Visa', 'visa'], ['MasterCard', 'master'], ['American Express', 'american_express'], ['Discover', 'discover']] %>
            <%= c.input :number, :input_html => { :placeholder => "Card Number"}, :label => "Card Number", :as => :numeric %>

            <li class="select required" id="account_creditcard_expiration_input">
                <label for="account_creditcard_expiration">Card Expires On<abbr title="required">*</abbr></label>
              <%= c.select :year, (Time.now.year .. 10.years.from_now.year), :selected => @creditcard.year.nil? ? Time.now.year : @creditcard.year, :class => 'dropkick dd-small' %>
              <%= c.select :month, [['1 - January', 1], ['2 - February', 2], ['3 - March', 3], ['4 - April', 4], ['5 - May', 5], ['6 - June', 6], ['7 - July', 7], ['8 - August', 8], ['9 - September', 9], ['10 - October', 10], ['11 - November', 11], ['12 - December', 12]], :selected => @creditcard.month.nil? ? "1" : @creditcard.month, :class => 'dropkick' %>
            </li>

            <%= c.input :verification_value, :label => "CVV Code", :input_html => { :placeholder => "CVV Code", :value => @creditcard.verification_value,
                :type => "password",
                :class => 'short' } %>

          <% end %>

<% end %>

Now the fields like card number, expiry date, etc; in the above form has to be in braintree drop-in ui(here itself the credit card number is validated by braintree). How can I modify this form? Please help.

This is my model, account.rb:

 def valid_subscription?
     return if errors.any? 
    self.build_subscription(:plan => @plan, :next_renewal_at => @plan_start, :creditcard => @creditcard, :address => @address, :affiliate => @affiliate)
       @address.first_name = @creditcard.first_name
      @address.last_name = @creditcard.last_name
    self.subscription.store_card(@creditcard, :billing_address => @address.to_activemerchant)
    if !subscription.valid?
      errors.add(:base, "Error with payment: #{subscription.errors.full_messages.to_sentence}")
      return false
    end
  end

accounts_controller:

class AccountsController < ApplicationController

  before_filter :build_account, :only => [:new, :create]
  before_filter :build_user, :only => [:new, :create]
  before_filter :load_billing, :only => [:new, :create, :billing]
  def create
    @address.first_name = @creditcard.first_name
    @address.last_name = @creditcard.last_name
    @account.address = @address
    @account.creditcard = @creditcard
    if @account.new_record?
      if @account.save
        flash[:notice] = 'Account was created.'
        bypass_sign_in(@user)
        redirect_to session[:previous_url] || user_reports_path(@user)
      else
        render :action => 'new'
      end
    else
      @user.account_id = @account.id
      if @user.save
        flash[:notice] = 'User was created.'
        bypass_sign_in(@user)
        redirect_to session[:previous_url] || user_reports_path(@user)
      else
        render :action => 'new'
      end
    end
  end

  def billing
    @user = current_user
    @account = Account.find(params[:id])
    if request.put?
      @address.first_name = @creditcard.first_name
      @address.last_name = @creditcard.last_name
      puts @address.first_name
      if @creditcard.valid? & @address.valid?
       if @subscription.store_card(@creditcard, :billing_address => @address.to_activemerchant, :ip => request.remote_ip)
         flash[:notice] = "Your billing information has been updated."
         redirect_to settings_path(@user)
       end
      end
    end
  end
  protected

  def resource
    @account ||= current_account
  end

  def build_account
    @account = params[:account_name].blank? ? Account.new : Account.find_by_name(params[:account_name])
  end

  def build_user
    @account.user = @user = User.new(params[:account].blank? ? nil : params[:account][:user])
  end

  def load_billing
    @creditcard = ActiveMerchant::Billing::CreditCard.new(params[:account].blank? ? {} : params[:account][:creditcard])
    @address = SubscriptionAddress.new(params[:account].blank? ? {} : params[:account][:address])
  end

end

This is another model associated with account model, subscription.rb:

class Subscription < ActiveRecord::Base

  attr_accessor :creditcard, :address
  def store_card(creditcard, gw_options = {})
    # Clear out payment info if switching to CC from PayPal
    destroy_gateway_record(paypal) if paypal?

    @response = if billing_id.blank?
      gateway.store(creditcard, gw_options)
    else
      gateway.update(billing_id, creditcard, gw_options)
    end

    if @response.success?
      if active_card = @response.params['active_card']
        # Stripe token-based response
        self.card_number = "XXXX-XXXX-XXXX-#{active_card['last4']}"
        self.card_expiration = "%02d-%d" % [active_card['exp_month'], active_card['exp_year']]
      else
        self.card_number = creditcard.display_number
        self.card_expiration = "%02d-%d" % [creditcard.expiry_date.month, creditcard.expiry_date.year]
      end
      set_billing
    else
      errors.add(:base, @response.message)
      false
    end
  end

  def card_storage
      self.store_card(@creditcard, :billing_address => @address.to_activemerchant) if @creditcard && @address && card_number.blank?
    end

  def set_billing
      self.billing_id = @response.token
    end

end

production.rb:

 config.after_initialize do
    ActiveMerchant::Billing::Base.mode = :production
    ::GATEWAY = ActiveMerchant::Billing::AuthorizeNetGateway.new(
      :login => "xxxxxxx",
      :password => "xxxxxxxxxxxxxx"    )
  end

Solution

  • Disclaimer: I work at Braintree.

    Short answer: you can't. The Drop-in is meant as a secure replacement for a credit card form you host. To use the Drop-in, you should take out the entire credit card section of your form and account model, and—instead of expecting to handle and store credit card data—receive a payment method nonce returned by the Drop-in and use it via the Braintree transaction or payment method API.

    Long answer: the Drop-in is a pre-built form hosted by Braintree that we'll insert via an iframe into a form on your page. When the form is submitted, the credit card (or PayPal, etc) information from the Drop-in is sent to Braintree, and a payment method nonce is returned to your page (either via a JavaScript callback or inserted into a hidden field in your form.) The nonce is a randomly-generated string that stands in for the payment info, and can be passed through your application without security risks. Check out the Braintree developer docs for more details and sample code.

    The primary reason for all this is security. It's against industry regulations (called the PCI security standards) to store credit card information in a non-secure environment, and it is against PCI regulations to ever store the CVV/security code of a credit card. Even having credit card data passing through your site can put it at risk. The Drop-in (or our Hosted Fields integration) makes it much easier to meet PCI standards and reduces the burden on you of securing your site.

    To sum up: you should remove the integration of customer credit cards from ActiveMerchant and not include them in your schema at all. Instead, include a blank div in your form for the Drop-in to be inserted into, and use the payment method nonce the Drop-in returns in your model/controller. How you integrate the nonce, server-side Braintree API calls, and their results into your rails model is up to you. We have a full example rails app that uses the Drop-in: feel free to take a look at the braintree_rails_example repo on the Braintree github page for ideas.