Rabeet Fatmi
Rabeet Fatmi

Reputation: 23

Stripe token not getting attached to request body for Rails App

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

Answers (1)

Mark Murphy
Mark Murphy

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

Related Questions