问题
Like others working with Stripe payments, I was getting an error 'Cannot charge a customer that has no active card'. The form was not appending the token because the javascript did not have a matching id in the form, but now I am getting an rails error and the card is not being charged, the customer is created, the error states that there is an invalid line item, when the code to create the customer and charge the card is not run the order is created without error.
Order View
<script type="text/javascript" src="https://js.stripe.com/v2/">
$(function(){
Stripe.setPublishableKey('<%= Rails.configuration.stripe[:PUBLISHABLE_KEY] %>');
});
</script>
<div class = "Power Me" >
<fieldset>
<legend> Please enter your details </legend>
<%= render 'form', object: @object %>
</fieldset>
</div>
Rendered Order Form
<%= form_for(@order, :html => {:id => 'payment-form'}) do |f| %>
<% if @order.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@order.errors.count, "error") %> prohibited this order from being saved:</h2>
<ul>
<% @order.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= "Order Total: #{order_total.to_s}" %>
<%= "Order Currency: #{order_currency.to_s}" %>
</div>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :Address_line_1 %><br>
<%= f.text_area :address_line_1 %>
</div>
<div class="field">
<%= f.label :Address_Line_2 %><br>
<%= f.text_area :address_line_2 %>
</div>
<div class="field">
<%= f.label :City %><br>
<%= f.text_area :address_city %>
</div>
<div class="field">
<%= f.label :Region %><br>
<%= f.text_area :address_state %>
</div>
<div class="field">
<%= f.label :Postcode %><br>
<%= f.text_area :address_zip %>
</div>
<div class="field">
<%= f.label :Country %><br>
<%= f.select :address_country, Order::CC_COUNTRIES, prompt: 'Select the country' %>
</div>
<div class="field">
<%= f.label :email %><br>
<%= f.text_field :email, :placeholder => "you@example.com" %>
</div>
<div class="field">
<%= f.label :Payment_Type %><br>
<%= f.select :pay_type, Order::PAYMENT_TYPES, prompt: 'Select a payment method' %>
</div>
<div class="form-row">
<label>Card Number</label>
<input type="text" size="20" autocomplete="off" data-stripe="number" id="number" class="credit-number", placeholder = "**** **** **** ****" pattern="[\d ]*" />
</div>
<div class="form-row">
<label>Security Code/CVC</label>
<input type="text" size="4" autocomplete="off" data-stripe="cvc" id="cvc" class="credit-scurity" placeholder="***" pattern="\d*" />
</div>
<div class="form-row">
<label>Expiration (MM/YYYY)</label>
<input type="text" size="2" data-stripe="exp-month" id="exp-month" class="card-expiry-month" placeholder="MM" pattern="\d*" />
<span> / </span>
<input type="text" size="4" data-stripe="exp-year" id="exp-year" class="card-expiry-year" placeholder="YYYY" pattern="\d*" />
</div>
<div class="actions">
<%= f.submit 'Pay', :class =>"stripe-button" %>
</div>
<% end %>
orders model
class Order < ActiveRecord::Base
# attr_accessor :stripeToken
PAYMENT_TYPES = ["credit card"]
CC_COUNTRIES = ["United Kingdom", "France", "Italy"]
validates :name, :address_line_1, :address_zip, :email, presence: true
# validates :pay_type, inclusion: PAYMENT_TYPES
has_many :line_items, dependent: :destroy
def add_line_items_from_cart(cart)
cart.line_items.each do |item|
item.cart_id = nil
line_items << item
end
end
end
Orders Controller
class OrdersController < ApplicationController
include CurrentCart
before_action :set_cart, only: [:new, :create]
before_action :set_order, only: [:show, :edit, :update, :destroy]
# GET /orders
# GET /orders.json
def index
@orders = Order.all
end
# GET /orders/1
# GET /orders/1.json
def show
end
# GET /orders/new
def new
if @cart.line_items.empty?
redirect_to store_url, notice: "Your cart is empty"
return
end
@order = Order.new
end
# GET /orders/1/edit
def edit
end
# POST /orders
# POST /orders.json
def create
@order = Order.new(order_params)
@order.add_line_items_from_cart(@cart)
respond_to do |format|
if @order.save
Cart.destroy(session[:cart_id])
session[:cart_id] = nil
format.html { redirect_to @order, notice: 'Order was successfully created.' }
format.json { render action: 'show', status: :created, location: @order }
else
format.html { render action: 'new' }
format.json { render json: @order.errors, status: :unprocessable_entity }
end
end
Stripe.api_key = "sk_test_BsdqHq0SQuPqHIsm46lcpX4v"
@amount = order_total.to_i * 100
token = params[:stripeToken]
=begin
begin
customer = Stripe::Customer.create(
:email => order_params[:email]
)
charge = Stripe::Charge.create(
:customer => customer.id,
:amount => @amount, # amount in cents, again
:currency => order_currency,
:card => token,
:description => order_params[:email]
)
redirect_to root_path
rescue Stripe::CardError => e
@error = e
end
=end
end
# PATCH/PUT /orders/1
# PATCH/PUT /orders/1.json
def update
respond_to do |format|
if @order.update(order_params)
format.html { redirect_to @order, notice: 'Order was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: @order.errors, status: :unprocessable_entity }
end
end
end
# DELETE /orders/1
# DELETE /orders/1.json
def destroy
@order.destroy
respond_to do |format|
format.html { redirect_to orders_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_order
@order = Order.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def order_params
params.require(:order).permit(:name, :email, :pay_type, :address_line_1, :address_line_2, :address_city, :address_state, :address_zip, :address_country)
end
end
My Application Helper
module ApplicationHelper
def order_total
total = LineItem.joins(:product).select("sum(line_items.quantity * products.price) as total").where("cart_id = ?", session[:cart_id]).first.total
end
def order_currency
currency = LineItem.joins(:product).joins(:currency).select("currencies.name as iso_name").where("cart_id = ?", session[:cart_id]).first.iso_name
end
# Returns the full title on a per-page basis.
def full_title(page_title)
base_title = "Ruby on Rails Tutorial Sample App"
if page_title.empty?
base_title
else
"#{base_title} | #{page_title}"
end
end
def bootstrap_class_for flash_type
{ success: "alert-success", error: "alert-danger", alert: "alert-warning", notice: "alert-info" }[flash_type] || flash_type.to_s
end
def flash_messages(opts = {})
flash.each do |msg_type, message|
concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)} fade in") do
concat message
end)
end
nil
end
# before filters
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless current_user?(@user)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
Orders.js
$('#payment-form').submit(function(event) {
var form = $(this);
form.find('button').prop('disabled', true);
Stripe.createToken(form, stripeResponseHandler);
return false;
});
回答1:
Working Order Form below, you cannot pass fields to Stripe and use them in the rails form to save in the db, its either or (previous example attempted to save address fields in the db and pass them to stripe as part of the transaction validation.
<%= form_for(@order, :html => {:id => 'payment-form'}) do |f| %>
<% if @order.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@order.errors.count, "error") %> prohibited this order from being saved:</h2>
<ul>
<% @order.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= "Order Total: #{order_total.to_s}" %>
<%= "Order Currency: #{order_currency.to_s}" %>
</div>
<div class="form-row">
<label>Full Name</label>
<input type="text" size="20" autocomplete="off" data-stripe="name" />
</div>
<div class="field">
<%= f.label :email %><br>
<%= f.text_field :email, :placeholder => "you@example.com" %>
</div>
<div class="field">
<%= f.label :Payment_Type %><br>
<%= f.select :pay_type, Order::PAYMENT_TYPES, prompt: 'Select a payment method', :selected => "credit card" %>
</div>
<div class="form-row">
<label>Address Line 1</label>
<input type="text" size="20" autocomplete="off" data-stripe="address_line1" />
</div>
<div class="form-row">
<label>Address Line 2</label>
<input type="text" size="20" autocomplete="off" />
</div>
<div class="form-row">
<label>Address City</label>
<input type="text" size="20" autocomplete="off" />
</div>
<div class="form-row">
<label>Address State</label>
<input type="text" size="20" autocomplete="off" />
</div>
<div class="form-row">
<label>Zip/Postcode</label>
<input type="text" size="20" autocomplete="off" data-stripe="address_zip" />
</div>
<div class="form-row">
<label>Country</label>
<input type="text" size="20" autocomplete="off" data-stripe="address_country" />
</div>
<div class="form-row">
<label>Card Number</label>
<input type="text" size="20" autocomplete="off" data-stripe="number" placeholder = "**** **** **** ****" pattern="[\d ]*" />
</div>
<div class="form-row">
<label>Security Code/CVC</label>
<input type="text" size="4" autocomplete="off" data-stripe="cvc" placeholder="***" pattern="\d*" />
</div>
<div class="form-row">
<label>Expiration (MM/YYYY)</label>
<input type="text" size="2" data-stripe="exp-month" placeholder="MM" pattern="\d*" />
<span> / </span>
<input type="text" size="4" data-stripe="exp-year" placeholder="YYYY" pattern="\d*" />
</div>
<div class="actions">
<%= f.submit 'Pay', :class => "button" %>
</div>
<% end %>
The form required the id referenced inthe javascript below
var stripeResponseHandler = function(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 {
// token contains id, last4, and card type
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 re-submit
$form.get(0).submit();
}
};
jQuery(function($) {
$('#payment-form').submit(function(e) {
var $form = $(this);
// Disable the submit button to prevent repeated clicks
$form.find('button').prop('disabled', true);
Stripe.card.createToken($form, stripeResponseHandler);
// Prevent the form from submitting with the default action
return false;
});
});
Order Model, fields removed
class Order < ActiveRecord::Base
PAYMENT_TYPES = ["credit card"]
CC_COUNTRIES = ["United Kingdom", "France", "Italy"]
validates :email, presence: true
validates :pay_type, inclusion: PAYMENT_TYPES
has_many :line_items, dependent: :destroy
def add_line_items_from_cart(cart)
cart.line_items.each do |item|
item.cart_id = nil
line_items << item
end
end
end
Application header
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script type="text/javascript">
$(function(){
Stripe.setPublishableKey('<%= Rails.configuration.stripe[:publishable_key] %>');
});
</script>
<%= csrf_meta_tags %>
<%= render 'layouts/shim' %>
</head>
<body>
Order Controller
class OrdersController < ApplicationController
include CurrentCart
before_action :set_cart, only: [:new, :create]
before_action :set_order, only: [:show,:edit, :update, :destroy]
# before_action :admin_user, only: [:destroy, :show]
# GET /orders
# GET /orders.json
def index
@orders = Order.all
end
# GET /orders/1
# GET /orders/1.json
def show
end
# GET /orders/new
def new
if @cart.line_items.empty?
redirect_to store_url, notice: "Your cart is empty"
return
end
@order = Order.new
end
# GET /orders/1/edit
def edit
end
# POST /orders
# POST /orders.json
def create
begin
@order = Order.new(order_params)
@order.add_line_items_from_cart(@cart)
Stripe.api_key = "sk_test_xxxxxxxxxxxxxxx"
@amount = order_total.to_i * 100
token = params[:stripeToken]
# Create a Customer
customer = Stripe::Customer.create(
:description => order_params[:email],
:card => token,
)
charge = Stripe::Charge.create(
:customer => customer.id,
:amount => @amount, # amount in cents, again
:currency => order_currency,
# :card => token,
:description => order_params[:email]
)
respond_to do |format|
if @order.save
puts "saving now"
Cart.destroy(session[:cart_id])
session[:cart_id] = nil
format.html { redirect_to @order, notice: 'Order was successfully created.' }
format.json { render action: 'show', status: :created, location: @order }
else
format.html { render action: 'new' }
format.json { render json: @order.errors, status: :unprocessable_entity }
end
end
rescue Stripe::CardError => e
flash[:error] = e.message
redirect_to root_path
end
end
# PATCH/PUT /orders/1
# PATCH/PUT /orders/1.json
def update
respond_to do |format|
if @order.update(order_params)
format.html { redirect_to @order, notice: 'Order was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: @order.errors, status: :unprocessable_entity }
end
end
end
# DELETE /orders/1
# DELETE /orders/1.json
def destroy
@order.destroy
respond_to do |format|
format.html { redirect_to orders_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_order
@order = Order.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def order_params
params.require(:order).permit(:email, :pay_type)
end
end
来源:https://stackoverflow.com/questions/30442434/rails-4-stripe-payment-causing-line-item-invalid-error