[Rails]Pay.jpを利用したクレジット決済機能実装　④　~クレジットカード購入(決済)機能~

## はじめに
例によって、某プログラミングスクールの最終課題である、フリマアプリのクローンサイト作成において、購入機能実装時にPay.jpを利用したので、健忘録としてここに記す。
今回は「クレジットカード購入(決済)機能」の実装に取り掛かります。


## バージョン情報
ruby '2.5.1'
Rails '5.2.4.2'

## 実装の流れ
1. [実装の準備・APIの導入](https://qiita.com/Shunsuke_i_21/items/19c8326282510ae30eab)
2. [モデルの作成・クレジットカード登録](https://qiita.com/Shunsuke_i_21/items/cd638e6aebba126683f1)
3. [クレジットカード詳細表示・削除](https://qiita.com/Shunsuke_i_21/items/75c13102c5db3246a6c6)
4. <font color="DeepPink">クレジットカード購入(決済)機能</font>　←　今回の実装内容　いよいよラスト！

## 前提条件として
* 記載はhaml記法で
 * [私の参考にしたrailsにhamlを導入するやり方はこちら、、](https://qiita.com/MitsuguSueyoshi/items/f1b0b047553fde49bbc1)
* ユーザー登録機能として、gemの``devise``を利用しています。 なので``current_user``などの``devise``のメソッドが随所に出てきます
 * 参考記事: [【Rails】deviseを導入してみる](https://qiita.com/Hal_mai/items/350c400e8763ce0487a3) 

## 今回の実装機能のダイジェスト
### 商品の詳細表示から購入画面への以降はこんな感じ！
(商品詳細画面なんかは今回は実装しないよ)
[![Image from Gyazo](https://i.gyazo.com/ee563a10ece070703544fad4f86e598a.gif)](https://gyazo.com/ee563a10ece070703544fad4f86e598a)
流れとしては
1. 商品詳細画面からのパスで購入確認画面に以降(今回は``buyアクション``で定義)
2. 事前にクレジットカードが登録されていたらクレジットカード詳細画面が表示される。
3. 購入完了すると、Pay.jp上で決済が行われ、購入完了画面に移行(今回は``payアクション``で定義)

[![Image from Gyazo](https://i.gyazo.com/823ba8692e0d134bb7c950bbb9c5d016.gif)](https://gyazo.com/823ba8692e0d134bb7c950bbb9c5d016)
4. もしクレジットカードを事前登録してなければ、専用のチェックボックスの入力フォームが出てきます。
[![Image from Gyazo](https://i.gyazo.com/ed57221c5c13365aca486a21e5ff884f.gif)](https://gyazo.com/ed57221c5c13365aca486a21e5ff884f)
5. 必要な入力情報を入力すると、同じように購入完了画面に移行します。
## <font color="red">  *注意！*</font>
**クレジットカード登録のテストの際には以下のサイトのテスト用のクレジット番号を必ずお使いください！**
[テストカード](https://pay.jp/docs/testcard)
**有効期限は現在以降の年と月であればなんでもOK**
**CVC(セキュリティコード)はなんでもOK！**
**<font color="red">間違ってもご自身のカード番号は使わないように</font>**

<font color="blue">**前置きはこれくらいで、いよいよ実装！**</font>

## **いよいよ実装！** まずはコントローラーの記載
まずはコントローラーの記載を行います。
今回は(DBで商品の購入管理を行う、Purchasesテーブルを作成していることもあり)、Purchasesコントローラーを作成してそちらにアクションを記載してます。
購入確認処理を``buyアクション``で、購入処理を``payアクション``で定義してます。

```ruby:controllers/purchases_controller.rb
  require "payjp"

  def buy
    # 購入する商品を引っ張ってきます。
    @product = Product.find(params[:product_id])
    # 商品ごとに複数枚写真を登録できるので、一応全部持ってきておきます。
    @images = @product.images.all

    # まずはログインしているか確認
    if user_signed_in?
      @user = current_user
      # クレジットカードが登録されているか確認
      if @user.credit_card.present?
        # 前前前回credentials.yml.encに記載したAPI秘密鍵を呼び出します。
        Payjp.api_key = Rails.application.credentials.dig(:payjp, :PAYJP_SECRET_KEY)
        # ログインユーザーのクレジットカード情報を引っ張ってきます。
        @card = CreditCard.find_by(user_id: current_user.id)
        # (以下は以前のcredit_cardsコントローラーのshowアクションとほぼ一緒です)
        # ログインユーザーのクレジットカード情報からPay.jpに登録されているカスタマー情報を引き出す
        customer = Payjp::Customer.retrieve(@card.customer_id)
        # カスタマー情報からカードの情報を引き出す
        @customer_card = customer.cards.retrieve(@card.card_id)

        ##カードのアイコン表示のための定義づけ
        @card_brand = @customer_card.brand
        case @card_brand
        when "Visa"
          # 例えば、Pay.jpからとってきたカード情報の、ブランドが"Visa"だった場合は返り値として
          # (画像として登録されている)Visa.pngを返す
          @card_src = "visa.gif"
        when "JCB"
          @card_src = "jcb.gif"
        when "MasterCard"
          @card_src = "master.png"
        when "American Express"
          @card_src = "amex.gif"
        when "Diners Club"
          @card_src = "diners.gif"
        when "Discover"
          @card_src = "discover.gif"
        end
        # viewの記述を簡略化
        ## 有効期限'月'を定義
        @exp_month = @customer_card.exp_month.to_s
        ## 有効期限'年'を定義
        @exp_year = @customer_card.exp_year.to_s.slice(2,3)
      else
      end
    else
      # ログインしていなければ、商品の購入ができずに、ログイン画面に移動します。
      redirect_to user_session_path, alert: "ログインしてください"
    end
  end

  def pay
    #ちなみに見やすさ考慮し、before_actionなどのリファクタリングなどはあえてしてません。
    @product = Product.find(params[:product_id])
    @images = @product.images.all

    # 購入テーブル登録ずみ商品は２重で購入されないようにする
    # (２重で決済されることを防ぐ)
    if @product.purchase.present?
      redirect_to product_path(@product.id), alert: "売り切れています。"
    else
      # 同時に2人が同時に購入し、二重で購入処理がされることを防ぐための記述
      @product.with_lock do
        if current_user.credit_card.present?
          # ログインユーザーがクレジットカード登録済みの場合の処理
          # ログインユーザーのクレジットカード情報を引っ張ってきます。
          @card = CreditCard.find_by(user_id: current_user.id)
          # 前前前回credentials.yml.encに記載したAPI秘密鍵を呼び出します。
          Payjp.api_key = Rails.application.credentials.dig(:payjp, :PAYJP_SECRET_KEY)
          #登録したカードでの、クレジットカード決済処理
          charge = Payjp::Charge.create(
          # 商品(product)の値段を引っ張ってきて決済金額(amount)に入れる
          amount: @product.price,
          customer: Payjp::Customer.retrieve(@card.customer_id),
          currency: 'jpy'
          )
        else
          # ログインユーザーがクレジットカード登録されていない場合(Checkout機能による処理を行います)
          # APIの「Checkout」ライブラリによる決済処理の記述
          Payjp::Charge.create(
          amount: @product.price,
          card: params['payjp-token'], # フォームを送信すると作成・送信されてくるトークン
          currency: 'jpy'
          )
        end
      #購入テーブルに登録処理(今回の実装では言及しませんが一応、、、)
      @purchase = Purchase.create(buyer_id: current_user.id, product_id: params[:product_id])
      end
    end
  end
```
(例によって、フラッシュメッセージをしれっと入れてます)
参考にした記事: [【Rails】flashメッセージを使用して簡易メッセージを表示させる詳しい方法と解説](https://qiita.com/dice9494/items/2a0e92aba58a516e42e9)

補足として、``buyアクション``は、クレジットカード情報を引っ張ってくる関係で、前回の``credit_cardsコントローラー``の``showアクション``と似た記述になってます。

``payアクション``では今回購入のための決済処理が行われる記述がなされています。

```ruby:登録済みのクレジットカードでの購入決済処理の記述
require "payjp"

Payjp.api_key = Rails.application.credentials.dig(:payjp, :PAYJP_SECRET_KEY)

# credit_cardsテーブルからuser_idに紐付けてクレジットカード情報を取ってきます。
@card = CreditCard.find_by(user_id: current_user.id)
# 決済処理の記述
charge = Payjp::Charge.create(
          amount: @product.price,
          customer: Payjp::Customer.retrieve(@card.customer_id),
          currency: 'jpy'
          )
```

クレジットカード登録がされていないユーザーには、その場でカード情報を打ち込ませます。
その際に公式から便利なCheckoutと言うライブラリが提供されておりますので、コチラを利用します。
これにより、簡単にカード情報打ち込みフォームをモーダルで表示させます。

[Checkout](https://pay.jp/docs/checkout)
> チェックアウトは< script >タグを1行で、 デザインされた決済フォーム、カード情報のバリデーション、カード情報のトークン化を行うフォームを生成するライブラリです。



Checkoutライブラリにより打ち込まれたカード情報から決済を行う際には、コントローラーに以下の記述を行います。

```ruby:クレジットカード未登録者が打ち込んだフォームを決済処理する記述
   Payjp::Charge.create(
   amount: @product.price,
   card: params['payjp-token'], # フォームを送信すると作成・送信されてくるトークン
   currency: 'jpy'
   )
```

後ほど、ビューにもCheckout機能呼び出しの記述を行います。

### ついでにルーティングも設定しておく
```ruby:config/routes.rb
Rails.application.routes.draw do

  resources :products do
    # 他の記述は省略
    resource :purchases do
      member do
        get  "buy"
        post "pay"
      end
    end
  end
end
```

## 続いて、ビューの記載
ちなみに部分テンプレートを使用してます(ファイル名を参照！)。

### ``buyビュー``の記載
```ruby:views/purchases/buy/_buy.html.haml
.buy
  .main_contents
    .main_details
      # 商品情報を部分テンプレートで共通化してます。今回は実装内容と逸れるので省略
      = render "purchases/shared/purchase_product_info.html.haml", product: @product, images: @images
    .purchase_registration
      .purchase_registration_main
        .payment
          支払い金額	
        .amount
          = "#{@product.price}" + "円"
      # クレジットカードを登録済みの場合の表示
      - if @user.credit_card.present?
        .purchase_function
          .credit_card_info_title
            クレジットカード情報
            # クレジットカード情報を表示させます。
            .credit_card_info
              .credit_card_info__brand
                = image_tag "cards/#{@card_src}", class: "credit_card_info__brand__img", alt: @card_brand
              .credit_card_info__numbers
                .number
                  = "**** **** **** " + @customer_card.last4
                .expiration_date
                  .expiration_date__title
                    有効期限
                  .expiration_date__info
                    = @exp_month + " / " + @exp_year
          .purchase_function__btn
            = link_to "登録クレジットカードで購入する", pay_product_purchases_path(product_id: @product.id), method: :post, class: "btn"
      - else 
        .purchase_none_btn
          # Checkoutのフォームを開くための記述
          = form_tag(action: :pay, method: :post) do
            %script.payjp-button{src: "https://checkout.pay.jp", type: "text/javascript" ,"data-text": "購入する" ,"data-key": "pk_test_(ご自身のAPI公開鍵)",}
```
ポイントはCheckoutのフォームを開くための記述です。

```ruby:Checkoutによる画面遷移の記述
   = form_tag(action: :pay, method: :post) do
     %script.payjp-button{src: "https://checkout.pay.jp", type: "text/javascript" ,"data-text": "購入する" ,"data-key": "pk_test_(ご自身のAPI公開鍵)",}
```
この二行を表示したいビューに書けばボタンと入力画面（モーダルウィンドウ）が勝手に出現します。

表示例としてはこんな感じ
#### (クレジットカード登録時)
[![Image from Gyazo](https://i.gyazo.com/8366e0f0a72aa13be86f30c582eaf353.png)](https://gyazo.com/8366e0f0a72aa13be86f30c582eaf353)

#### (クレジットカード未登録時)
[![Image from Gyazo](https://i.gyazo.com/10ed54f14e1234a3d187c0d565d5013e.png)](https://gyazo.com/10ed54f14e1234a3d187c0d565d5013e)
購入ボタンを押すとモーダルが出現します。
ちなみにこの「購入する」ボタンはクラスの指定ができなかったので、chromeの検証ツールでid名を取得し、SCSSの指定を行なってます。

### ``payビュー``の記載

```ruby:views/purchases/pay/_pay.html.haml
.pay
  .main_contents
    .main_details
      .main_details__message
        購入が完了しました。
    # 商品の表示を部分テンプレートで共通化
    # 今回の実装にはあまり関係ないので省略
    = render "purchases/shared/purchase_product_info.html.haml", product: @product, images: @images
    %ul.purchased__links
      %li.purchased__links__TopPage
        = link_to "トップページに戻る", root_path, class: "purchased__links__TopPage__btn"
      %li.purchased__links__index
        = link_to "購入商品一覧", "#", class: "purchased__links__index__btn"
```
、、、特に言うことはないですね。

表示例としてこんな感じ
[![Image from Gyazo](https://i.gyazo.com/8c6c79d873eb57a2da3f80fcb98eaced.png)](https://gyazo.com/8c6c79d873eb57a2da3f80fcb98eaced)
flashメッセージで購入完了のメッセージを出して、リダイレクトでトップページに遷移させれば、なくてもOKですね。

###<font color="blue">クレジットカード購入(決済)機能　実装完了！！</font>

## 最後に
以上でクレジットカード購入(決済)機能の実装は完成です！
何度も言いますが、**登録テスト時には必ず、[テストカード](https://pay.jp/docs/testcard)の番号での登録**をお願いしますね！

ワタクシごとですが、私は購入機能の実装より、購入後に商品と購入ユーザーをpurchasesテーブルに登録する処理にてこずりましたwww

そして以上で、今回実装したPay.jpの実装は以上になります。
皆様の学習や実装の参考にして頂けましたら幸いで御座います。

## 参考リンク
* 公式サイト
    * [公式リファレンス](https://pay.jp/docs/payjs)
    * [公式ガイド](https://pay.jp/docs/api/?ruby#payjp-api)
* 参考にさせて頂いたQiita記事
    * [【Ruby on Rails で簡単！】PAY.JPを利用したクレジットカード決済の導入](https://qiita.com/mylevel/items/7462ca28260581509d19)
    * [【PAY.JP】支払い機能の実装](https://qiita.com/tanaka-t/items/99b722ab3be471d43183)
    * [railsでクレジット決済機能をpay.jpで実装する](https://qiita.com/trn031/items/d7684ed91178adac7932)
    * [RailsでPayjpを使った購入機能を実装する](https://qiita.com/suzy1031/items/7964829086eb929471a6)
    * [Pay.jpをRailsで！【登録・削除・購入】](https://qiita.com/Masataka_Sugi/items/57b68eb19de0c014ef12)
    * [Payjpを使って、商品購入機能を実装する【Rails】](https://qiita.com/helix04/items/99b3766137e5d52a04ca)
* その他の機能実装時の参考サイト
    * [Railsでhamlを導入する！](https://qiita.com/MitsuguSueyoshi/items/f1b0b047553fde49bbc1)
    * [【Rails】deviseを導入してみる](https://qiita.com/Hal_mai/items/350c400e8763ce0487a3) 
    * [【Rails】flashメッセージを使用して簡易メッセージを表示させる詳しい方法と解説](https://qiita.com/dice9494/items/2a0e92aba58a516e42e9)