Help us understand the problem. What is going on with this article?

[HowTo]Pay.jpを用いたクレジットカードの登録機能実装について/カスタムフォーム版

某スクールのチーム開発にてpay.jpを活用したクレジットカード登録と商品購入機能実装を担当させていただくことになりました!
色々と苦戦しましたがなんとか実装できましたので、以下にまとめてみたいと思います。
今回はクレジットカード登録機能実装までまとめており、商品購入機能に関しては、以下記事をご参照いただけますと幸いです!
https://qiita.com/Tatsu88/items/eb420e372077939a4627

そもそもPay.jpとは

シンプルなAPI・多彩な機能、分かりやすい料金形態でクレジットカード決済をかんたんに導入できる決済サービスです。手数料も比較的リーズナブル(2.59%〜)であることと、導入が簡単ということもあり、スタートアップ企業などに多く採用されているようです。
イメージとしては以下のようになっており、一時的なトークンを作成し、取引をすることで、加盟店はクレジットカード情報などの重要な情報を扱う必要がないまま、取引ができます。

payjpimage.png

クレジットカード登録について

クレジットカード登録に関しては、カードの登録のフォーマットによってやり方が異なります。
- チェックアウト:pay.jp社にて用意されているフォーマットを利用する方法。
- カスタムフォーム:ご自身でフォーマットを作成し、そちらに準じてカード登録を行う方法。

今回は既に入力フォームを作成していたので、カスタムフォームにて実装を行うことにしました!

全体の流れ

0.下準備(gemインストール+application.html.hamlに追記)
1.クレジットカード番号などの情報登録:view
2.”トークン”作成:Javascript
3.”トークン”をキーとしてPayjpに顧客情報として登録:controller
4.登録情報の確認:view+controller

今回の記事では、まず、Viewの実装内容を確認いただき、JSを用いたトークンの作成をご確認いただきます。
そして、最後にコントローラでそのトークンをキーとしてPayjpに顧客登録をする動きを確認いただければと思います。

0. 下準備(gemインストール+application contorollerに追記)

下準備として、pay.jpのgemをインストールします。

Gemfile
gem 'payjp'

記載後、"bundle install"を忘れずに実行しましょう!

また、viewにあるapplication.html.hamlに以下の記述を行いましょう。

application.html.haml
%script{src: "https://js.pay.jp/", type: "text/javascript"} 
%script{type:"text/javascript"}Payjp.setPublicKey('公開鍵'); 

1.クレジットカード番号などの情報登録:view

今回は以下のようなviewで登録画面を作ってます。

612316d11d112f7fb4f670ef83a440ec.png

view
.single-container
  %header.single-header
    %h1.single-header__logo
      = link_to root_path do
        =image_tag("fmarket_logo_red.svg")
    %nav.single-header__progress
      %ol
        %li.single-header__progress__text{ id: "first" }
          会員情報
          .single-header__progress__round--red
        %li.single-header__progress__text
          お届け先住所入力
          .single-header__progress__round--red
        %li.single-header__progress__text--active
          支払い方法
          .single-header__progress__round--red
            .single-header__progress__round--red-long{ id: "long" }
        %li.single-header__progress__text{ id: "end" }
          完了
          .single-header__progress__round
  %main.single-main
    %section.single-main__container
      %h2.single-main__container__title
        支払い方法

.single-main__container__form
  .single-main__container__form__frame
    = form_for(@creditcard, url: creditcards_path,method: :post,html: {id: "form" }) do |f|
      = render "devise/shared/error_messages", resource: @creditcard
      .form-group
        = f.label :カード番号
        %span.form-group__require 必須
        = f.text_field :card_number, {placeholder: "半角数字のみ", class: "form-group__input",maxlength:"16"}
      .form-group
        = f.label :カード会社
        %span.form-group__require 必須
        = f.select :card_company, Creditcard.card_companies.keys, {}, {class: 'form-group__input'}
        %ul.signup__card--list
          %li.icon--visa
            = image_tag("visa.svg", id:"icon--visa")
          %li.icon--master
            = image_tag("master-card.svg", id:"icon--master")
          %li.icon--saison
            = image_tag("saison-card.svg", id:"icon--saison")
          %li.icon--jcb
            = image_tag("jcb.svg", id:"icon--jcb")
          %li.icon--americanexpress
            = image_tag("american_express.svg", id:"icon--americanexpress")
          %li.icon--diners
            = image_tag("dinersclub.svg", id:"icon--diners")
          %li.icon--discover
            = image_tag("discover.svg", id:"icon--discover")
      .form-group
        = f.label :有効期限
        %span.form-group__require 必須
        %br
        = f.select :card_month, options_for_select(["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]), {}, {class: "form-group__input--half"}
        = f.label :, class: "form-group__card--year-and-month"
        -# = f.select :card_year, options_for_select(["20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30"]), {}, {class: "form-group__input--half"}
        = f.select :card_year, options_for_select((2020..2030)), {}, {class: "form-group__input--half"}
        = f.label :, class: "form-group__card--year-and-month"
      .form-group
        = f.label :セキュリティコード, class: "label"
        %span.form-group__require 必須
        = f.text_field :card_pass, placeholder: "カード背面4桁もしくは3桁の番", class: "form-group__input"

      .form-group__add
        .form-group__add--question ?
        %p.form-group__text--right--blue
          カード裏面の番号とは?
      .form-group
        = f.submit "登録する", class: "btn-default btn-red", url: "creditcards_path",id:"charge-form",method: :post
  = render "/registration/registration_footer"

  f.submit "Sign up"

2.”トークン”作成:Javascript

今回はPay.jpの機能を利用するために、入力された値で一時的な”トークン”を作成し、その”トークン”をキーとしてクレジットカード情報などを登録します。
そのためにはクレジットカード情報を入力してもらった後に、その情報を元に”トークン”を作成するためには、Javascriptを活用して実装します。

今回はJqueryにて実装を行いました。
ポイントは以下の通りです。
- Payjpの公開鍵の記述を忘れずに。
- e.preventDefault()の記述を忘れずに。submitする前にトークンを作成します。
- jsにHTML情報を追加するときはバッククオーテーション``
- 入力エラーのときはprop('disabled', false)でボタンのdisabledを解除。2回以上押せるようにします。
- $("#form").get(0).submit();最後の送信はformの情報をとばす
*上記ミスで、当方はエラー地獄にはまりました。。笑

jquery
$(function() {
  Payjp.setPublicKey('公開鍵');
  $("#charge-form").on('click', function(e){
    e.preventDefault();
    let card = {
        number: $('#creditcard_card_number').val(),
        cvc:$('#creditcard_card_pass').val(),
        exp_month: $('#creditcard_card_month').val(),
        exp_year: $('#creditcard_card_year').val()
    };

    Payjp.createToken(card, function(status, response) {
      if (response.error) {
        $("#charge-form").prop('disabled', false);
        alert("カード情報が正しくありません。");
      }
      else {
        $(".number").removeAttr("name");
        $(".cvc").removeAttr("name");
        $(".exp_month").removeAttr("name");
        $(".exp_year").removeAttr("name");
        let token = response.id;
        $("#card_token").append(`<input type="hidden" name="payjpToken" value=${token}>`);
        $("#form").get(0).submit();
        alert("登録が完了しました");
      }
    });
  });
});

3.”トークン”をキーとしてPayjpに顧客情報として登録:controller

最後に”トークン”をキーとしてPayjpに顧客情報として登録するために、コントローラに記述が必要となります。
*今回はセッションを用いたユーザー情報を登録しおりますため、ごちゃごちゃしてますが、クレジットカードの登録だけであれば、クレジットカードのコントローラを作成し、対応いただけますと幸いです。
*セッションを用いた登録の詳細記述は以下URLよりご確認いただけますと幸いです。
https://qiita.com/Tatsu88/items/7447a669b788b011e96b

今回のクレジットカードに関する記述は、「def create_creditcard」に記載ございます。
”#顧客情報をPAY.JPに登録。”という記述で、"トークン”をPay.jpに飛ばしてます。

controller
class Users::RegistrationsController < Devise::RegistrationsController
  def new
    super
  end

  # POST /resource
  def create
    if params[:sns_auth] == 'true'
      pass = Devise.friendly_token
      params[:user][:password] = pass
      params[:user][:password_confirmation] = pass
    end
    params[:user][:birthday] = params[:birthday]
    @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    @address = @user.build_address
    render :new_address
  end

  def create_address
    @user = User.new(session["devise.regist_data"]["user"])
    @address = Address.new(address_params)
    unless @address.valid?
      flash.now[:alert] = @address.errors.full_messages
      render :new_address and return
    end
    @user.build_address(@address.attributes)
    session["address"] = @address.attributes
    @creditcard = @user.build_creditcard
    render :new_credit_card
  end

  def create_creditcard
    @user = User.new(session["devise.regist_data"]["user"])
    @address = Address.new(session["address"])
    Payjp.api_key = '秘密鍵'
    if params['payjpToken'].blank?
      redirect_to action: "new"
    else
      # 顧客情報をPAY.JPに登録。
      customer = Payjp::Customer.create(
        description: 'test', 
        email: @user.email,
        card: params['payjpToken'], 
      )
    end
    @creditcard = Creditcard.new(creditcard_params)
    @creditcard[:customer_id]=customer.id
    @creditcard[:card_id]=customer.default_card
    unless @creditcard.valid?
      flash.now[:alert] = @creditcard.errors.full_messages
      render :new_credit_card and return
    end
    binding.pry
    @user.build_address(@address.attributes)
    @user.build_creditcard(@creditcard.attributes)
    if @user.save
      sign_in(:user, @user)
    else
      render :new
    end
  end

  protected
  def address_params
    params.require(:address).permit(:address,:postal_code, :prefecture,:city,:apartment)
  end

  def creditcard_params
    params.require(:creditcard).permit(:card_number,:card_year, :card_month, :card_pass,:card_company)
  end

4.登録情報の確認:view+controller

最後にPay.jpに登録した情報を取得できるようにしましょう。
まずは、情報をpay.jpから取得するための記述をコントローラに行います。

view
=render "home/header_login"
.mypage_a  
  %main.mypage-contents.clearfix
    .main-content
      .payment
        .payment-content
          .payment-content__title
            %h1.payment-header 支払い方法
          .payment-content__main
            .payment-content__creditcards
              %h2.payment-title クレジットカード一覧
            .payment-content__creditcards__list
              %figure
                = image_tag "#{@card_src}",alt: @card_brand, id: "card_image"
              .payment-content__creditcards__list__number
                = "**** **** **** " + @creditcard_information.last4
              .payment-content__creditcards__list__number
                - exp_month = @creditcard_information.exp_month.to_s
                - exp_year = @creditcard_information.exp_year.to_s.slice(2,3)
                = exp_month + " / " + exp_year
    .side-content
      %nav.mypage-nav
        %ul.mypage-nav-list
          %li
            =link_to "#", class: "mypage-nav-list-item" do
              マイページ
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-item" do
              お知らせ
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-item" do
              やることリスト
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-item" do
              いいね!一覧
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-item" do
              出品する
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-item" do
              出品した商品 - 出品中
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-item" do
              出品した商品 - 取引中
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-item" do
              出品した商品 - 売却済み
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-item" do
              購入した商品 - 取引中
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-item" do
              購入した商品 - 過去の取引
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-item" do
              ニュース一覧
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-item" do
              評価一覧
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-item" do
              ガイド
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-item" do
              お問い合わせ
              %i.icon-arrow-right
        %h3.mypage-nav-head-merpay メルペイ
        %ul.mypage-nav-list-merpay
          %li
            =link_to "#", class: "mypage-nav-list-merpay-item" do
              売上・振込申請
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-merpay-item" do
              ポイント
              %i.icon-arrow-right
        %h3.mypage-nav-head-setting 設定
        %ul.mypage-nav-list-setting
          %li
            =link_to "#", class: "mypage-nav-list-setting-item" do
              プロフィール
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-setting-item" do
              発送元・お届け先住所変更
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-setting-item" do
              支払い方法
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-setting-item" do
              メール/パスワード
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-setting-item" do
              本人情報
              %i.icon-arrow-right
          %li
            =link_to "#", class: "mypage-nav-list-setting-item" do
              電話番号の確認
              %i.icon-arrow-right
          %li
            =link_to destroy_user_session_path, method: :delete, class: "mypage-nav-list-setting-item" do
              ログアウト
              %i.icon-arrow-right
=render "home/footer"
controller
class CardsController < ApplicationController
  require "payjp"
  before_action :set_creditcard

  def show
    Payjp.api_key = "秘密鍵"
    customer = Payjp::Customer.retrieve(@creditcard.customer_id)
    @creditcard_information = customer.cards.retrieve(@creditcard.card_id)
    @card_brand = @creditcard_information.brand 
    case @card_brand
    when "Visa"
      @card_src = "visa.svg"
    when "JCB"
      @card_src = "jcb.svg"
    when "MasterCard"
      @card_src = "master-card.svg"
    when "American Express"
      @card_src = "american_express.svg"
    when "Diners Club"
      @card_src = "dinersclub.svg"
    when "Discover"
      @card_src = "discover.svg"
    end
  end

完成イメージは以下のようになっております。
此の情報はpay.jpでも同じ情報が登録できてます。
creditcard.info.png

注意点(テストする時のクレジットカード番号について)

テストを行う時のクレジットカードの番号が定められており、この番号以外で適当な番号を入れるとエラーとなります。
下記URLに詳細載ってますので、ご確認の上、対応ください。
https://pay.jp/docs/testcard

メモ

-function(e) {} のeって何?
function(e)の「e」。これはイベントハンドラ、イベントリスナとして設定したコールバック関数が受け取ることができるイベントオブジェクトです。
JavaScriptの関数は引数を指定しなくてもOKなのでイベントオブジェクトを省略してもエラーとはなりません。

-get(index)
DOMエレメントの集合からインデックスを指定して、ひとつのエレメントを参照する。
これによって、特にjQueryオブジェクトである必要のないケースで特定のDOM Elementそのものを操作することが可能。例えば$(this).get(0)は、配列オペレータである$(this)[0]と同等の意味になる。

参照

JavaScriptをしっかり勉強 vol.6 Eventオブジェクト
http://brush-clover.com/program/js-study6/

jQuery日本語リファレンス
http://semooh.jp/jquery/api/core/get/index/

payjpリファレンス
https://pay.jp/docs/api/#payjp-api

トークン作成
https://pay.jp/docs/cardtoken

カード情報非通過化対応のお願い
http://payjp-announce.hatenablog.com/entry/2017/11/10/182738

顧客を作成
https://pay.jp/docs/api/#%E9%A1%A7%E5%AE%A2%E3%82%92%E4%BD%9C%E6%88%90

【Rails5】簡単便利!PAY.JPでクレジットカードのオンライン決済機能を導入!
https://qiita.com/emincoring/items/ce29dbbd182aa3c49c6b

payjp.jsの導入方法<Rails>
https://qiita.com/tripoodle/items/57d1cf9aef74ac5c9ab6#payjp%E3%81%A8%E3%81%AF

Payjpでクレジットカード登録と削除機能を実装する(Rails)
https://qiita.com/takachan_coding/items/f7e70794b9ca03b559dd

以上となります。最後までご覧いただき、ありがとうございました!
今後も学習した事項に関してQiitaに投稿していきますので、よろしくお願いします!
記述に何か誤りなどございましたら、お手数ですが、ご連絡いただけますと幸いです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした