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);
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.