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: "raf6000@gmail.com" 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>
upvote
  flag
If you're not seeing the token passed in the params object to your controller then it's probably something client side in the browser which isn't working correctly. In that case I would add a brakepoint to your stripeResponseHandler to check two things: make sure it gets called, make sure token is present and that it actually is submitted with the other params to the server – Mark Murphy

1 Answers 11

up vote 5 down vote accepted

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.

upvote
  flag
Good catch! Would have been stuck for God knows how long if it wasn't for you. Thank you. Didn't know I was using an old version of jQuery. How were you able to see the error? I am complete Javascript noob so don't know how to debug a js file. – Rabeet Fatmi
1 upvote
  flag
If you are using Google Chrome, this link will show you how to access Chrome DevTools: developer.chrome.com/devtools This link shows you how to debug javascript using Chrome DevTools: developer.chrome.com/devtools/docs/javascript-debugging If you are new to javascript in general I would recommend that you learn it, you'll use it a lot when it comes to web development. Plus it's awesome. There are tons of tutorials out there! I would recommend you check out javascript.com (put together by codeschool) – Mark Murphy
upvote
  flag
@MrR You should mark this as solved if my answer got it working for you :) – Mark Murphy
upvote
  flag
@MrR in Firefox case, have a look at 'Firefox Developer Tools': developer.mozilla.org/en-US/docs/Tools/Debugger and developer.mozilla.org/en-US/docs/Debugging_JavaScript – Luca G. Soave

Not the answer you're looking for? Browse other questions tagged or ask your own question.