Pay.jpを使用してクレジットカード登録削除機能の実装をしてみた

# はじめに
軽く自己紹介から
TECH::EXPRETというプログラミングスクールに通っております。
53期で活動して、そろそろ終わりに近づいてきましたので、実装した部分くらいは
アウトプットするかーってことで、やります。
テストはやってないので、それ目的の方はごめんなさい。

今回この記事を書く目的は2つほどあります。
#### 1.チームメンバーに学んだ技術の共有の為
なんか作ったらわかりやすいかなーと思ってます
#### 3.アウトプットを通して、今回学んだ内容をしっかり定着させる為
ぶっちゃけ忘れているところが多々あります笑　思い出すためにもしっかり書きます笑

# 開発環境
 ruby 2.5.1
 rails 5.2.1
 mysql
 

# ゴール
以下のように登録をrails5でできるようにしましょう

- 実際のビュー
[![Image from Gyazo](https://i.gyazo.com/4b745de7897f0f533d6e730e3033f264.gif)](https://gyazo.com/4b745de7897f0f533d6e730e3033f264)
- Sequel Pro の CreditCardの画面
<img width="864" alt="スクリーンショット 2019-07-07 15 47 21" src="https://user-images.githubusercontent.com/42095725/60765050-e360f480-a0ce-11e9-9d84-a836c97ece27.png">
- payjpの画面
<img width="1280" alt="スクリーンショット 2019-07-07 15 44 37" src="https://user-images.githubusercontent.com/42095725/60765061-025f8680-a0cf-11e9-886e-bf46bccd6b56.png">

# 前提準備
deviseでのユーザログイン機能の実装

# 処理の概要

# 1) pay.jpのgemをインストール
作業目的：Pay.jpを使用できるようにする為

```Ruby:Gemfile
gem 'payjp'
```

# 2) Pay.jpの公式のjavascriptを読み込ませられるようする
作業目的：Pay.jpの公式のjavascriptを読み込ませられるようする為


```Ruby:app/views/layouts/application.html.haml 

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

# 3)　クレジットカードテーブルを作成する
作業目的： クレジットカード情報を保存するテーブルを作成

|Colun|Type|Options|意味|
|-----|---|-------|----|
|user_id|references|null: false, foreign_key: true|ログインユーザ
|costomer_id|integer|null: false|顧客ID情報(pay.jpから返ってくるデータ)
|card_id|integer|null: false|カードID情報(pay.jpから返ってくるデータ)

costomer_id, card_idは、カード情報(16桁のやつ),有効期限年月,セキュリティーコードを渡すとpay.jpから返ってくるデータのこと


```Ruby:db/migrate/***************_create_credit_cards.rb
class CreateCreditCards < ActiveRecord::Migration[5.2]
  def change
    create_table :credit_cards do |t|
      t.references :user ,foreign_key: true, null: false
      t.string :customer_id, null: false
      t.string :card_id, null: false
      t.timestamps
    end
  end
end

```

なんで、DBにカード情報とか保存しないの？っと疑問に持たれる人もいると思いますので、ここで解説
以下のURLを参照していただければわかるのですが、
Pay.jp側の方で、
セキュリティーの観点から
クレジット情報は、勝手にDBに保存するな。俺が管理する。お前らにはIDやるから我慢しろ。っと言われているからなんですよねー
めんどいですが、クレジット情報や購入情報は毎回Pay.jpに問い合わせる必要があります。

http://payjp-announce.hatenablog.com/entry/2017/11/10/182738


# 4)　クレジットカード情報の追加と削除のコントローラを作っていきます
## 1つのget、3つのpostの実装
### GET
- edit<br>
クレジットカードの新規登録画面
- confirmation<br>
 クレジットカードを追加するか選択する画面

### POST
- create<br>
payjpとCardのデータベース作成を実施します。
- delete<br>
payjpとCardのデータベースを削除する。
- show<br>
DBのCreditCard情報を、payjpに送りcustomer情報を取り出すために実装

```Ruby:app/controllers/credit_cards_controller.rb 
class CreditCardController < 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 = CreditCard.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.credit_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.credit_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.credit_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

```
# 5)　ビュー
作業目的：クレジット情報の、確認と削除、追加のファームを作成する必要があった為。

- confirmation

```haml:app/views/credit_card/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_credit_card_path(current_user), class: "red__btn", data: {"turbolinks" => false} do
              %i
              = icon('fas', 'credit-card', class: "card__icon")
              クレジットカードを追加する
        .not__regist
          %span 支払い方法について
```
[![Image from Gyazo](https://i.gyazo.com/be2f121af3e2055e88efe751d649668c.png)](https://gyazo.com/be2f121af3e2055e88efe751d649668c)

- edit

```haml:app/views/credit_card/edit.html.haml
   .main__content
    %section.chapter__container
      %h2.chapter__head クレジットカード情報入力
      = form_tag('/credit_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"

```

[![Image from Gyazo](https://i.gyazo.com/80716eee8b0b1b414faf67d64029a239.png)](https://gyazo.com/80716eee8b0b1b414faf67d64029a239)

- show

```haml:app/views/credit_card/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 支払い方法について
```
[![Image from Gyazo](https://i.gyazo.com/a15b38a0d96323d3a24c8b188259fd62.png)](https://gyazo.com/a15b38a0d96323d3a24c8b188259fd62)

# 6) トークン作成用のJS
作業目的：payjpにデータを送るために実装。入力されたクレジット情報を参考にし、トークンを作成する

```javascript:app/assets/javascripts/payjp.js
document.addEventListener(
  "DOMContentLoaded", e => {
    if (document.getElementById("token_submit") != null) { //token_submitというidがnullの場合、下記コードを実行しない
      Payjp.setPublicKey("pk_test_61fe8dd669a59526fd8542f5"); //ここに公開鍵を直書き
      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
);
```
# 7) ルーティング
作業目的：最後に、全てのpathを通すためのルーティングを設計します。

```ruby:config/routes.rb
  resources :credit_card, only: [:create, :show, :edit] do
    collection do
      post 'delete', to: 'credit_card#delete'
      post 'show'
    end
    member do
      get 'confirmation'
    end
  end 
```


以上で完成です！
とりあえず実装のフローを書いてみました。

# 感想
商品の購入するためには、クレジット情報が必要になります。
以上の順番で、実装すれば、できるはず！！！

気力があれば、購入のフローも書きたいと思いますー。