[Rails][完全版]Payjpを使ったECサイト作成

#Payjpとは
シンプルなAPIでクレジットカード決済機能を導入出来る、クレジット決済代行サービスです。
ApplePayに対応していたり、croｎを使わず定期課金を組み込むことができたりと便利です！

#実装手順
1.[Payjp](https://pay.jp/)に登録する。

2.登録後、画面左側にある、「API」をクリックします。テスト用、本番用の鍵が表示されますので本番用のキーを使用します。(テスト環境等ではテスト用を使いましょう)

3.Gemfileに以下を記述する。

```Gemfile.
gem 'payjp'
```

忘れず実行する。

```
$ bundle install
```

4.Pay.jpの公式のjavascriptを読み込ませられるようする

```application.html.haml
%script{src: "https://js.pay.jp/", type: "text/javascript"}
```

5.cardsテーブルを作成する

```20××××××××××.rb
class CreateCards < ActiveRecord::Migration[5.2]
  def change
    create_table :cards do |t|
      t.references :user ,foreign_key: true, null: false  # ログインユーザー
      t.string :customer_id, null: false                  # 顧客ID情報(pay.jpから返ってくるデータ)
      t.string :card_id, null: false                      # カードID情報(pay.jpから返ってくるデータ)
      t.timestamps
    end
  end
end
```

忘れず実行する。

```
$ rails db:migrate
```

6.クレジットカード情報の追加と削除のコントローラを作成する

```
$ rails g controller cards
```
コントローラー内は以下を記述する。

```app/controllers/cards_controller.rb

class CardController < ApplicationController

  before_action :get_user_params, only: [:edit, :confirmation, :show]
  before_action :get_payjp_info, only: [:new_create, :create, :delete, :show]

  def edit
  end

  def create
    if params['payjp-token'].blank?
      redirect_to action: "edit", id: current_user.id
    else
      customer = Payjp::Customer.create(
      email: current_user.email,
      card: params['payjp-token'],
      metadata: {user_id: current_user.id}
      )
      @card = Card.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card)
      if @card.save
        redirect_to action: "show"
      else
        redirect_to action: "edit", id: current_user.id
      end
    end
  end

  def delete
    card = current_user.cards.first
    if card.present?
      customer = Payjp::Customer.retrieve(card.customer_id)
      customer.delete
      card.delete
    end
      redirect_to action: "confirmation", id: current_user.id
  end

  def show
    card = current_user.cards.first
    if card.present?
      customer = Payjp::Customer.retrieve(card.customer_id)
      @default_card_information = customer.cards.retrieve(card.card_id)
    else
      redirect_to action: "confirmation", id: current_user.id
    end
  end

  def confirmation
    card = current_user.cards
    redirect_to action: "show" if card.exists?
  end

  private

  def get_payjp_info
    if Rails.env == 'development'
      Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"]
    else
      Payjp.api_key = Rails.application.credentials.payjp[:PAYJP_PRIVATE_KEY]
    end
  end
end

```

7.クレジット情報の、確認と削除、追加のフォームを作成する

####confirmation
クレジットカードを追加するか選択する画面

```confirmation.html.haml

.main__content
    %section.chapter__container
      %h2.chapter__head 支払い方法
      .payment__main
        .payment__list
          .payment__list__content
            %h3.sub__head クレジットカード一覧
        .add__card
          .payment__list__content
            = link_to edit_card_path(current_user), class: "red__btn", data: {"turbolinks" => false} do
              %i
              = icon('fas', 'credit-card', class: "card__icon")
              クレジットカードを追加する
        .not__regist
          %span 支払い方法について
```

####edit
クレジットカードの新規登録画面

```edit.html.haml

  .main__content
    %section.chapter__container
      %h2.chapter__head クレジットカード情報入力
      = form_tag('/card', method: :post, action: "create", id: 'charge-form',  name: "inputForm", class: "chapter__container__inner") do
        .form__main__container
          .form__content
            %label.payment__card カード番号
            %span.require__form 必須
            = text_field_tag "number", "", class: "number input__deafult", placeholder: "半角数字のみ" ,maxlength: "16", type: "text", id: "card_number"
            .card__list
              .card__list__picture
          .form__content.top__margin
            %label.payment__card 有効期限
            %span.require__form 必須
            .select__box.top__margin
              .select__box__half
                %select.select__default#exp_month{name: "exp_month", type: "text"}
                  %option{value: ""} --
                  %option{value: "1"}01
                  %option{value: "2"}02
                  %option{value: "3"}03
                  %option{value: "4"}04
                  %option{value: "5"}05
                  %option{value: "6"}06
                  %option{value: "7"}07
                  %option{value: "8"}08
                  %option{value: "9"}09
                  %option{value: "10"}10
                  %option{value: "11"}11
                  %option{value: "12"}12
                %i
                  = icon('fas', 'chevron-down')
                %span 月
              .select__box__half
                .icon
                %select.select__default#exp_year{name: "exp_year", type: "text"}
                  %option{value: ""} --
                  %option{value: "2019"}19
                  %option{value: "2020"}20
                  %option{value: "2021"}21
                  %option{value: "2022"}22
                  %option{value: "2023"}23
                  %option{value: "2024"}24
                  %option{value: "2025"}25
                  %option{value: "2026"}26
                  %option{value: "2027"}27
                  %option{value: "2028"}28
                  %option{value: "2029"}29
                %i
                  = icon('fas', 'chevron-down')
                %span  年
          .form__content.top__margin
            %label セキュリティーコード
            %span.require__form 必須
            = text_field_tag "cvc", "", class: "cvc", placeholder: "カード背面4桁もしくは3桁の番号", maxlength: "4", id: "cvc"
            .single__code
              .single__code__text
              %span.form__question ? 
              %span カードの裏面の番号とは？
          #card_token
          = submit_tag "追加する", id: "token_submit", class: "top__margin"

```

####show
DBのCard情報を、payjpに送りcustomer情報を取り出す

```show.html.haml

 .main__content
    %section.chapter__container
      %h2.chapter__head 支払い方法
      .payment__main
        .payment__list
          .payment__list__content
            %h3.sub__head クレジットカード一覧
        .card__payment__list
          %form.card__payment__content
          .card__list
            .card__list__picture  
          .card__number 
            = "**** **** **** " + @default_card_information.last4
          .expire__date 
            - exp_month = @default_card_information.exp_month.to_s
            - exp_year = @default_card_information.exp_year.to_s.slice(2,3)
            = exp_month + " / " + exp_year
          = form_tag(delete_credit_card_index_path, method: :post, id: 'charge-form',  name: "inputForm") do
            %input{ type: "hidden", name: "card_id", value: "" }
            %button.delete__button 削除する
        .how__not__regist
          %span 支払い方法について
```

8.payjpにデータを送るため、入力されたクレジット情報を参考にし、トークンを作成する

```payjp.js

document.addEventListener(
  "DOMContentLoaded", e => {
    if (document.getElementById("token_submit") != null) { //token_submitというidがnullの場合、下記コードを実行しない
      Payjp.setPublicKey("ここに公開鍵を書く"); 
      let btn = document.getElementById("token_submit"); //IDがtoken_submitの場合に取得されます
      btn.addEventListener("click", e => { //ボタンが押されたときに作動します
        e.preventDefault(); //ボタンを一旦無効化します
        let card = {
          number: document.getElementById("card_number").value,
          cvc: document.getElementById("cvc").value,
          exp_month: document.getElementById("exp_month").value,
          exp_year: document.getElementById("exp_year").value
        }; //入力されたデータを取得します。
        Payjp.createToken(card, (status, response) => {
          if (status === 200) { //成功した場合
            $("#card_number").removeAttr("name");
            $("#cvc").removeAttr("name");
            $("#exp_month").removeAttr("name");
            $("#exp_year").removeAttr("name"); //データを自サーバにpostしないように削除
            $("#card_token").append(
              $('<input type="hidden" name="payjp-token">').val(response.id)
            ); //取得したトークンを送信できる状態にします
            document.inputForm.submit();
            alert("登録が完了しました"); //確認用
          } else {
            alert("カード情報が正しくありません。"); //確認用
          }
        });
      });
    }
  },
  false
);
```

9.最後にルーティングを設定する

```routes.rb

  resources :_card, only: [:create, :show, :edit] do
    collection do
      post 'delete', to: 'card#delete'
      post 'show'
    end
    member do
      get 'confirmation'
    end
  end 
```