Элемент Stripe card не отображается в моем приложении Rails

#javascript #ruby-on-rails #ruby #webpack #turbolinks

#javascript #ruby-on-rails #рубиновый #веб-пакет #turbolinks

Вопрос:

Я создаю простое приложение rails, которое использует Stripe для оплаты картой, но поле ввода карты не отображается при запуске приложения. Для меня все выглядит нормально, но по какой-то причине это не отображается. я использую Rails 5.2.4.4, Ruby 2.7. Вот мой код:

/приложение/представления/макет/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> amp;nbsp;amp;nbsp; 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? %> amp;nbsp; <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>
 

/приложение/просмотры/макет/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/активы/ javascripts/подписки.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? amp;amp; 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: "amp;#x1f389; 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 в subscriptions.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
 

Комментарии:

1. Можете ли вы проверить представление и убедиться, что есть соответствующий мета-элемент <%= tag :meta, name: "stripe-key:", content: Rails.application.credentials.stripe_publishable_key %> . Это действительно есть на странице, когда вы ее просматриваете?

2. Здесь, по-видимому, есть одно «да» <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>

3. Может показаться странным, но если вы переместите этот мета-тег так, чтобы он находился над файлами javascript, это сработает?

4. Просто попробовал переместить мета-тег над javascript, а также попробовал над таблицами стилей, но все равно безуспешно : (

5. Отлаживать их может быть сложно. По моему опыту, эти cannot read content of null ошибки JS, как правило, возникают, когда он ищет элемент, который еще не загружен на страницу, поэтому он равен null. Похоже, что он поступает из subscriptions.js файла.

Ответ №1:

Была ли это проблема неправильного выбора запроса? Я заметил, что вы объявляете имя мета-тега как «stripe-key:» (с завершающим двоеточием), но выбираете элемент с document.querySelector("meta[name='stripe-key']") помощью (без завершающего двоеточия в имени элемента)

Комментарии:

1. вы убедились, что ваш ответ работает, или это просто не уверенный тип ваших мыслей?