Reputation: 23
I am making a Rails App that allows users to become subscribing users for $X/yr and I am stuck on the subscriptions page where whatever I do the stripeToken doesn't get attached to the request and CreateSubscription.rb says "This customer has no attached payment source". My turbolinks is disabled.
My Stripe Logs say that it is getting a request with the POST body: email: "[email protected]" plan: "praducks-yearly" but the error is thrown as follows - as there is no source object attached to request - error: type: "invalid_request_error" message: "This customer has no attached payment source"
CreateSubscription.rb
class CreateSubscription
def self.call(plan, email_address, token)
user, raw_token = CreateUser.call(email_address)
subscription = Subscription.new(
plan: plan,
user: user
)
begin
stripe_sub = nil
if user.stripe_customer_id.blank?
puts "Customer is new"
puts token.nil?
customer = Stripe::Customer.create(
source: token,
email: user.email,
plan: plan.stripe_id,
)
user.stripe_customer_id = customer.id
user.save!
stripe_sub = customer.subscriptions.first
else
puts "Customer exists"
customer = Stripe::Customer.retrieve(user.stripe_customer_id)
# Check if the customer was a stripe customer, but had no source added
# If no source then update customer here:-
# if customer.sources.total_count == 0
# customer.source = token
# customer.save
# end
stripe_sub = customer.subscriptions.create(
plan: plan.stripe_id
)
end
subscription.stripe_id = stripe_sub.id
subscription.save!
rescue Stripe::StripeError => e
subscription.errors[:base] << e.message
end
subscription
end
end
CreateUser.rb
class CreateUser
def self.call(email_address)
user = User.find_by(email: email_address)
return user if user.present?
raw_token, enc_token = Devise.token_generator.generate(
User, :reset_password_token)
password = SecureRandom.hex(32)
user = User.create!(
email: email_address,
password: password,
password_confirmation: password,
reset_password_token: enc_token,
reset_password_sent_at: Time.now
)
return user, raw_token
end
end
subscription.js
jQuery(function($) {
$('#payment-form').submit(function(event) {
var $form = $(this);
alert('CLICKED');
$form.find('button').prop('disabled', true);
Stripe.card.createToken($form, stripeResponseHandler);
return false;
});
});
function stripeResponseHandler(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 {
// response contains id and card, which contains additional card details
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 submit
$form.get(0).submit();
}
};
SubscriptionsController
class SubscriptionsController < ApplicationController
before_filter :authenticate_user!
before_filter :load_plans
def index
# if !current_user.subscription.nil?
# redirect_to edit_subscription_path
# end
end
def new
@subscription = Subscription.new
@plan = Plan.find_by_id(params[:plan_id])
if @plan.nil?
redirect_to subscriptions_path, :notice => "Please select a plan first."
end
end
def create
@plan = Plan.find(params[:plan_id])
puts params[:stripeToken]
@subscription = CreateSubscription.call(
@plan,
params[:email_address],
params[:stripeToken]
)
if @subscription.errors.blank?
flash[:notice] = 'Thank you for your upgrade! ' +
'Enjoy your premium membership at Praducks.com. ' +
'We also sent you an email with the details of your purchase.'
redirect_to '/'
else
render :new
end
end
def edit
@subscription = current_user.subscription
if @subscription.nil?
redirect_to subscriptions_path, :notice => "Please join a membership before making changes"
end
end
def update
@subscription = current_user.subscription
@subscription = ChangeSubscriptionCard.call(
@subscription,
params[:stripeToken]
)
if @subscription.errors.blank?
flash[:notice] = 'Account updated!'
redirect_to '/'
else
render :edit
end
end
protected
def load_plans
@plans = Plan.where(published: true).order('amount')
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def subscriptions_params
params.require(:subscription).permit(:email_address, :stripeToken)
end
end
new.html.erb
<!--<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css" rel="stylesheet">-->
<div class="col2">
<div class="shoppingcart">
<% unless @subscription.errors.blank? %>
<%= @subscription.errors.full_messages.to_sentence %>
<% end %>
<h4 class="heading colr">Joining <%= @plan.name %></h4>
<div class="clear"></div>
<div class="cart_form">
<div class="form_cont">
<%= form_for @subscription, url: subscription_path, html: { id: 'payment-form' } do |f| %>
<input type="hidden" name="plan_id" value="<%= @plan.id %>" />
<input type="hidden" name="stripeEmail" value="<%= current_user.email %>" />
<input type="hidden" name="email_address" value="<%= current_user.email %>" />
<span class="payment-errors"></span>
<%= f.label :Email_Address %>: <%= current_user.email %>
<div class="clear"></div>
<%= f.label :Card_Number %>
<div class="clear"></div>
<input type="text" size="20" data-stripe="number"/>*
<div class="clear"></div>
<%= f.label :CVC %>
<div class="clear"></div>
<input type="text" size="4" data-stripe="cvc"/>*
<div class="clear"></div>
<%= f.label :"Expiration (MM/YYYY)" %>
<div class="clear"></div>
<input type="text" data-stripe="exp-month" placeholder="mm" id="exp" limit="2"/>
<input type="text" data-stripe="exp-year" placeholder="yyyy" id="exp" limit="4"/>*
<div class="clear"></div>
<button type="submit">Join</button>
<% end %>
</div>
</div>
</div>
</div>
application.html.erb
<title><%= full_title(yield(:title)) %></title>
<% if params[:beta] == "1" %>
<% session[:beta] = "1" %>
<% end %>
<!--CSS-->
<%= stylesheet_link_tag 'application', media: 'all' %>
<!--<%= javascript_include_tag 'application', type: 'text/javascript'%>-->
<script type="text/javascript" src="https://s3-us-west-2.amazonaws.com/praducks-uploads/jquery.min.js"></script>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<%= csrf_meta_tags %>
<meta name="viewport" content="width=device-width, initial-scale=1"/><!-- Prelang: Analytics / Google Analytics -->
<%= analytics_init if Rails.env.production? or Rails.env.development? %>
<!--Javascript-->
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script type="text/javascript">
$(function(){
Stripe.setPublishableKey('<%= Rails.configuration.stripe[:publishable_key] %>');
});
</script>
<script type="text/javascript" src="/js/subscriptions.js"></script>
</head>
<body data-no-turbolink>
<div id ="wrapper_sec">
<% if Rails.env.production? %>
<% if session[:beta] == "1" %>
<%= render partial: "elements/navbar" %>
<%= bootstrap_flash %>
<!-- Bread Crumb Section -->
<%= render partial: "elements/crumbs" unless current_page?(root_url) %>
<div class="clear"></div>
<%= yield %>
<%= render partial: "elements/footer" %>
<% else %>
<%= render "layouts/comingsoon" %>
<% end %>
<% else %>
<%= render partial: "elements/navbar" %>
<%= bootstrap_flash %>
<!-- Banner -->
<%= render "elements/banner" if current_page?(root_url) %>
<!-- Bread Crumb Section -->
<%= render partial: "elements/crumbs" %>
<div class="clear"></div>
<div id="content_sec">
<%= render "elements/leftnav" %>
<%= yield %>
</div>
<%= render partial: "elements/footer" %>
<% end %>
</div>
</body>
Upvotes: 2
Views: 1664
Reputation: 2874
After running this code locally I saw a couple of things:
I noticed that subscriptions.js isn't being included using the rails helper method javascript_include_tag
. Unless you've placed your script in your app's public/js
folder than your script probably isn't on the page.
Assuming that you do have it in public/js
and it is on the page, I noticed this error when submitting the form:
Uncaught TypeError: $form.find(...).prop is not a function
Because of this error, your submit handler never reaches the return false
to prevent the form from submitting thus it submits without the stripe token.
The version of jquery that's on the page is 1.3.2 which .prop
isn't available for which is the reason for this error.
If you don't want to upgrade your version of jquery you can replace .prop
with .attr
:
$form.find('button').prop('disabled', true);
becomes:
$form.find('button').attr('disabled', true);
Also, if you want to ensure that the form doesn't submit (assuming javascript is enabled) I typically add this line at the very beginning of my submit handler:
event.preventDefault();
This should ensure that your form doesn't submit. You can also remove the return false
at the end of your handler as that would no longer be required.
Upvotes: 5