payjpを使用したカード機能の搭載

# カード機能を搭載しました。

>**使用gem**
[devise](https://github.com/heartcombo/devise)
[PAY.JP for Ruby](https://github.com/payjp/payjp-ruby)
~~[Gon gem](https://github.com/gazay/gon)~~←最終的に使用しませんでした。

## まずはカード登録機能
ですが、カード番号などをECサイト運営社が持つことは法律で禁じられているらしいです。
ですのでDBにそれらの情報は登録できません。
代わりにpayjpのIDを登録することになります。

>**カード登録の流れ**
・エンドユーザーがカード番号等を入力しsubmitをポチ
・payjpにデータが送信されカードトークンが帰ってくる。
・帰ってきたカードトークンからカスタマー(持ち主)をcreate
・createされたカスタマー情報からカスタマーIDとカードIDをDBへ登録

私の場合、アソシエーションは

```ruby
user has_one :card
card belongs_to :user
```
cardsテーブルは

```ruby:migrate/***create_cards.rb
      t.references  :user,        foreign_key: true#,index:{unique: true}
      t.string      :customer_id, null: false
      t.string      :card_id,     null: false
```

では早速、Gemfileに以下を追記して bundle install しましょう。

```ruby:Gemfile
gem 'payjp'
# gem 'gon'←最終的に使用しませんでした。
```
**ここでgonとは**
rubyの変数をJQuery(javascript)に渡せるgemです。

>**間違っているかもしれませんが**
JQuery(javascript)や他のコードに、直接キー(公開キーを除く)を書き込むことは、キーの漏洩リスクが上がると考えています。
テストキーならいいかもしれませんが、リリース後などの本番ではテストキーは使えません。
credentials.ymlに書く方法もありますが、個人のキーはあくまで個人のものなので、個人での管理が望ましいかと考えています。

~~installが終了したらgonを使えるようにします~~
~~application.html.hamlの~~
~~javascript_include_tagの上に追記~~

```ruby:app/views/layouts/application.html.haml
# = include_gon ←最終的に使用しませんでした。
= javascript_include_tag 'application'
```
```ruby:app/controllers/card_controller.rb
def new
    # gon.api_key = ENV["PAYJP_PUBLIC_KEY"]
    @api_key = ENV["PAYJP_PUBLIC_KEY"]
end
```
カード関連のjsは全てのページで読み込む必要性が無いため
今回は
card/new.html.hamlに直接以下を書きました。

```ruby:app/views/card/new.html.haml
:javascript
  #let payjp = Payjp(gon.api_key);
  let payjp = Payjp("#{@api_key}");
```
~~これで環境変数をJQueryで読み込んでいるのでキーを晒さずに済みそうです。~~
共同開発をしている場合、必要な人は各人payjpに登録して環境変数に書き込みましょう。

newアクションが上記でできているので登録フォームを作ります。

```javascript:app/views/card/new.html.haml
= form_with url: card_index_path,local:true,id:"cardNew__form" do |f| //card_index_pathはcardcontrollerのcreate action へのpath
    %input#cardNew__form--inputHidden{type:"hidden",name:"payjp-token"}
    #cardNew__form--input
    #cardNew__form--errMessage
    = f.submit "登録する",id:"cardNew__form--submit"

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

:javascript //ここからJQueryです。

  //let payjp = Payjp(gon.api_key); //先程のgonです。
  let payjp = Payjp("#{@api_key}"); //viewファイル内なので、この記述でJQにルビーの変数を渡せます。
  let elements = payjp.elements();
  let cardElement = elements.create('card');

  cardElement.mount('#cardNew__form--input');
//ここまででPAYJPが予め用意した入力欄ができます。カードのワンポイントがかわゆいので私はこのまま使用します^^

  $(".cardNew").on("click","#cardNew__form--submit",
    function(e){
      e.preventDefault();
      payjp.createToken(cardElement).then(function(response){ //ここでpayjpにカード情報を送ってカードトークンをいただきます。
        if(response.error){
          $('#cardNew__form--errMessage').text(response.error.message);
        }else{
          $("#cardNew__form--inputHidden").attr("value",response.id); //response.idがカードトークンです。ここでinput name="payjp-token"に値をわたします。
          $("#cardNew__form").submit();
        }
      });
    }
  );
```
カード番号:424242424242
↑有効期限は未来、CVCは何でも良いみたいです。
[テストカード](https://pay.jp/docs/testcard)

これでsubmitを押せば、createアクションへトークンが送られます。
card_index_pathはcreateアクションへのパスです。
うまく見えない人はCSSで調整してください。

```ruby:app/controllers/card_controller.rb
  def create
    if params["payjp-token"].blank?
      render :new
    else
      Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
      customer = Payjp::Customer.create( #ここで先程のカードトークンと紐付いたカスタマーをクリエイトします。
        card: params["payjp-token"]
      )
      @card = Card.new(
        user_id: current_user.id,
        customer_id: customer.id, #先程のカスタマーのidです。
        card_id: customer.default_card #先程のカスタマーに紐付いたカードのidです。
      )
      if @card.save
        redirect_to new_card_path
      else
        render :new
      end
    end
  end

```
ここまででDBへカードの登録ができるかと思います。

以下は試していないので動くかどうか分かりませんが、
もっと簡単に済ませたい人は[こちら](https://pay.jp/docs/cardtoken)から

```haml
=form_with url: card_index_path,local:true,id:"new_form" do |f|
    %script{src:"https://checkout.pay.jp/",class:"payjp-button","data-key":"#{@api_key}"}
```
をコピペしてdata-keyにご自身のパブリックキー
@api_keyをnew controllerで作ってください。パブリックなので直接書き込んでも構いません。
urlは先程と同じ。idは任意。classは試してないのでわかりません。
先程のnew controllerはgon仕様になっているので、そこを

```ruby
@api_key = ENV["PAYJP_PUBLIC_KEY"]
```
にすればOKだと思います。
payjp-tokenパラメーターの値としてトークンを送信するみたいです。
これだけで、質の高いカード入力画面が出来上がるようです。
すごいですね。

参考一次ソース
[PAY.JP Announcement](http://payjp-announce.hatenablog.com/entry/2017/11/10/182738)
[payjp.js v2 リファレンス](https://pay.jp/docs/payjs)
[payjp.js v2 ガイド](https://pay.jp/docs/payjs-guidance)
[顧客を作成する](https://pay.jp/docs/customer)

参考ブログ
[【Rails】PAYJPを用いた決済機能の実装手順を簡単にまとめてみた](https://techtechmedia.com/payjp-rails/#i-3)
[Payjpでクレジットカード登録と削除機能を実装する(Rails)](https://qiita.com/takachan_coding/items/f7e70794b9ca03b559dd)

## 次にカードでのお支払い
流れとしては
>商品詳細ページで購入をポチ
購入確認ページで購入を確定をポチ
カード決済される
paymentsテーブルに決済IDと何を誰が買ったのかをcreate
itemsテーブルのstock欄を減らしてupdate

アソシエーション

```ruby
item has_many :payments
payment belogs_to :item
```

paymentsテーブル

```ruby:***create_payments.rb
      t.string      :charge_id, null: false
      t.references  :user,      foreign_key: true
      t.references  :buyer,     foreign_key: {to_table: :users}
      t.integer     :item_id,   null: false
      t.integer     :quantity,  null: false
      t.integer     :payment,   null: false
```
```haml:views/payments/new.html.haml

= form_with url:item_payments_path,local:true,id:"new_form" do |f|
  = f.number_field :quantity
  = f.submit "購入する" #なんか怖いので金額などのデータは飛ばさないようにしてます。購入数だけです。
```
```ruby:payments_controller.rb
  def create
    item = Item.find(params[:item_id])                              #ルーティングはネストしてitemのidを含むようにしてください。
    item.stock -= params[:quantity].to_i                            #itemのstockから購入数を引きます。後はupdateするだけ
    payment = Payment.new(payment_params)
    Payjp.api_key = ENV["PAYJP_SECRET_KEY"] 
    charge = Payjp::Charge.create( #ここで決済します。
      amount: item.price.to_i * params[:quantity].to_i,             #決済額
      customer: Card.find_by(user_id: current_user.id).customer_id, #お客様
      currency: 'jpy'                                               #日本円
    )
    payment.charge_id = charge.id                                   #先程生成したpaymentインスタンスに決済IDを入れています。別に必要はありません。お好みで^^
    if payment.save && item.update(stock: item.stock)
      redirect_to root_path
    else
      render "new"
    end
  end

  private
  def payment_params
    params.permit(
      :item_id,
      :quantity,
    ).merge(user_id: current_user.id)
  end
```

こんな感じです。
わかりやすくするため、色々と省略しています。
ルーティングのネストがわからない人は

```ruby:config/routes.rb
  resources :items do
    resources :payments,only: [:new,:create]
  end
```
こんな感じです。

たぶんこれで動くと思います。

参考一次ソース
[payjp.js APIガイド](https://pay.jp/docs/charge)
[PAY.JP chargeオブジェクト](https://pay.jp/docs/api/#charge%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88)
↑省略しましたが、カードの下４桁やVIZAとか表示させたい人へ（retrieveとか使ってください。）


参考ブログ
[Rails で Payjp を使って決済システムを導入する
](https://qiita.com/hirotakasasaki/items/794c920016ac7c33da74)


## 登録したカードを削除する。
自分の場合
ルーティング

```ruby:config/routes.rb
resources :card,only: [:new,:create,:destroy]
```
```
card_index POST    /card(.:format)        card#create
new_card   GET     /card/new(.:format)    card#new
card       DELETE  /card/:id(.:format)    card#destroy
```
なので、上記のviewを変更しました。
変わったのは上部のifからelseと
~~JQueryのgonの定義判定のみです。~~
@api_keyの判定のみです。

```view/card/new.html.haml
  - if current_user.card
    #cardNew__form
      #cardNew__form--type= @card.brand.upcase
      #cardNew__form--num= "**** **** **** #{@card.last4}"
      #cardNew__form--name= @card.name
      =link_to "削除","/card/#{current_user.card.id}"
  - else
    = form_with url: card_index_path,local:true,id:"cardNew__form" do |f|
      %input#cardNew__form--inputHidden{type:"hidden",name:"payjp-token"}
      #cardNew__form--input
      #cardNew__form--errMessage
      = f.submit "登録する",id:"cardNew__form--submit"



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

:javascript
  if(typeof gon != 'undefined'){
  if("#{@api_key}"){

    const style = {
      base: {
        backgroundColor:'#ffffff',
        '::placeholder': {
          color: '#bbbbbb',
        }
      },
      invalid: {
        color: 'red',
      }
    }

    let payjp = Payjp("#{@api_key}");
    let elements = payjp.elements();
    let cardElement = elements.create('card', {style: style});

    cardElement.mount('#cardNew__form--input');

    $(".cardNew").on("click","#cardNew__form--submit",
      function(e){
        e.preventDefault();
        payjp.createToken(cardElement).then(function(response){
          if(response.error){
            $('#cardNew__form--errMessage').text(response.error.message);
          }else{
            $("#cardNew__form--inputHidden").attr("value",response.id);
            $("#cardNew__form").submit();
          }
        });
      }
    );
  }
```
```ruby:card_controller.rb
  def new
    if current_user.card
      Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
      customer = Payjp::Customer.retrieve(current_user.card.customer_id)
      @card = customer.cards.retrieve(current_user.card.card_id)
    else
      gon.api_key = ENV["PAYJP_PUBLIC_KEY"]
    end
  end
```