Stripe token not carrying over to controller rails

2019-06-26 08:57发布

问题:

The Issue

I've tested the CoffeeScript and the form makes the call to Stripe, sets the hidden field with the proper response token and submits the form. My issue is that once its submitted the controller doesn't seem to grab the token properly and throws this error: Stripe::InvalidRequestError - You must supply either a card or a customer id.

Next I tired taking the token that was generated and hard coded it into the controller to see if that would work. I submitted the form, that worked and payment was received on Stripes end. I'm pretty much out of ideas on what to try next. I'm wondering if I am forgetting something or missing something since payments is nested under assignments.

Gem Versions

  • Ruby: 2.1.0
  • Rails: 4.0.1
  • Stripe: 1.9.9

Files

/payment/new.html.erb

<%= form_for([@assignment, @payment]) do |f| %>
  <% if @payment.errors.any? %>
    <div class="error_messages">
      <h2><%= pluralize(@payment.errors.count, "error") %> prohibited this subscription from being saved:</h2>
      <ul>
        <% @payment.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <%= f.hidden_field :stripe_customer_token %>

  <% if @payment.stripe_customer_token.present? %>
    <p>This assignment has already been paid for.</p>
  <% else %>

    <div class="field">
      <%= label_tag :card_number, "Credit Card Number" %>
      <%= text_field_tag :card_number, nil, name: nil, placeholder: "00000000000000" %>
    </div>
    <div class="row">
      <div class="field card__dates">
        <%= 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"} %>
      </div>
      <div class="field card__cvv">
        <%= label_tag :card_code, "CVV" %>
        <%= text_field_tag :card_code, nil, name: nil, placeholder: "003", required: true, maxlength: 4, minlength: 3 %>
      </div>
    </div>

  <% 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>
  <div class="actions">
    <%= f.submit "Pay " + number_to_currency(@assignment.price.to_s), class: 'btn btn__primary btn__large btn--fill' %>
  </div>

payment_controller.rb

class PaymentsController < ApplicationController
  def new
    set_assignment
    @payment = @assignment.build_payment
    @price = @assignment.price
  end

  def create
    set_assignment
    @payment = @assignment.build_payment(payment_params)

    if save_with_payment
      redirect_to assignments_path, :notice => "Payment received, Thank you!"

      # since payment was successful, set assignment paid to true
      Assignment.update(@assignment, assignment_paid: true, project_status: "In Progress")
    else
      render :new
    end
  end

  private

    def save_with_payment

      # Set your secret key: remember to change this to your live secret key in production
      # See your keys here https://manage.stripe.com/account
      Stripe.api_key = Rails.configuration.stripe[:secret_key]

      # Get the credit card details submitted by the form
      token = params[:stripe_customer_token]

      # How much the assignment costs, which must be converted to cents
      @amount = (@price * 100)

      # Create the charge on Stripe's servers - this will charge the user's card
      begin
        charge = Stripe::Charge.create(
          :amount => @amount,
          :currency => "cad",
          :card => token,
          :description => "some description of the product"
        )
      rescue Stripe::CardError => e
        redirect_to @assignment, :notice => "The card has been declined"
      end
    end

    def set_assignment
      @assignment = Assignment.friendly.find(params[:assignment_id])
    end

    def payment_params
      params.require(:payment).permit(
        :stripe_customer_token
      )
    end
end

payment.js.coffee

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

payment =
  setupForm: ->
    $('#new_payment').submit ->
      $('input[type=submit]').attr('disabled', true)
      if $('#card_number').length
        payment.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, payment.handleStripeResponse)

  handleStripeResponse: (status, response) ->
    if status == 200
      console.log response
      $('#payment_stripe_customer_token').val(response.id)
      $('#new_payment')[0].submit()
    else
      $('#stripe_error').text(response.error.message)
      $('input[type=submit]').attr('disabled', false)

payment.rb

class Payment < ActiveRecord::Base
  belongs_to :assignment
end

回答1:

At least two problems as I saw. And I guess there may be more after progressing.

  1. You don't have access to params within #save_with_payment

    The problem happens in this line:

    # Get the credit card details submitted by the form
    token = params[:stripe_customer_token]
    

    The params is protected by strong_params and you don't have access to it.

    The fix is to permit all of the needed params in payment_params and reuse it within this method.

  2. Actually you don't have @price within #create

    This problem does not relate to the question directly but it exists.

    This instance variable @price is in #new. And #create is another instance so you can't have it again.

    The fix is to get it from payment_params