LaunchSchool - An Online School for Developers /

Blog

Process Payments With Paypal in Rails, Part 2 - Charge Credit Cards

This is the second article in the “Process Payments with Paypal in Rails” tutorial series:

In this article we will adapt the basic checkout form with Paypal in the last article Process Payments with Paypal in Rails, Part 1 – Basic Checkout, to accept credit cards for courses payments. We will work on cards branch of the same repository:

1
2
3
4
5
6
7
git clone -b cards git@github.com:gotealeaf/paypal-basics.git
cd paypal-basics
rake db:create
rake db:migrate
rake db:seed
rails s
open http://localhost:3000

Step 1. Create Paypal Pro account

On the Paypal developer site browse into ‘Dashboard > Sandbox: Accounts > Create Account’, creating a Business account:

We will need to upgrade to “Pro” account, so in “Account lists” click on account profile, and click “Upgrade to Pro”:

In the same popup window you can obtain the “API credentials” we will need in the next step:

Step 2. Create credit card form

Here we will need the Credit Card model to store payment data:

1
2
3
rails g model Card registration:references ip_address:string first_name:string last_name:string card_type:string card_expires_on:date

rake db:migrate

The registration form will allow us to choose between Card or Paypal payments:

In the registration view app/views/registrations/new.html.haml we should add a method payment selector, which allows the user to choose between Paypal or credit card:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.field.payment_method
  = label_tag :payment_method
  = radio_button_tag :payment_method, "paypal"
  = image_tag "ppcom.svg"
  = radio_button_tag :payment_method, "card", checked: true
  = image_tag "credit-card.jpg", style: "max-width: 100px;"
  :javascript
    $(document).on("click", "#new_registration input[type='radio']",
      function(e) {
        if ($("#payment_method_card:checked").val() == "card") {
          $("fieldset.card_info").show();
        }
        else {
          $("fieldset.card_info").hide();
        }
      });

And a filedset which gathers all information needed for the payment:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
%fieldset.card_info
  %legend Card info
  = f.fields_for :card do |c|
    .field
      = c.label :first_name
      = c.text_field :first_name
    .field
      = c.label :last_name
      = c.text_field :last_name
    .field
      = c.label :card_type
      = c.select :card_type, [["Visa", "visa"], ["MasterCard", "master"], ["Discover", "discover"], ["American Express", "american_express"]]
    .field
      = c.label :card_number
      = c.text_field :card_number
    .field
      = c.label :card_verification, "Card Verification Value (CVV)"
      = c.text_field :card_verification
    .field
      = c.label :card_expires_on
      = c.date_select :card_expires_on, discard_day: true, start_year: Date.today.year, end_year: (Date.today.year+10), add_month_numbers: true

Paypal gateway is really slow, so in order to prevent multiple form submits, or system-failure sensation, we will disable the button on submit:

1
2
3
4
5
6
7
.actions
  = f.submit "Registration Payment", class: "btn", style: "color: white;background: rgb(242, 118, 73);"
  :javascript
    $("form#new_registration").submit( function() {
      $(this).find('input[type=submit]').attr('disabled', 'disabled');
      $(this).find('input[type=submit]').val("Submitting, please wait ...");
    });

Step 3. Configure Active Merchant

Add gem 'activemerchant' to your Gemfile, and bundle install.

Add the configuration from the first step to your config/environment/development.rb file:

1
2
3
4
5
6
7
8
config.after_initialize do
  ActiveMerchant::Billing::Base.mode = :test  # :production when you will use a real Pro Account
  ::GATEWAY = ActiveMerchant::Billing::PaypalGateway.new(
    login: "merchant_api1.gotealeaf.com",
    password: "2PWPEUKZXAYE7ZHR",
    signature: "AFcWxV21C7fd0v3bYYYRCpSSRl31A-dRI5VpyF4A9emruhNYzlM8poc0"
  )
end

This configuration is important because it says to Paypal where to put the charges of your clients.

Step 4. Processing payments

Building the controller

Once we have configured the application, and we have made the payment form, we should write the controller registration-creation action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  def create
    @registration = Registration.new(registration_params)
    @registration.card.ip_address = request.remote_ip
    if @registration.save
      case params['payment_method']
        when "paypal"
          redirect_to @registration.paypal_url(registration_path(@registration))
        when "card"
          if @registration.card.purchase
            redirect_to registration_path(@registration), notice: @registration.card.card_transaction.message
          else
            redirect_to registration_path(@registration), alert: @registration.card.card_transaction.message
          end
      end
    else
      render :new
    end
  end

As you can see, depending on payment method selected we redirect to paypal checkout URL or process the purchase with the card.

Regarding the “Strong parameters” feature in Rails 4, we should make accessible the card attributes here, in the controller:

1
2
3
4
5
6
  def registration_params
    params.require(:registration).permit(:course_id, :full_name, :company, :email, :telephone,
                                          card_attributes: [
                                              :first_name, :last_name, :card_type, :card_number,
                                              :card_verification, :card_expires_on])
  end

If we will use the nested_attributes feature of ActiveRecord, we should build the card before to render the form:

1
2
3
4
5
  def new
    @registration = Registration.new
    @registration.build_card
    @course = Course.find_by id: params["course_id"]
  end

Building the models

The Card model will be the most complex:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Card < ActiveRecord::Base
  belongs_to :registration
  has_one :card_transaction

  # These attributes won't be stored
  attr_accessor :card_number, :card_verification

  before_create :validate_card

  def purchase
    response = GATEWAY.purchase(price_in_cents, credit_card, purchase_options)
    create_card_transaction(action: "purchase", amount: price_in_cents, response: response)
    registration.update_attribute(:purchased_at, Time.now) if response.success?
    response.success?
  end

  def price_in_cents
    (registration.course.price*100).round
  end

  private

  def purchase_options
    {
        ip: ip_address,
        billing_address: {
            name:      "Flaying Cakes",
            address1:  "123 5th Av.",
            city:      "New York",
            state:     "NY",
            country:   "US",
            zip:       "10001"
        }
    }
  end

  def validate_card
    unless credit_card.valid?
      credit_card.errors.full_messages.each do |message|
        errors.add :base, message
      end
    end
  end

  def credit_card
    @credit_card ||= ActiveMerchant::Billing::CreditCard.new(
        type:                card_type,
        number:              card_number,
        verification_value:  card_verification,
        month:               card_expires_on.month,
        year:                card_expires_on.year,
        first_name:          first_name,
        last_name:           last_name
    )
  end
end

Notice that card_number and card_verification will not be stored. This means that we will not be able to retry the transaction any more, so that the user should repeat the registration each time it fails or he needs. That is enough for our example.

This will validate the card and make the purchase, annotating the result of transaction into the “CardTransaction” model. So let’s create it:

1
2
3
rails g model card_transaction card:references action:string amount:integer success:boolean authorization:string message:string params:text

rake db:migrate

The Card Transaction model should store the transaction response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CardTransaction < ActiveRecord::Base
  belongs_to :card
  serialize :params

  def response=(response)
    self.success       = response.success?
    self.authorization = response.authorization
    self.message       = response.message
    self.params        = response.params
  rescue ActiveMerchant::ActiveMerchantError => e
    self.success       = false
    self.authorization = nil
    self.message       = e.message
    self.params        = {}
  end
end

Finally we need to add the relationship between card and registration to the Registration model:

1
2
3
4
5
6
  has_one :card
  accepts_nested_attributes_for :card

  def payment_method
    if card.nil? then "paypal"; else "card"; end
  end

Also we have created a payment_method method to know which method corresponds each registration.

Modifying the final invoice

Finally, we should show on each invoice the result of the payment process depending on each method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- if notice
  %p#notice{style: "background-color: lightgreen;padding: 5px;"}= notice
- if alert
  %p#alert{style: "background-color: lightpink;padding: 5px;"}= alert


%p
  %strong Payment method:
  = @registration.payment_method.capitalize
- if @registration.payment_method == "paypal"
  %p
    %strong Payment status:
    = @registration.status
  %p
    %strong Paypal Transaction Identifier:
    = @registration.transaction_id
- else
  %p
    %strong Transaction Identifier:
    = @registration.card.card_transaction.params["transaction_id"]

Step 5. Test with Paypal Sandbox

Now, let’s test our project …

First, select a course (INTRODUCTION TO REPOSTERY) and fill the form:

You can use a dummy credit card accepted for tests: 4242424242424242

Wait some seconds (~20 secs), don’t worry, Paypal production site is faster.

After that, you should see the successful invoice:

If you access to Paypal Sandbox with the merchant account created, you should see the payment:

Conclusions

While submitting payments with Paypal directly can have its appeals to some customers for its perceived security and convenience, charging credit cards directly can open your service to a broad audience which is anyone who has credit cards. It also has the benefit of having total control of your payment experience without redirecting users through Paypal’s site.

Following the approach described in this article, the card data (card_number and card_verification) will not be stored in our database, but this data does come to our servers. This means that it could be registered in our logs or spied in our networks. ** You should only implement your payment system this way if your organization already has taken measures to comply with PCI DSS, the credit card processing industry’s merchant security standard.

If you don’t have such infrastructure set up or the cost of the compliance is too high, you should consider the following solutions:

  • Braintree: Braintree takes care of PCI Compliance in a way that the card numbers would never flow through your server. Braintree is Paypal company therefore they have very good integration with Paypal.
  • Stripe.com: Another very popular service for developers who don’t want to deal with PCI Compliance. It doesn’t have direct Paypal integration but takes care of merchant account for you. They will deposit funds directl to your bank.