Я создаю простое приложение 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);
}
});
/приложение/контроллеры/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>
консоль: (указывает на «контент» из строки 2 в subscribes.jss)
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
Кажется, есть одно да, здесь <script src = "/assets/application.self-66347cf0a4cb1f26f76868b4697a9eee457c8c3a6da80c6fdd76ff77e911715e.js?body=1" data-turbolinks-track = "reload"></script> <script src = "https://js.stripe.com/v3/" data-turbolinks-track = "reload"></script> <meta name = "stripe-key:" content = "pk_test_51Hvsl0H0jrWUklo94ZerOcqYvCT45O3zX19pq4dlCZ6Cd9JVzmj1DXDz1LcsFuYhcaUcOX7gPcKsaC0vLBq3GtqG006ehhnrqL" /> </head>
Может показаться странным, но если вы переместите этот метатег так, чтобы он был над файлами javascript, он сработает?
Только что попробовал переместить метатег над javascript, а также попробовал над таблицами стилей, но все равно не повезло :(
Отладить их может быть сложно. По моему опыту, эти cannot read content of null
ошибки JS, как правило, возникают, когда он ищет элемент, который еще не загружен на страницу, поэтому он равен нулю. Похоже, это исходит из файла subscriptions.js
.
Было ли это проблемой неправильного селектора запросов? Я заметил, что вы объявляете имя метатега как «полосатый ключ:» (с двоеточием в конце), но выбираете элемент с помощью document.querySelector("meta[name='stripe-key']")
(без двоеточия в конце имени элемента)
Вы убедились, что ваш ответ работает, или это просто не уверенная мысль от вас?
Можете ли вы проверить представление и убедиться, что есть метаэлемент, соответствующий
<%= tag :meta, name: "stripe-key:", content: Rails.application.credentials.stripe_publishable_key %>
. Это на самом деле на странице, когда вы ее проверяете?