kahoxx121u
@kahoxx121u (kaho)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Token can't be blankと表示される(javascript)

pay.jpを使用してフリマアプリの購入画面を実装しています。
order#createにてbinding.pryをすると以下のようにtokenがnilと表示され、購入ボタンを押しても、「Token can't be blank」と表示されます。
javascript内の記載が誤っていると予想して、色々と試しましたがわかりませんでした。
初歩的で申し訳ございませんが、ヒントをいただけると大変助かります。

そしてjavascript内の以下の記載もあっているのか不安です。
#のあとにindexに記載しているidを入力すると認識しているのですが、合っておりますでしょうか。

numberElement.mount('#card-number');
expiryElement.mount('#card-exp-month');
expiryElement.mount('#card-exp-year');
cvcElement.mount('#card-cvc');

エラーコード

[2] pry(#<OrdersController>)> order_params
Unpermitted parameters: :number, :exp_month, :exp_year, :cvc
=> <ActionController::Parameters {"postal_number"=>"520-0801", "prefecture_id"=>"2", "city"=>"あ", "address"=>"1", "building_name"=>"", "telephone_number"=>"09056401111", "user_id"=>12, "item_id"=>"12", "token"=>nil} permitted: true>

paramsと入力した場合のエラーでは、tokenの記載があります。

[1] pry(#<OrdersController>)> params
=> <ActionController::Parameters {"authenticity_token"=>"w03XQzQ8VpU+tQuew/LXNW2J1eNMrOTKgViG1UX9S+F8aIXuqL9mblO2AnaRXaARmUzI+r//m93udMNVi54KAw==", "order_payment"=>{"number"=>"12345678123456", "exp_month"=>"12", "exp_year"=>"24", "cvc"=>"234", "postal_number"=>"520-0801", "prefecture_id"=>"2", "city"=>"あ", "address"=>"1", "building_name"=>"", "telephone_number"=>"09056401111"}, "controller"=>"orders", "action"=>"create", "item_id"=>"12"} permitted: false>

コントローラー

class OrdersController < ApplicationController
  before_action :authenticate_user!

def index
  @item = Item.find(params[:item_id])
  @order_payment = OrderPayment.new
end

def create
  @item = Item.find(params[:item_id])
  binding.pry
  @order_payment = OrderPayment.new(order_params)
    if @order_payment.valid?
     pay_item
     @order_payment.save
       return redirect_to root_path
    else
      render :index
    end
end

private

  def order_params
    params.require(:order_payment).permit(:postal_number, :prefecture_id, :city, :address, :building_name, :telephone_number).merge(user_id: current_user.id, item_id: params[:item_id],token: params[:token])
  end

  def pay_item
    Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
    Payjp::Charge.create(
      amount: @item.price,  # 商品の値段
      card: order_params[:token],    # カードトークン
      currency: 'jpy'                 # 通貨の種類(日本円)
    )
  end

javascript(card.js)

const pay = () => {
  const payjp = Payjp(process.env.PAYJP_PUBLIC_KEY)
  const elements = payjp.elements();
  const numberElement = elements.create('cardNumber');
  const expiryElement = elements.create('cardExpiry');
  const cvcElement = elements.create('cardCvc');

  numberElement.mount('#card-number');
  expiryElement.mount('#card-exp-month');
  expiryElement.mount('#card-exp-year');
  cvcElement.mount('#card-cvc');

  const submit = document.getElementById("button");

  submit.addEventListener("click", (e) => {
    e.preventDefault();
    payjp.createToken(numberElement).then(function (response) {
      if (response.error) {
      } else {
        const token = response.id;
        const renderDom = document.getElementById("charge-form");
        const tokenObj = `<input value=${token} type="hidden" name='token'>`;
        renderDom.insertAdjacentHTML("beforeend", tokenObj);
      }
      numberElement.clear();
      expiryElement.clear();
      cvcElement.clear();
      document.getElementById("charge-form").submit();
    });
  });
};
window.addEventListener("load", pay);

index.html

<%= render "shared/second-header"%>

<div class='transaction-contents'>
  <div class='transaction-main'>
    <h1 class='transaction-title-text'>
      購入内容の確認
    </h1>
    <%# 購入内容の表示 %>
    <div class='buy-item-info'>
      <%= image_tag @item.image, class: 'buy-item-img' %>
      <div class='buy-item-right-content'>
        <h2 class='buy-item-text'>
          <%= @item.item_name %>
        </h2>
        <div class='buy-item-price'>
          <p class='item-price-text'>¥<%= @item.price %></p>
          <p class='item-price-sub-text'><%= @item.delivery_charge.delivery_charge %></p>
        </div>
      </div>
    </div>
    <%# /購入内容の表示 %>

    <%# 支払額の表示 %>
    <div class='item-payment'>
      <h1 class='item-payment-title'>
        支払金額
      </h1>
      <p class='item-payment-price'>
        ¥<%= @item.price %>
      </p>
    </div>
    <%# /支払額の表示 %>

    <%= form_with(model: @order_payment, url: item_orders_path(@item), id: 'charge-form', class: 'transaction-form-wrap',local: true ) do |f| %>
    <%= render 'shared/error_messages', model: f.object %>

    <%# カード情報の入力 %>
    <div class='credit-card-form'>
      <h1 class='info-input-haedline'>
        クレジットカード情報入力
      </h1>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">カード情報</label>
          <span class="indispensable">必須</span>
        </div>
        <%= f.text_field :number, class:"input-default", id:"card-number", placeholder:"カード番号(半角英数字)", maxlength:"16" %>
        <div class='available-card'>
          <%= image_tag 'card-visa.gif', class: 'card-logo'%>
          <%= image_tag 'card-mastercard.gif', class: 'card-logo'%>
          <%= image_tag 'card-jcb.gif', class: 'card-logo'%>
          <%= image_tag 'card-amex.gif', class: 'card-logo'%>
        </div>
      </div>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">有効期限</label>
          <span class="indispensable">必須</span>
        </div>
        <div class='input-expiration-date-wrap'>
          <%= f.text_area :exp_month, class:"input-expiration-date", id:"card-exp-month", placeholder:"例)3" %>
          <p>月</p>
          <%= f.text_area :exp_year, class:"input-expiration-date", id:"card-exp-year", placeholder:"例)23" %>
          <p>年</p>
        </div>
      </div>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">セキュリティコード</label>
          <span class="indispensable">必須</span>
        </div>
        <%= f.text_field :cvc, class:"input-default", id:"card-cvc", placeholder:"カード背面4桁もしくは3桁の番号", maxlength:"4" %>
      </div>
    </div>
    <%# /カード情報の入力 %>
    
    <%# 配送先の入力 %>
    <div class='shipping-address-form'>
      <h1 class='info-input-haedline'>
        配送先入力
      </h1>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">郵便番号</label>
          <span class="indispensable">必須</span>
        </div>
        <%= f.text_field :postal_number, class:"input-default", id:"postal-code", placeholder:"例)123-4567", maxlength:"8" %>
      </div>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">都道府県</label>
          <span class="indispensable">必須</span>
        </div>
        <%= f.collection_select(:prefecture_id, Prefecture.all, :id, :name, {}, {class:"select-box", id:"prefecture"}) %>
      </div>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">市区町村</label>
          <span class="indispensable">必須</span>
        </div>
        <%= f.text_field :city, class:"input-default", id:"city", placeholder:"例)横浜市緑区"%>
      </div>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">番地</label>
          <span class="indispensable">必須</span>
        </div>
        <%= f.text_field :address, class:"input-default", id:"addresses", placeholder:"例)青山1-1-1"%>
      </div>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">建物名</label>
          <span class="form-any">任意</span>
        </div>
        <%= f.text_field :building_name, class:"input-default", id:"building", placeholder:"例)柳ビル103"%>
      </div>
      <div class="form-group">
        <div class='form-text-wrap'>
          <label class="form-text">電話番号</label>
          <span class="indispensable">必須</span>
        </div>
        <%= f.text_field :telephone_number, class:"input-default", id:"phone-number", placeholder:"例)09012345678",maxlength:"11"%>
      </div>
    </div>
    <%# /配送先の入力 %>
    <div class='buy-btn'>
      <%= f.submit "購入" ,class:"buy-red-btn", id:"button" %>
    </div>
    <% end %>
  </div>
</div>
<%= render "shared/second-footer"%>

バリデーション

class OrderPayment
  include ActiveModel::Model
  attr_accessor :postal_number, :prefecture_id, :city, :address, :building_name, :telephone_number, :user_id, :item_id, :token
 
  #バリデーションを記載する
  with_options presence: true do
    validates :user_id
    validates :item_id
    validates :postal_number, format: {with: /\A[0-9]{3}-[0-9]{4}\z/, message: "is invalid. Include hyphen(-)"}
    validates :prefecture_id, numericality: { other_than: 1, message: "can't be blank" }
    validates :city
    validates :address
    validates :telephone_number, format: {with: /\A[0-9]{11}\z/, message: 'is invalid' }
    validates :token
  end

  #データをテーブルに保存する処理
  def save
    # 発送先情報を保存し、変数orderに代入する
    order = Order.create(item_id: item_id, user_id: user_id)
    # 住所を保存する
    # order_idには、変数orderのidと指定する
    Payment.create(postal_number: postal_number, prefecture_id: prefecture_id, city: city, address: address, building_name: building_name, telephone_number: telephone_number, order_id: order.id)
  end
end

テーブル

class CreatePayments < ActiveRecord::Migration[6.0]
  def change
    create_table   :payments do |t|
      t.string     :postal_number,     null: false
      t.integer    :prefecture_id,     null: false
      t.string     :city,              null: false
      t.string     :address,           null: false
      t.string     :building_name
      t.string     :telephone_number,  null: false
      t.references :order,             null: false, foreign_key:true
      t.timestamps
    end
  end
end
1

1Answer

paramstokenはないように見えます(あるのはauthenticity_token(CSRFトークン)のみ)
まずは以下を確認してみてはいかがでしょうか?

response.errorがtrueであるか

if (response.error) {
  console.error(`message: ${response.error.message}`);
  console.error(`status: ${response.error.status}`);
  console.error(`type: ${response.error.type}`);
}else{
  // 省略
}
numberElement.clear();
expiryElement.clear();
cvcElement.clear();
// document.getElementById("charge-form").submit(); サブミットせずにログを確認できるようにしておく

エラーレスポンスの参考

エラーになっていないようであれば次はなんでRails側へのリクエストに乗らないのか調査をします。

1Like

Comments

  1. @kahoxx121u

    Questioner

    ありがとうございます。コンソール上では、
    ```
    Uncaught Error: mountに指定されたセレクタが現在ページに存在しません。
    at t.mount (pay.js:1:11100)
    at pay (card.js:8:1)
    ```
    とエラーが出ました。tokenにはデータが入っていないようです。
    javascriptのmount指定が誤っていると記載が出ましたので、以下に記載を変更してみましたが、同じくtokenエラーが出ます。
    ```
    numberElement.mount('#numberーform');
    expiryElement.mount('#expiry-form');
    cvcElement.mount('#cvc-form');
    ```
  2. リファレンスを確認したところ`mount`の使いかたが違うみたいです。
    元のHTML上には`card-number`等の要素は不要で、numberElement.mount('#id');では当該のinput項目をラップしてほしい親要素を指定します。
    mountを実行することにより入力フォームの要素がDOMに追加されます。

    参考:https://pay.jp/docs/payjs#element-mount

Your answer might help someone💌