Включение информации о карте Stripe в форму ajax удаляет authenticity_token - PullRequest
0 голосов
/ 09 февраля 2020

У меня есть form_tag, использующий Ajax для сохранения новой пользовательской карты с Stripe. Он отображает форму карты Stripe, передает входные данные в метод контроллера, а затем должен обрабатывать файл js. Он работает без Stripe, но с ним у меня возникают проблемы с аутентификацией.

Вот базовый c код:

<%= form_tag(save_card_path, id:'payment-form', remote: true) do %>
    <div id="card-element">
      <!-- A Stripe Element will be inserted here. -->
    </div>
  <button id="submit-card" class="submit-btn">Save Card</button>
<% end %>

<script type="text/javascript">

  var stripe = Stripe('<%= @stripe_public %>');
  var elements = stripe.elements();

  // Custom styling can be passed to options when creating an Element.
  var style = {
    base: {
      // Add your base input styles here. For example:
      fontSize: '20px',
      color: "#32325d",
    }
  };

  // Create an instance of the card Element.
  var card = elements.create('card', {style: style});

  // Add an instance of the card Element into the `card-element` <div>.
  card.mount('#card-element');

  card.addEventListener('change', function(event) {
    var displayError = document.getElementById('card-errors');
    if (event.error) {
      displayError.textContent = event.error.message;
    } else {
      displayError.textContent = '';
    }
  });

  // Create a token or display an error when the form is submitted.
  var form = document.getElementById('payment-form');
  form.addEventListener('submit', function(event) {
    event.preventDefault();

    stripe.createToken(card).then(function(result) {
      if (result.error) {
        // Inform the customer that there was an error.
        var errorElement = document.getElementById('card-errors');
        errorElement.textContent = result.error.message;
      } else {
        // Send the token to your server.
        stripeTokenHandler(result.token);
      }
    });
  });

  function stripeTokenHandler(token) {
    // Insert the token ID into the form so it gets submitted to the server
    var form = document.getElementById('payment-form');
    var hiddenInput = document.createElement('input');
    hiddenInput.setAttribute('type', 'hidden');
    hiddenInput.setAttribute('name', 'stripeToken');
    hiddenInput.setAttribute('value', token.id);
    form.appendChild(hiddenInput);

    // Submit the form
    form.submit();
  }
</script>

card_controller.rb

def save_card
  respond_to do |format|
    format.js
  end
end

save_card. js .erb

$("html").hide();

Итак, как я уже сказал, без кода Stripe все работает нормально, и в параметрах присутствует authenticity_token, но код, как я его написал выше выдает следующую ошибку:

    def handle_unverified_request
      raise ActionController::InvalidAuthenticityToken
    end

только с параметрами:

{"utf8"=>"✓", "stripeToken"=>"<token>"}

Когда я добавляю к форме параметр authenticity_token: true, параметры снова содержат authenticity_token , но теперь, когда он попадает в строку format.js, я получаю сообщение об ошибке

ActionController::UnknownFormat

Я сталкивался с подобной проблемой прежде, пытаясь загрузить файлы в формах через Ajax, но я обнаружил, камень remotipart , и это решило его. Но, похоже, это не поможет в этом случае.

Кто-нибудь знает, почему включение поля Stripe избавило бы от моего authenticity_token и почему даже при использовании authenticity_token формат js не распознается?


РЕДАКТИРОВАНИЕ:

Jquery -u js включено через строку //= require jquery_ujs в моем application.js, строка <%= csrf_meta_tags %> включена в application.html.erb , и мой исходный код включает в себя строки

<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="<TOKEN>" />

Это тот случай, если мой вызов ajax работает или нет.

ОБНОВЛЕНИЕ:

Это мой полный журнал когда я добавляю опцию authenticity_token: true:

Запущена POST "/ save_card" для 127.0.0.1 в 2020-02-20 00:07:04 + 0100

Обработка UsersController #save_card as HTML

Параметры: {"utf8" => "✓", "authenticity_token" => "[LONG TOKEN]", "post" => "47", "транзакция" => "bid", "stripeToken" => "[STRIPE TOKEN]"}

Загрузка пользователя (0,9 мс)

ВЫБРАТЬ "users". * ИЗ "users" WHERE "users". " id "= $ 1 ORDER BY" users "." id "AS C LIMIT $ 2 [[" id ", 2], [" LIMI T ", 1]]

(0,3 мс) НАЧАЛО SQL

(2,3 мс) INSERT INTO" cards "(" stripe_customer_id "," brand "," last4 "," exp_month "," exp_year "," user_id "," creation_at "," updated_at ") ЗНАЧЕНИЯ ($ 1, $ 2, $ 3, $ 4, $ 5, $ 6, $ 7, $ 8) RETURNING" id "[[" stripe_customer_id "," [STRIPE TOKEN ] "], [" brand "," Visa "], [" last4 "," 4242 "], [" exp_month "," 4 "], [" exp_year "," 2024 "], [" user_id ", 2 ], ["creation_at", "2020-02-19 23: 07: 05.246789"], ["updated_at", "2020-02-19 23: 07: 05.246789"]]

(2,8 мс) COMMIT

Выполнено 406 Недопустимо в 672 мс (ActiveRecord: 6,3 мс)

ActionController :: UnknownFormat (ActionController :: UnknownFormat): app / controllers / users_controller.rb: 485: в `save_card ' Запущен GET "/serviceworker.js" для 127.0.0.1 в 2020-02-20 00:07:05 +0100 Запущен GET "/serviceworker.js" для :: 1 в 2020-02-20 00:07 : 21 + 0100

и это мой журнал, когда я удаляю authenticity_token: true:

Запущена POST "/ save_card" для 127.0.0.1 в 2020-02-20 00:33:17 + 0100

Обработка UsersController # save_card как HTML

Параметры: {"utf8" => "✓", "post" => "47", "транзакция" => " bid "," stripeToken "=>" [STRIPE TOKEN] "} Невозможно проверить подлинность токена CSRF. Выполнено 422 необработанных объекта в 1 мс (ActiveRecord: 0,0 мс)

ActionController :: InvalidAuthenticityToken (ActionController :: InvalidAuthenticityToken): actionpack (5.1.7) lib / action_controller / metal / request_forgery_protectionr. * handle_unverified_request 'devise (4.7.0) lib / devise / controllers / helpers.rb: 255: в handle_unverified_request' actionpack (5.1.7) lib/action_controller/metal/request_forgery_protection.rb:222:in verify_authenticity_token' activesupport (5.1.7) lib / active_support / callbacks.rb: 413: в block in make_lambda' activesupport (5.1.7) lib/active_support/callbacks.rb:197:in блок (2 уровни) в остановке 'actionpack (5.1.7) lib / abstract_controller / callbacks.rb: 12: в block (2 levels) in <module:Callbacks>' activesupport (5.1.7) lib/active_support/callbacks.rb:198:in блок в остановке' activesupport (5.1.7) lib / active_support / callbacks.rb: 507: в block in invoke_before' activesupport (5.1.7) lib/active_support/callbacks.rb:507:in каждый 'activesupport (5.1.7) lib / active_support / callbacks.rb: 507: в invoke_before' activesupport (5.1.7) lib/active_support/callbacks.rb:130:in Пакет действий * run_callbacks (5.1.7) instrumentation.rb: 32: в блоке block in process_action' activesupport (5.1.7) lib/active_support/notifications.rb:166:in в инструменте activesupport (5.1.7) lib / active_support / notifications / instrumenter.rb: 21: в instrument' activesupport (5.1.7) lib/active_support/notifications.rb:166:in инструменте actionpack (5.1.7) lib / action_controller / metal / instrumentation.rb: 30: в process_action' actionpack (5.1.7) lib/action_controller/metal/params_wrapper.rb:252:in process_action 'activerecord (5.1.7) lib / active_record / railties / controller_runtime.rb: 22: в process_action' actionpack (5.1.7) lib/abstract_controller/base.rb:124:in process' actionview (5.1.7) lib / action_view / render.rb: 30: в process' actionpack (5.1.7) lib/action_controller/metal.rb:189:in диспетчер 'actionpack (5.1.7) lib / action_controller / metal.rb: 253: в dispatch' actionpack (5.1.7) lib/action_dispatch/routing/route_set.rb:49:in диспетчеризация' actionpack (5.1.7) lib / action_dispatch / routing / route_set.rb: 31: в serve' actionpack (5.1.7) lib/action_dispatch/journey/router.rb:50:in block in serve 'actionpack (5.1.7) lib / action_dispatch / trip / router.rb: 33: в each' actionpack (5.1.7) lib/action_dispatch/journey/router.rb:33:in serve' actionpack (5.1.7) lib / action_dispatch / routing / route_set.rb: 844: в call' serviceworker-rails (0.6.0) lib/serviceworker/middleware.rb:35:in call 'remotipart (1.4.3) lib / remotipart / middleware.rb: 32: в call' warden (1.2.8) lib/warden/manager.rb:36:in блок в call' смотрителе (1.2.8) l ib / warden / manager.rb: 34: в catch' warden (1.2.8) lib/warden/manager.rb:34:in call 'rack (2.0.7) lib / rack / etag.rb: 25: в call' rack (2.0.7) lib/rack/conditional_get.rb:38:in call' rack (2.0.7) lib / rack / head. rb: 12: в call' rack (2.0.7) lib/rack/session/abstract/id.rb:232:in контексте 'rack (2.0.7) lib / rack / session / abstract / id.rb: 226: в call' actionpack (5.1.7) lib/action_dispatch/middleware/cookies.rb:613:in call' activerecord (5.1.7) lib / active_record /igration.rb: 556: в call' actionpack (5.1.7) lib/action_dispatch/middleware/callbacks.rb:26:in блоке в call 'activesupport (5.1.7) lib / active_support / callbacks.rb: 97: в run_callbacks' actionpack (5.1.7) lib/action_dispatch/middleware/callbacks.rb:24:in call' actionpack (5.1.7) lib / action_dispatch / middleware / executor.rb: 12: в call' actionpack (5.1.7) lib/action_dispatch/middleware/debug_exceptions.rb:59:in вызовите веб-консоль (3.7.0) lib / web_console / middleware.rb: 135: в call_app' web-console (3.7.0) lib/web_console/middleware.rb:30:in заблокируйте в вызове веб-консоль (3.7.0) lib / web_console / middleware.rb: 20: в catch' web-console (3.7.0) lib/web_console/middleware.rb:20:in call 'actionpack (5.1.7) lib / action_dispatch / middleware / show_exceptions.rb: 31: в call' railties (5.1.7) lib/rails/rack/logger.rb:36:in call_app' railties (5.1.7) lib / rails / rack / logger.rb: 24: в block in call' activesupport (5.1.7) lib/active_support/tagged_logging.rb:69:in блок в тэге 'activesupport (5.1.7) lib / active_support / tagged_logging.rb: 26: в tagged' activesupport (5.1.7) lib/active_support/tagged_logging.rb:69:in тэге' railties (5.1.7) lib / rails / rack / logger.rb: 24: в call' sprockets-rails (3.2.1) lib/sprockets/rails/quiet_assets.rb:13:in call 'actionpack (5.1.7) lib / action_dispatch / middleware / remote_ip.rb: 79: в call' actionpack (5.1.7) lib/action_dispatch/middleware/request_id.rb:25:in call' rack (2.0.7) lib / rack / method_override. rb: 22: в call' rack (2.0.7) lib/rack/runtime.rb:22:in вызов 'activesupport (5.1.7) lib / active_support / cache / стратегии / local_cache_middleware.rb: 27: в call' actionpack (5.1.7) lib/action_dispatch/middleware/executor.rb:12:in вызов "actionpack (5.1.7) lib / action_dispatch / middleware / stati c .rb: 125: in call' rack (2.0.7) lib/rack/sendfile.rb:111:in call 'railties (5.1.7) lib / rails / engine.rb: 522: in call' puma (3.12.1) lib/puma/configuration.rb:227:in call' puma (3.12.1) lib / puma / server.rb: 660 : в handle_request' puma (3.12.1) lib/puma/server.rb:474:in process_client 'puma (3.12.1) lib / puma / server.rb: 334: в block in run' puma (3.12.1) lib/puma/thread_pool.rb:135:in блоке в spawn_thread' Запущено GET "/serviceworker.js" для 127.0.0.1 в 2020-02- 20 00:33:18 + 0100

Ответы [ 3 ]

1 голос
/ 20 февраля 2020

Сохраните все как было, и просто обновите .submit()

Фактическая проблема отправляется как HTML вместо JS на form.submit().

https://github.com/rails/rails/issues/29546

 //from
 form.submit();

 //to
 form.dispatchEvent(new Event('submit', {bubbles: true})); 

Обновлено

Ранее вы отправляли, а затем просили отправить его снова. OnClick кнопку, получить данные полосы, а затем отправить с моим ответом. Пример ниже.

Также вам нужно добавить disable/enable для кнопки отправки при клике, чтобы игнорировать несколько кликов.

...
// Create a token or display an error when the form is submitted.
  let submitCardBtn = document.getElementById('submit-card');

  submitCardBtn.addEventListener('click', function(event) {
    event.preventDefault();

   // disable button
    submitCardBtn.disabled = true;


    stripe.createToken(card).then(function(result) {
      if (result.error) {
        // Inform the customer that there was an error.
        var errorElement = document.getElementById('card-errors');
        errorElement.textContent = result.error.message;

        // enable button on false
        submitCardBtn.disabled = false; 
      } else {
        // Send the token to your server.
        stripeTokenHandler(result.token);
      }
    });
  });

  function stripeTokenHandler(token) {
    // Insert the token ID into the form so it gets submitted to the server
    var form = document.getElementById('payment-form');
    var hiddenInput = document.createElement('input');
    hiddenInput.setAttribute('type', 'hidden');
    hiddenInput.setAttribute('name', 'stripeToken');
    hiddenInput.setAttribute('value', token.id);
    form.appendChild(hiddenInput);

    // Submit the form
   form.dispatchEvent(new Event('submit', {bubbles: true})); 

   // enable button
   submitCardBtn.disabled = false;
  }

1 голос
/ 20 февраля 2020

Если вы находитесь на Rails 5.X и у вас есть //= require rails-ujs в вашем application.js файле

Вместо form.submit(), использование Rails.fire(form, 'submit') позволит вам отправить форму через Ajax

1 голос
/ 17 февраля 2020

Не могли бы вы попробовать следующее:

Добавьте опцию authenticity_token: true в форму, а затем параметры еще раз содержат authenticity_token. И переименуйте свой save_card.js в save_card.js.erb.

...