4
votes

I am implementing Stripe in my Rails app & get an Invalid Token error when I load the entry form - I haven't submitted customer data yet. I mostly followed the http://railscasts.com/episodes/288-billing-with-stripe tutorial. I made a few modifications because it was somewhat incomplete.

books/show.html.erb is the page where I link to the form:

<b>Title:</b>  <%= @book.title %> </p>
<b>Author:</b>  <% authorid = @book.author %></p>

<%= @book.id %>
<%= link_to "Buy Now", new_purchase_path(:book_id => @book.id) %>

purchases/new.html.erb is where the user fills out info. When this loads, I get the Invalid Token error:

<%= form_for @purchase do |f| %>
  <% if @purchase.errors.any? %>
    <%= pluralize(@purchase.errors.count, "error") %> prohibited this purchase from being saved.
    <% @purchase.errors.full_messages.each do |msg| %>
      <%= msg %>
    <% end %>
  <% end %>

  <%= f.hidden_field :stripe_card_token %>

  <% if @purchase.stripe_card_token.present? %>
    Credit card has been provided.
  <% else %>
    <%= label_tag :card_number, "Credit Card Number" %>
    <%= text_field_tag :card_number, nil, name: nil %><p>

    <%= label_tag :card_code, "Security Code on Card (CVV)" %>
    <%= text_field_tag :card_code, nil, name: nil %><p>

    <%= label_tag :card_month, "Card Expiration" %>
    <%= select_month nil, {add_month_numbers: true}, {name: nil, id: "card_month"} %>
    <%= select_year nil, {start_year: Date.today.year, end_year: Date.today.year+15}, {name: nil, id: "card_year"} %>
  <% end %>

<div id="stripe_error">
  <noscript>JavaScript is not enabled and is required for this form. First enable it in your web browser settings.</noscript>
</div>

  <%= f.submit "Purchase" %>
<% end %>

purchases.js.coffee is pretty much the same as in the tutorial. I added a few alerts. The status according to my Stripe dashboard is 402. It's a POST /v1/tokens error and the Response Body is:

error:
  type: "card_error"
  message: "This card number looks invalid"
  param: "number"

purchases.js.coffee:

jQuery ->
  Stripe.setPublishableKey($('meta[name="stripe-key"]').attr('content'))
  purchase.setupForm()

purchase =
   setupForm: ->
    $('#new_purchase').submit ->
      $('input[type=submit]').attr('disabled', true)
     if $('#card_number').length
       purchase.processCard()
       false
     else
       true

  processCard: ->
    card =
      number: $('#card_number').val()
      cvc: $('#card_code').val()
      expMonth: $('#card_month').val()
      expYear: $('#card_year').val()
    Stripe.createToken(card, purchase.handleStripeResponse)

  handleStripeResponse: (status, response) ->
    if status == 200
      alert('This token can still be charged.')
      alert(response.id)
      $('#purchase_stripe_card_token').val(response.id)
      $('#new_purchase')[0].submit()
    else
      alert(response.error.message) 
      alert('The token was invalid, or has been used.')
      $('#stripe_error').text(response.error.message)
      $('input[type=submit]').attr('disabled', false)

I've tried a few versions of my purchase.rb model, such as commenting out the Stripe::Charge function, but still get the 402 Token error. However creating the Customer is successful (code 200).

class Purchase < ActiveRecord::Base

  attr_accessible :stripe_customer_token, :author_id, :book_id
  attr_accessor :stripe_card_token

  belongs_to :book

def save_with_payment
  if valid?

    customer = Stripe::Customer.create(
      :description => "customer email", 
      :card => stripe_card_token
    )
    self.stripe_customer_token = customer.id

#    charge = Stripe::Charge.create(  - this code doesn't work either
#      :amount => 1000,
#      :currency => "usd",
#      :card => stripe_card_token,
#      :description => "book title"
#    )
    save!
  end

  rescue Stripe::InvalidRequestError => e
    logger.error "Stripe error while creating customer: #{e.message}"
    errors.add :base, "There was a problem with your credit card."
    false
  end
end

The error I get if I uncomment the Stripe::Charge code is: Stripe::CardError in PurchasesController#create Cannot charge a customer that has no active card

And, the create method in my purchases_controller.rb

def create
  @purchase = Purchase.new(params[:purchase])
  if @purchase.save_with_payment
    redirect_to @purchase, :notice => "Thank you for purchasing this book!"
  else
    render :new
  end
end

Here's my new method in the purchases_controller.rb:

def new

 book = Book.find(params[:book_id])    

 @purchase = book.purchases.build  

end

BUT if I hit the Back button after Submitting the purchase (to go back to the purchase/new.html.erb page), a SECOND 'purchase' is entered into my database and the code for that POST Token in my Stripe log is 200 (pass)!!!

Here's the javascript compiled from coffeescript:

(function() {

var purchase;

jQuery(function() {

Stripe.setPublishableKey($('meta[name="stripe-key"]').attr('content'));

return purchase.setupForm();

});

purchase = {

setupForm: function() {

 $('#new_purchase').submit(function() {

   return $('input[type=submit]').attr('disabled', true);

 });

 if ($('#card_number').length) {

   purchase.processCard();

   return false;

 } else {

   return true;

 }

},

processCard: function() {

 var card;

 card = {

   number: $('#card_number').val(),

   cvc: $('#card_code').val(),

   expMonth: $('#card_month').val(),

   expYear: $('#card_year').val()

 };

 return Stripe.createToken(card, purchase.handleStripeResponse);

},

handleStripeResponse: function(status, response) {

 if (status === 200) {

   alert('This token can still be charged.');

   alert(response.id);

   $('#purchase_stripe_card_token').val(response.id);

   return $('#new_purchase')[0].submit();

 } else {

   alert(response.error.message);

   alert('The token was invalid, or has been used.');

   $('#stripe_error').text(response.error.message);

   return $('input[type=submit]').attr('disabled', false);

 }

}

};

}).call(this);

2
Have you tried passing :customer => customer to Stripe::Charge.create instead of :card?deefour
Yes. I passed :customer => customer.id and that didn't work either. It seems that the app is submitting the info upon loading the page, before any info is entered.ThinQtv
Could you post the javascript that is output from your coffeescript?dwhalen
This !@#$%^&* editor won't make my 'code' look like code so I'll paste the code above.ThinQtv
I see the problem. The indentation in your CoffeeScript (specifically the setupForm function) is incorrect. Cross-check it with the Railscast episode. I'll post a proper answer when I have time this evening.dwhalen

2 Answers

5
votes

As mentioned in my comment above, the indentation of your CoffeeScript is slightly off (whitespace is significant in CS). It's not a syntax error so it still compiles, but the generated JS is not what you're intending. Here is the code properly indented.

purchase =
  setupForm: ->
    $('#new_purchase').submit ->
      $('input[type=submit]').attr('disabled', true)
      if $('#card_number').length
        purchase.processCard()
        false
      else
        true
  # ...

In the JS you posted, the conditional if ($('#card_number').length) exists outside of the anonymous function bound to the submit event. That causes the conditional to run as soon as the page loads, rather than when the form is submitted. Since there is nothing in the '#card_number' input when the page is initially loaded, the conditional's alternative (else branch) is silently executed. Here is what the JS should look like:

$('#new_purchase').submit(function() {
  $('input[type=submit]').attr('disabled', true);
  if ($('#card_number').length) {
    purchase.processCard();
    return false;
  } else {
    return true;
  }
});

The behavior you see when navigating "back" to the new page is the result of the page loading with input in the '#card_number' field (executing the processCard function), which is what you want, just not when you want it.

If my speculations are correct, fixing the indentation in your CoffeeScript is the solution.

1
votes

Is something submitting the form on page load? (Maybe an errant jQuery request from another part of the app?) I found that this page helped me a lot when setting up / testing Stripe: https://stripe.com/docs/testing

UPDATE

Try this first

def new
  @book = Book.find(params[:book_id])
  @purchase = @book.purchases.new
end

and if that doesn't work change your form to

<%= form_for [@book, Purchase.new] do |f| %>