Payjp クレジットカード登録・削除

#はじめに

今回　`gem payjp`　を使用しクレジットカードの登録・削除機能を実装したので
備忘録として書きます


#バージョン情報
ruby  '2.5.1'
rails '5.2.4'
mysql2 '5.6系'

#導入済みのgem
`gem haml-rails`
`gem devise`

#PAY.JPとは
PAY.JPの重要な機能であるカード情報のトークン化、支払い処理、定期課金を簡単に実装ができる凄い子なのです
※クレジットカード情報を保存する事は禁止されているため
payjpがトークン化(暗号化)してこちらのmysqlに返して保存できるようにしてくれます

今回下記流れを頭に入れておくと実装しやすいかも！

❶フォームにカード情報を入力し送信
❷入力されたカード情報は直接PAY.JPサーバーへ送られ、PAY.JPは一意のトークンを返す
❸サーバーサイドでトークンを受け取り、トークンを使った各種処理を行う(支払い処理、顧客へカードを紐付ける)

#1.PAY.JPへ登録を行う
https://pay.jp/

#APIの確認
今回はテスト秘密鍵とテスト公開鍵を使用します
<img width="997" alt="api.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558117/d27d0107-5d49-aec7-5355-27594fa6f286.png">

#payjpのgemを導入
```ruby:Gemfile
gem  payjp
```

bundle install をお忘れなく

```
$ bundle install
```

#payjp.のjsを読み込めるようにする
`%script{src: "https://js.pay.jp/", type: "text/javascript"}`
を下記のように入れてください

```ruby:app/view/layouts/application.html.haml
!!!
%html
  %head
    %meta{content: "text/html; charset=UTF-8", "http-equiv": "Content-Type"}/
    %title test
    %script{src: "https://js.pay.jp/", type: "text/javascript"}
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_link_tag    'application', media: 'all'
    = javascript_include_tag 'application'
  %body
    = yield
```

#テーブルを作成

```ruby:
rails g model Card user_id:integer customer_id:string card_id:string
```
テーブルは下記になります

```ruby:db/migrate/20190400000000_create_cards.rb
class CreateCards < ActiveRecord::Migration[5.2]
  def change
    create_table :cards do |t|
      t.integer :user_id, null: false
      t.string :customer_id, null: false
      t.string :card_id, null: false
      t.timestamps
    end
  end
end
```
マイグレーションをお忘れなく

```
$ rails db:migrate
```

#環境変数を設定
テスト秘密鍵とテスト公開鍵を入れます

```
$ vim ~/.bash_profile
```
`i`　で入力できるようにして
自身のテスト秘密鍵とテスト公開鍵を入れてください

```
export PAYJP_PRIVATE_KEY='sk_test_＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊'
export PAYJP_KEY='pk_test_＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊'
```
入力できたら　`esc`　⇨　`：wp`　⇨　`ENTER`　で保存

最後に環境変数を記述したら必ず下記のコマンドで設定を有効する

```
$ source ~/.bash_profile
```
設定できているかは

```
$printenv
```

にて確認

```
PAYJP_PRIVATE_KEY='sk_test_＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊'
PAYJP_KEY='pk_test_＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊'
```
が入っていればOK！

#環境変数の使用方法

```
ENV['PAYJP_PRIVATE_KEY']
ENV['PAYJP_KEY']
```
上記の表示で環境変数として使用ができます！

#routesの設定
```ruby:config/routes
  resources :cards,only:[:index,:new,:show]do
    collection do
      #payjpでトークン化を行う
      post 'pay', to: 'cards#pay'
      #カード削除
      post 'delete', to: 'cards#delete'
      #カード情報入力
      post 'show', to: 'cards#show'
    end
  end
```

#Payjpのjavascripts を設定
app/asstes/javascriptsに`pay.js`のファイルを作成してください

```ruby:app/asstes/javascripts/pay.js
document.addEventListener(
  "DOMContentLoaded", e => {
    if (document.getElementById("token_submit") != null) { //token_submitというidがnullの場合、下記コードを実行しない
      Payjp.setPublicKey("pk_test_4842a1069943b3e72c287e0b"); //ここに公開鍵を直書き
      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
);

```
#コントローラーを作成

```
$ rails g controller cards new show
```

```ruby:app/controllers/cards_controller.rb
class CardsController < ApplicationController

  require "payjp"

  def new
    #current_user.idでログインしてるユーザーのみ登録ができるようにしてます
    card = Card.where(user_id: current_user.id)
    #カード登録がまだならshowページへ飛ぶ
    redirect_to action: "show" if card.exists?
  end

  def pay #payjpとCardのデータベース作成
    Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"]
    if params['payjp-token'].blank?
       # paramsの中にjsで作った'payjpTokenが存在するか確かめる
      redirect_to action: "new"
    else
      customer = Payjp::Customer.create(
        description: '登録テスト', #なくてもOK
        email: current_user.email, #なくてもOK
        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"
        flash[:notice] = 'クレジットカードの登録が完了しました'
      else
        redirect_to action: "pay"
        flash[:alert] = 'クレジットカード登録に失敗しました'
      end
    end
  end

  def delete #PayjpとCardデータベースを削除
    card = Card.where(user_id: current_user.id).first
    if card.blank?
    else
      Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"]
      customer = Payjp::Customer.retrieve(card.customer_id)
      customer.delete
      card.delete
    end
      redirect_to action: "new"
  end

  def show #Cardのデータpayjpに送り情報を取り出す
    card = Card.where(user_id: current_user.id).first
    if card.blank?
      redirect_to action: "new" 
    else
      Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"]
      customer = Payjp::Customer.retrieve(card.customer_id)
      @default_card_information = customer.cards.retrieve(card.card_id)
    end
  end
end
```

#カードの登録画面を作成

```ruby:app/view/cards/new.html.haml
       .form-wapper
          = form_tag(pay_cards_path, method: :post, id: 'charge-form',  name: "inputForm") do
            .signup-content_text
              .form-group.form-group_text
                %label.form-group_label カード番号
                %span.essential 必須
              = text_field_tag "number", "", class: "number", placeholder: "半角数字のみ" ,maxlength: "16", type: "text", id: "card_number"
              .form-group.form-group_text.card_label
                %label.form-group_label 有効期限
                %span.essential 必須
            .form-group
              .form-flex#form-flex
                .birth-group
                  %select#exp_month{name: "exp_month", type: "text", class: "select-default"}
                    %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.fa.fa-chevron-down
                %span.birth-group__month 月
                .birth-group
                  %select#exp_year{name: "exp_year", type: "text", class: "select-default2"}
                    %option{value: ""} --
                    %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
                    %option{value: "2030"}30
                  %i.fa.fa-chevron-down.down
                %span.birth-group__year 年
            .form-group.form-group_text.card_code
              %label.form-group_label セキュリティコード
              %span.essential3 必須
            = text_field_tag "cvc", "", class: "cvc", placeholder: "カード背面3~4桁の番号", maxlength: "4", id: "cvc"
            #card_token
            .form-group
            = submit_tag "追加する", id: "token_submit"
```

#カード削除画面を作成


```ruby:app/view/cards/show.html.haml
      .card-group
        .card-group__title
          %label クレジットカード一覧
          %br
          = "**** **** **** " + @default_card_information.last4
          %br
          - 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_cards_path, method: :post, id: 'charge-form',  name: "inputForm") do
            %input{ type: "hidden", name: "card_id", value: "" }
            %button.btn_delate  削除する
```

#カード登録ができる確認
http://localhost:3000/card/new にアクセスして登録できるか確認してください
※このとき[テストカード](https://pay.jp/docs/testcard)を使用して登録をしてください！

<img width="705" alt="登録.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558117/e6fe75b3-ea6a-99f6-f194-8b6679aa4fdb.png">


<img width="705" alt="35d4e6735af7a0f631f97daacb7d5783.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/558117/09c6f28c-3534-840c-0339-b840808d02f5.png">

ができていれば成功です！！！


#もし下記エラーが起きたら

400 Bad Request＝不正なパラメーターなどのリクエストエラー
もしくは
Invalid email address provided＝(無効なメールアドレスが提供されました)
と出た場合は
a@gmail.com /  b@yahoo.co.jp で試してみたら解決します
アドレス　a@a などのアドレスが無効と返されているみたいです