Intermediate TDD in Rails : Payment Form

Objective


In order to subscribe a registered user, they must fill out credit card details form.

Scope


The html and javascript files needed to get the payment form working will not be explained in detail. They are explained in detail with examples on stripe.com. You need to play with the example code to become familiar with how the payment flow works with Stripe servers.

Steps


Step 1

Change the subscription_spec.rb as follows:

require 'rails_helper'
require 'spec_helper'

feature "Subscribe", :type => :feature do
  scenario "to gold plan" do
    visit root_path

    click_link 'Register'
    fill_in 'Email', with: 'bugs@rubyplus.com'
    fill_in 'Password', with: '12345678'    
    click_button 'Sign up'

    click_link 'Gold'
    fill_in "Card Number", with: '4242424242424242'
    page.select '10', from: "card_month"
    page.select '2029', from: 'card_year'
    click_button 'Subscribe Me'

    expect(page).to have_text("You are now on gold plan.")
  end

end

Run the test. It fails with the error: Unable to find field Card Number

Step 2

Let's add the payment form in the subscriptions/new.html.erb:

<form action="create" method="POST" id="payment-form">
  <span class="payment-errors">
    <noscript>JavaScript is not enabled and is required for this form. First enable it in your web browser settings.</noscript>
  </span>
  <%= hidden_field_tag :authenticity_token, form_authenticity_token -%>
  <%= hidden_field_tag :plan_name, @plan_name %>

  <% if @error_message %>
    <%= @error_message %>
  <% end %>

  <div class="form-row">
    <label>
      <span>Card Number</span>
      <input type="text" size="20" data-stripe="number"/>
    </label>
  </div>

  <div class="form-row">
    <label>
      <span>Expiration</span>
<%= select_month nil, {add_month_numbers: true}, {name: nil, id: "card_month", data: {stripe: "exp-month"}} %>

    </label>
    <span>/</span>
    <%= select_year nil, {start_year: Date.today.year, end_year: Date.today.year+15}, {name: nil, id: "card_year", data: {stripe: "exp-year"}} %>

  </div>

  <button type="submit" class='btn btn-primary'>Subscribe Me</button>

</form>

The credit card related form values will not be submitted to our servers.

Step 3

We also need to change the stripe.html.erb as follows:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Striped</title>
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application' %>

  <%= javascript_include_tag 'https://js.stripe.com/v2/' %>
  <%= tag :meta, :name => "stripe-key", :content => 'pk_test_GxDbP7Q6f4GkaUe0I0LrbQSz' %>

  <script type="text/javascript">
    // This identifies your website in the createToken call below
    Stripe.setPublishableKey($('meta[name="stripe-key"]').attr('content'));

    var stripeResponseHandler = function(status, response) {
      var $form = $('#payment-form');

      if (response.error) {
        // Show the errors on the form
        $form.find('.payment-errors').text(response.error.message);
        $form.find('button').prop('disabled', false);
      } else {
        // token contains id, last4, and card type
        var token = response.id;

        // Insert the token into the form so it gets submitted to the server
        $form.append($('<input type="hidden" name="stripeToken" />').val(token));

        // and re-submit
        $form.get(0).submit();
      }
    };

    jQuery(function($) {
      $('#payment-form').submit(function(event) {

        var $form = $(this);

        // Disable the submit button to prevent repeated clicks
        $form.find('submit').prop('disabled', true);

        Stripe.card.createToken($form, stripeResponseHandler);

        // Prevent the form from submitting with the default action
        return false;
      });
    });

  </script>  

    <%= csrf_meta_tags %>
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>

  <body data-no-turbolink>

    <div class='container'>
        <%= yield %>
    </div>

  </body>
</html>

You can see that the turbo-links has been turned off. You also need to change the stripe-key, content value to your website Stripe Publishable Key value. Run the test. It fails with:

Failure/Error: expect(page).to have_text("You are now on gold plan.")
       expected to find text "You are now on gold plan." in "Subscriptions#create Find me in app/views/subscriptions/create.html.erb"

Step 4

You can tail the test.log file.

tail -f log/test.log

You can see that it went to subscriptions controller create action. We need to implement it now.

Step 5

We already have create subscription implemented in the stripe gateway class. It requires two remote calls, after some digging into the Stripe API docs, I found a way to create and subscribe a user to a plan with just one remote call:

module StripeGateway

  class Subscription

    def self.create(email, stripe_token, stripe_plan_id)
      Stripe::Customer.create(description: email, card: stripe_token, plan: stripe_plan_id)      
    end
  end
end  

We can delete the old methods in the stripe gateway class. The subscriptions_controller can use this method:

class SubscriptionsController < ApplicationController
  layout 'stripe'  

  def new
    @plan_name = params[:plan_name]      
  end

  def create
    StripeGateway::Subscription.create(current_user.email, params[:stripeToken], params[:plan_name])    
  end
end

Change the subscriptions/create.html.erb to : You are now on gold plan.

Step 6

Run the test. It fails with:

Stripe::AuthenticationError:
       No API key provided. Set your API key using "Stripe.api_key = <API-KEY>". You can generate API keys from the Stripe web interface. See https://stripe.com/api for details, or email support@stripe.com if you have any questions.

Add the before block to configure the api_key as follows in the subscription_spec.rb:

  before do
    Stripe.api_key = 'sk_test_watHqWl2XD88WHqhknk7sqXN'
    Stripe.api_version = "2015-02-18"
  end

Run the test again. The test passes. You can now login to the Stripe dash board and check if the customer is created or not.

Summary


In this lesson we implemented a payment form where the user can enter credit card details to subscribe to a plan.


Related Articles


Ace the Technical Interview

  • Easily find the gaps in your knowledge
  • Get customized lessons based on where you are
  • Take consistent action everyday
  • Builtin accountability to keep you on track
  • You will solve bigger problems over time
  • Get the job of your dreams

Take the 30 Day Coding Skills Challenge

Gain confidence to attend the interview

No spam ever. Unsubscribe anytime.