条纹卡片元素未显示在我的 Rails 应用中
Stripe card element is not showing in my Rails app
我正在创建一个简单的 rails 应用程序,它使用 Stripe 进行卡支付,但是当我 运行 该应用程序时,卡输入字段没有显示。我觉得一切都很好,但由于某种原因没有显示出来。我正在使用 Rails 5.2.4.4,Ruby 2.7。这是我的代码:
/app/views/layout/application.html.erb :
<!DOCTYPE html>
<html>
<head>
<title><%= Rails.configuration.application_name %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<meta name="viewport" content="width=device-width, initial-scale=1">
<%= stylesheet_link_tag 'https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css' %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'https://js.stripe.com/v3/', 'data-turbolinks-track': 'reload' %>
<%= tag :meta, name: "stripe-key:", content: Rails.application.credentials.stripe_publishable_key %>
</head>
<body class="<%= yield (:body_class) %>">
<% if flash[:notice] %>
<div class="notification is-success global-notification">
<p class="notice"><%= notice %></p>
</div>
<% end %>
<% if flash[:alert] %>
<div class="notification is-danger global-notification">
<p class="alert"><%= alert %></p>
</div>
<% end %>
<nav class="navbar is-light" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<%= link_to root_path, class:"navbar-item" do %>
<h1 class="title is-5"><%= Rails.configuration.application_name %></h1>
<% end %>
<div class="navbar-burger burger" data-target="navbar">
<span></span>
<span></span>
<span></span>
</div>
</div>
<div id="navbar" class="navbar-menu">
<div class="navbar-start">
<% if subscribed? %>
<div class="navbar-item">
<%= link_to library_index_path, class: 'navbar-item button is-dark' do %>
<i class="fa fa-book"></i> My Bookcase
<% end %>
</div>
<% end %>
</div>
<div class="navbar-end">
<div class="navbar-item">
<% if admin? %>
<%= link_to 'New Book', new_book_path, class:'button is-dark' %>
<% end%>
<div class="field is-grouped">
<% if user_signed_in? %>
<div class="navbar-item has-dropdown is-hoverable">
<%= link_to 'Account', edit_user_registration_path, class: "navbar-link" %>
<div class="navbar-dropdown is-right">
<%= link_to edit_user_registration_path, class:"navbar-item" do %>
<%= current_user.name %> <% if admin? %> <span class="tag is-warrning">ADMIN</span> <% end %>
<% end %>
<%= link_to "Log Out", destroy_user_session_path, method: :delete, class:"navbar-item" %>
</div>
</div>
<% else %>
<p class="control">
<%= link_to 'Pricing', pricing_index_path, class: 'navbar-item button is-light' %>
</p>
<p class="control">
<%= link_to "Sign In", new_user_session_path, class:"navbar-item button is-light" %>
</p>
<p class="control">
<%= link_to "Sign up", new_user_registration_path, class:"navbar-item button is-light"%>
</p>
<% end %>
</div>
</div>
</div>
</div>
</nav>
<div class="container">
<%= yield %>
</div>
</body>
</html>
/app/views/layout/subscribe.html.erb :
<!DOCTYPE html>
<html>
<head>
<title><%= Rails.configuration.application_name %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<meta name="viewport" content="width=device-width, initial-scale=1">
<%= stylesheet_link_tag 'https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css' %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'https://js.stripe.com/v3/', 'data-turbolinks-track': 'reload' %>
<%= tag :meta, name: "stripe-key:", content: Rails.application.credentials.stripe_publishable_key %>
</head>
<body class="<%= yield (:body_class) %>">
<% if flash[:notice] %>
<div class="notification is-success global-notification">
<p class="notice"><%= notice %></p>
</div>
<% end %>
<% if flash[:alert] %>
<div class="notification is-danger global-notification">
<p class="alert"><%= alert %></p>
</div>
<% end %>
<nav class="navbar is-light" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<div class="navbar-item">
<h1 class="title is-5"><%= Rails.configuration.application_name %></h1>
</div>
</div>
</nav>
<div class="container">
<%= yield %>
</div>
</body>
</html>
/app/assets/javascripts/subscriptions.jss
document.addEventListener("turbolinks:load", function() {
const publishableKey = document.querySelector("meta[name='stripe-key']").content;
const stripe = Stripe(publishableKey);
const elements = stripe.elements({
fonts: [{
cssSrc: "https://rsms.me/inter/inter-ui.css"
}],
locale: 'auto'
});
// Custom styling can be passed to options when creating an Element.
const style = {
base: {
color: "#32325D",
fontWeight: 500,
fontFamily: "Inter UI, Open Sans, Segoe UI, sans-serif",
fontSize: "16px",
fontSmoothing: "antialiased",
"::placeholder": {
color: "#CFD7DF"
}
},
invalid: {
color: "#E25950"
}
};
// Create an instance of the card Element.
const card = elements.create('card', { style });
// Add an instance of the card Element into the `card-element` <div>.
card.mount('#card-element');
card.addEventListener('change', ({ error }) => {
const displayError = document.getElementById('card-errors');
if (error) {
displayError.textContent = error.message;
} else {
displayError.textContent = '';
}
});
// Create a token or display an error when the form is submitted.
const form = document.getElementById('payment-form');
form.addEventListener('submit', async(event) => {
event.preventDefault();
const { token, error } = await stripe.createToken(card);
if (error) {
// Inform the customer that there was an error.
const errorElement = document.getElementById('card-errors');
errorElement.textContent = error.message;
} else {
// Send the token to your server.
stripeTokenHandler(token);
}
});
const stripeTokenHandler = (token) => {
// Insert the token ID into the form so it gets submitted to the server
const form = document.getElementById('payment-form');
const hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'stripeToken');
hiddenInput.setAttribute('value', token.id);
form.appendChild(hiddenInput);
["type", "last4", "exp_month", "exp_year"].forEach(function(field) {
addCardField(form, token, field);
});
// Submit the form
form.submit();
}
function addCardField(form, token, field) {
let hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', "user[card_" + field + "]");
hiddenInput.setAttribute('value', token.card[field]);
form.appendChild(hiddenInput);
}
});
/app/controllers/subscriptions_controller.rb
class SubscriptionsController < ApplicationController
layout "subscribe"
before_action :authenticate_user!, except: [:new, :create]
def new
if user_signed_in? && current_user.subscribed?
redirect_to root_path, notice: "You are already a subscriber!"
end
end
def create
Stripe.api_key = Rails.application.credentials.stripe_api_key
plan_id = params[:plan_id]
plan = Stripe::Plan.retrieve(plan_id)
token = params[:stripeToken]
product = Stripe::Product.retrieve(Rails.application.credentials.book_library)
customer = if current_user.stripe_id?
Stripe::Customer.retrieve(current_user.stripe_id)
else
Stripe::Customer.create(email: current_user.email, source: token)
end
subscription = customer.subscriptions.create(plan: plan.id)
options = {
stripe_id: customer.id,
stripe_subscription_id: subscription.id,
subscribed: true,
}
options.merge!(
card_last4: params[:user][:card_last4],
card_exp_month: params[:user][:card_exp_month],
card_exp_year: params[:user][:card_exp_year],
card_type: params[:user][:card_type]
) if params[:user][:card_last4]
current_user.update(options)
redirect_to root_path, notice: "🎉 Your subscription was set up successfully!"
end
def destroy
customer = Stripe::Customer.retrieve(current_user.stripe_id)
customer.subscriptions.retrieve(current_user.stripe_subscription_id).delete
current_user.update(stripe_subscription_id: nil)
redirect_to root_path, notice: "Your subscription has been cancelled."
end
end
/app/views/subscriptions/new.html.erb
<div class="section">
<div class="columns is-centered">
<div class="column is-6 border pa5">
<h1 class="title is-3">Subscribe</h1>
<hr />
<p>Chosen plan: <strong><%= params[:plan] %>
<hr/>
<%= form_tag subscriptions_path, id: "payment-form" do |form| %>
<div class="field">
<label for="card-element" class="label">Enter credit or debit card</label>
<div id="card-element">
<!-- A Stripe element will be intserted here !-->
</div>
<div id="card-errors" role="alert"></div>
<%= hidden_field_tag :plan_id, params[:plan_id] %>
<button class="button is-fullwidth is-link mt4">Submit</button>
</div>
<% end %>
</div>
</div>
</div>
控制台:(从 subscriptions.jss 中的第 2 行指向 'content')
Uncaught TypeError: Cannot read property 'content' of null
at HTMLDocument.<anonymous> (subscriptions.self-16b0862fbc44d0ce1a2c1d499b9e65be153010612892033690c2ee948affcab0.js?body=1:2)
at Object.e.dispatch (turbolinks.self-569ee74eaa15c1e2019317ff770b8769b1ec033a0f572a485f64c82ddc8f989e.js?body=1:6)
at r.notifyApplicationAfterPageLoad (turbolinks.self-569ee74eaa15c1e2019317ff770b8769b1ec033a0f572a485f64c82ddc8f989e.js?body=1:7)
at r.pageLoaded (turbolinks.self-569ee74eaa15c1e2019317ff770b8769b1ec033a0f572a485f64c82ddc8f989e.js?body=1:7)
at turbolinks.self-569ee74eaa15c1e2019317ff770b8769b1ec033a0f572a485f64c82ddc8f989e.js?body=1:6
这是查询选择器错误的问题吗?我注意到您将元标记名称声明为 "stripe-key:"(带有尾随冒号),但选择带有 document.querySelector("meta[name='stripe-key']")
的元素(没有尾随冒号元素名称)
我正在创建一个简单的 rails 应用程序,它使用 Stripe 进行卡支付,但是当我 运行 该应用程序时,卡输入字段没有显示。我觉得一切都很好,但由于某种原因没有显示出来。我正在使用 Rails 5.2.4.4,Ruby 2.7。这是我的代码:
/app/views/layout/application.html.erb :
<!DOCTYPE html>
<html>
<head>
<title><%= Rails.configuration.application_name %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<meta name="viewport" content="width=device-width, initial-scale=1">
<%= stylesheet_link_tag 'https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css' %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'https://js.stripe.com/v3/', 'data-turbolinks-track': 'reload' %>
<%= tag :meta, name: "stripe-key:", content: Rails.application.credentials.stripe_publishable_key %>
</head>
<body class="<%= yield (:body_class) %>">
<% if flash[:notice] %>
<div class="notification is-success global-notification">
<p class="notice"><%= notice %></p>
</div>
<% end %>
<% if flash[:alert] %>
<div class="notification is-danger global-notification">
<p class="alert"><%= alert %></p>
</div>
<% end %>
<nav class="navbar is-light" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<%= link_to root_path, class:"navbar-item" do %>
<h1 class="title is-5"><%= Rails.configuration.application_name %></h1>
<% end %>
<div class="navbar-burger burger" data-target="navbar">
<span></span>
<span></span>
<span></span>
</div>
</div>
<div id="navbar" class="navbar-menu">
<div class="navbar-start">
<% if subscribed? %>
<div class="navbar-item">
<%= link_to library_index_path, class: 'navbar-item button is-dark' do %>
<i class="fa fa-book"></i> My Bookcase
<% end %>
</div>
<% end %>
</div>
<div class="navbar-end">
<div class="navbar-item">
<% if admin? %>
<%= link_to 'New Book', new_book_path, class:'button is-dark' %>
<% end%>
<div class="field is-grouped">
<% if user_signed_in? %>
<div class="navbar-item has-dropdown is-hoverable">
<%= link_to 'Account', edit_user_registration_path, class: "navbar-link" %>
<div class="navbar-dropdown is-right">
<%= link_to edit_user_registration_path, class:"navbar-item" do %>
<%= current_user.name %> <% if admin? %> <span class="tag is-warrning">ADMIN</span> <% end %>
<% end %>
<%= link_to "Log Out", destroy_user_session_path, method: :delete, class:"navbar-item" %>
</div>
</div>
<% else %>
<p class="control">
<%= link_to 'Pricing', pricing_index_path, class: 'navbar-item button is-light' %>
</p>
<p class="control">
<%= link_to "Sign In", new_user_session_path, class:"navbar-item button is-light" %>
</p>
<p class="control">
<%= link_to "Sign up", new_user_registration_path, class:"navbar-item button is-light"%>
</p>
<% end %>
</div>
</div>
</div>
</div>
</nav>
<div class="container">
<%= yield %>
</div>
</body>
</html>
/app/views/layout/subscribe.html.erb :
<!DOCTYPE html>
<html>
<head>
<title><%= Rails.configuration.application_name %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<meta name="viewport" content="width=device-width, initial-scale=1">
<%= stylesheet_link_tag 'https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css' %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'https://js.stripe.com/v3/', 'data-turbolinks-track': 'reload' %>
<%= tag :meta, name: "stripe-key:", content: Rails.application.credentials.stripe_publishable_key %>
</head>
<body class="<%= yield (:body_class) %>">
<% if flash[:notice] %>
<div class="notification is-success global-notification">
<p class="notice"><%= notice %></p>
</div>
<% end %>
<% if flash[:alert] %>
<div class="notification is-danger global-notification">
<p class="alert"><%= alert %></p>
</div>
<% end %>
<nav class="navbar is-light" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<div class="navbar-item">
<h1 class="title is-5"><%= Rails.configuration.application_name %></h1>
</div>
</div>
</nav>
<div class="container">
<%= yield %>
</div>
</body>
</html>
/app/assets/javascripts/subscriptions.jss
document.addEventListener("turbolinks:load", function() {
const publishableKey = document.querySelector("meta[name='stripe-key']").content;
const stripe = Stripe(publishableKey);
const elements = stripe.elements({
fonts: [{
cssSrc: "https://rsms.me/inter/inter-ui.css"
}],
locale: 'auto'
});
// Custom styling can be passed to options when creating an Element.
const style = {
base: {
color: "#32325D",
fontWeight: 500,
fontFamily: "Inter UI, Open Sans, Segoe UI, sans-serif",
fontSize: "16px",
fontSmoothing: "antialiased",
"::placeholder": {
color: "#CFD7DF"
}
},
invalid: {
color: "#E25950"
}
};
// Create an instance of the card Element.
const card = elements.create('card', { style });
// Add an instance of the card Element into the `card-element` <div>.
card.mount('#card-element');
card.addEventListener('change', ({ error }) => {
const displayError = document.getElementById('card-errors');
if (error) {
displayError.textContent = error.message;
} else {
displayError.textContent = '';
}
});
// Create a token or display an error when the form is submitted.
const form = document.getElementById('payment-form');
form.addEventListener('submit', async(event) => {
event.preventDefault();
const { token, error } = await stripe.createToken(card);
if (error) {
// Inform the customer that there was an error.
const errorElement = document.getElementById('card-errors');
errorElement.textContent = error.message;
} else {
// Send the token to your server.
stripeTokenHandler(token);
}
});
const stripeTokenHandler = (token) => {
// Insert the token ID into the form so it gets submitted to the server
const form = document.getElementById('payment-form');
const hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'stripeToken');
hiddenInput.setAttribute('value', token.id);
form.appendChild(hiddenInput);
["type", "last4", "exp_month", "exp_year"].forEach(function(field) {
addCardField(form, token, field);
});
// Submit the form
form.submit();
}
function addCardField(form, token, field) {
let hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', "user[card_" + field + "]");
hiddenInput.setAttribute('value', token.card[field]);
form.appendChild(hiddenInput);
}
});
/app/controllers/subscriptions_controller.rb
class SubscriptionsController < ApplicationController
layout "subscribe"
before_action :authenticate_user!, except: [:new, :create]
def new
if user_signed_in? && current_user.subscribed?
redirect_to root_path, notice: "You are already a subscriber!"
end
end
def create
Stripe.api_key = Rails.application.credentials.stripe_api_key
plan_id = params[:plan_id]
plan = Stripe::Plan.retrieve(plan_id)
token = params[:stripeToken]
product = Stripe::Product.retrieve(Rails.application.credentials.book_library)
customer = if current_user.stripe_id?
Stripe::Customer.retrieve(current_user.stripe_id)
else
Stripe::Customer.create(email: current_user.email, source: token)
end
subscription = customer.subscriptions.create(plan: plan.id)
options = {
stripe_id: customer.id,
stripe_subscription_id: subscription.id,
subscribed: true,
}
options.merge!(
card_last4: params[:user][:card_last4],
card_exp_month: params[:user][:card_exp_month],
card_exp_year: params[:user][:card_exp_year],
card_type: params[:user][:card_type]
) if params[:user][:card_last4]
current_user.update(options)
redirect_to root_path, notice: "🎉 Your subscription was set up successfully!"
end
def destroy
customer = Stripe::Customer.retrieve(current_user.stripe_id)
customer.subscriptions.retrieve(current_user.stripe_subscription_id).delete
current_user.update(stripe_subscription_id: nil)
redirect_to root_path, notice: "Your subscription has been cancelled."
end
end
/app/views/subscriptions/new.html.erb
<div class="section">
<div class="columns is-centered">
<div class="column is-6 border pa5">
<h1 class="title is-3">Subscribe</h1>
<hr />
<p>Chosen plan: <strong><%= params[:plan] %>
<hr/>
<%= form_tag subscriptions_path, id: "payment-form" do |form| %>
<div class="field">
<label for="card-element" class="label">Enter credit or debit card</label>
<div id="card-element">
<!-- A Stripe element will be intserted here !-->
</div>
<div id="card-errors" role="alert"></div>
<%= hidden_field_tag :plan_id, params[:plan_id] %>
<button class="button is-fullwidth is-link mt4">Submit</button>
</div>
<% end %>
</div>
</div>
</div>
控制台:(从 subscriptions.jss 中的第 2 行指向 'content')
Uncaught TypeError: Cannot read property 'content' of null
at HTMLDocument.<anonymous> (subscriptions.self-16b0862fbc44d0ce1a2c1d499b9e65be153010612892033690c2ee948affcab0.js?body=1:2)
at Object.e.dispatch (turbolinks.self-569ee74eaa15c1e2019317ff770b8769b1ec033a0f572a485f64c82ddc8f989e.js?body=1:6)
at r.notifyApplicationAfterPageLoad (turbolinks.self-569ee74eaa15c1e2019317ff770b8769b1ec033a0f572a485f64c82ddc8f989e.js?body=1:7)
at r.pageLoaded (turbolinks.self-569ee74eaa15c1e2019317ff770b8769b1ec033a0f572a485f64c82ddc8f989e.js?body=1:7)
at turbolinks.self-569ee74eaa15c1e2019317ff770b8769b1ec033a0f572a485f64c82ddc8f989e.js?body=1:6
这是查询选择器错误的问题吗?我注意到您将元标记名称声明为 "stripe-key:"(带有尾随冒号),但选择带有 document.querySelector("meta[name='stripe-key']")
的元素(没有尾随冒号元素名称)