Pay.jpを用いた購入機能の実装

##概要
Pay.jpを用いた購入機能の実装を備忘録としてまとめます。
修正点ありましたらご指摘お願いいたします。
Transaction（取り引き）テーブルおよびProduct（商品）テーブルをもとに作っていきます。

##前提
* Pay.jpのアカウント作成済み
* Pay.jpにてクレジットカード登録機能は実装済み
* ビューはHamlで記載
* deviseにてログイン済み

##手順

1. Transactionテーブルを作成（購入済みの場合SOLD OUTを表示させるため）
2. アソシエーションの設定
3. Transactionコントローラーを作成
4. 環境変数の設定
5. ルーティングの設定
6. マークアップ：購入内容確認画面
7. マークアップ：購入完了画面
8. マークアップ：購入済みの場合SOLD OUTを表示
9. 購入データの確認

##Transactionテーブルを作成
今回は購入済みの場合SOLD OUTを表示させるためにTransactionテーブルを作成します。

```
$ rails g model Transaction
```

``` db/migrate/20200000000000_create_transactions.rb
class CreateTransactions < ActiveRecord::Migration[5.2]
  def change
    create_table :transactions do |t|
      t.references :product, null: false, foreign_key: true
      t.references :user, null: false, foreign_key: true
      t.timestamps
    end
  end
end

```

マイグレートを実行

```
$ rails db:migrate
```

##アソシエーションの設定

``` transaction.rb
class Transaction < ApplicationRecord
  belongs_to :user, optional: true
  belongs_to :product, optional: true
end
```

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


```
$ rails g controller transactions
```

``` transactions_controller.rb
class TransactionsController < ApplicationController
  require 'payjp'
  before_action :set_card, only: [:pay_index, :pay]
  before_action :set_product

  def pay_index
    @top_image = @product.images.first
    @card = @set_card.first
    if @card.blank?
      redirect_to controller: "cards", 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

  def pay
    @card = @set_card.first
    Payjp.api_key = ENV['PAYJP_PRIVATE_KEY']
    Payjp::Charge.create(
    :amount => @product.price,
    :customer => @card.customer_id,
    :currency => 'jpy',
  )
  redirect_to action: 'done', product_id: @product
  end

  def done
    @top_image = @product.images.first
    Transaction.create(product_id: @product.id, user_id: current_user.id)
  end

  private

  def set_card
    @set_card = Card.where(user_id: current_user.id)
  end

  def set_product
    @product = Product.find(params[:product_id])
  end

end
```

##環境変数の設定

コントローラ内のENV["PAYJP_PRIVATE_KEY"]は環境変数でテスト秘密鍵を設定し読み込む。
今回はdotenvとgonを利用する。

dotenv：Railsの環境変数管理
gon：JSにてRailsで定義した環境変数を使用
参考：https://qiita.com/3443/items/44202ff6504210592570#comments

※gonはCard登録機能にて使用したため今回は関係ありません

##ルーティングの設定

``` routes.rb
Rails.application.routes.draw do
  devise_for :users
  root "products#index"
  resources :users, only: [:edit, :update]
  resources :products
  resources :cards, only: [:new, :show, :destroy] do
    collection do
      post 'pay_show', to: 'cards#pay_show'
      post 'pay', to: 'cards#pay'
    end
  end

  ##今回の該当箇所
  resources :transactions, only: [:index] do
    collection do
      get 'pay_index', to: 'transactions#pay_index'
      post 'pay', to: 'transactions#pay'
      get 'done', to: 'transactions#done'
    end
  end

end
```

##マークアップ：購入内容確認画面

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

``` pay_index.html.haml
.transaction-pay
  .transaction-pay__content
    %h2.transaction-pay__title 購入内容の確認
    .transaction-pay__item
      .transaction-pay__item-box
        = image_tag @top_image.image.url, alt:"商品画像", class: "transaction-pay__item-image"
      .transaction-pay__item-detail
        %p.transaction-pay__item-detail--name
          = @product.name
        .transaction-pay__item-detail-price
          .transaction-pay__item-detail-price--text
            ￥
          .transaction-pay__item-detail-price--text
            = @product.price
          .transaction-pay__item-detail-price--shipping （税込）送料込み
    .transaction-pay__table
      .transaction-pay__table-inner
        .transaction-pay__table-form
          .transaction-pay__table-content
            .transaction-pay__table-pay
              %p.transaction-pay__table-pay--title 支払い金額
            .transaction-pay__table-price
              %p.transaction-pay__table-price--title
                ￥
              %p.transaction-pay__table-price--title
                = @product.price
          .transaction-pay__table-way
            %h3 支払い方法
            .transaction-pay__table-register
              - if @default_card_information.blank?
                %i.fas.fa-plus-circle
                %span.icon-register
                = link_to "登録してください", new_card_path
              - else
                = "**** **** **** " + @default_card_information.last4
                - 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
          .transaction-pay__table-buy
            = form_tag(action: :pay, method: :post, product_id: @product) do
              %button.transaction-pay__table-buy-button 購入する
```

##マークアップ：購入完了画面

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

``` done.html.haml
.transaction-done
  .transaction-done__content
    .transaction-done__text
      購入が完了しました！
    .transaction-done__image
      = image_tag @top_image.image.url, alt:"商品画像", class: "transaction-done__image--img"
    .transaction-done__title
      = @product.name
    .transaction-done__price
      .transaction-done__price--text
        = @product.price
      .transaction-done__price--info
        (送料込み)
```

##マークアップ：購入済みの場合SOLD OUTを表示

[![Image from Gyazo](https://i.gyazo.com/0822d8faa8daa76781b753390e6bd212.jpg)](https://gyazo.com/0822d8faa8daa76781b753390e6bd212)

``` show.html.haml
.product-show
  .product-show__main
    .product-show__content
      .product-show__top-content
        .product-show__item-box
          .product-show__item-box--name
            = @product.name
          .product-show__item-box__body
            .product-show__item-box__body--top-img
              = image_tag @top_image.image.url, alt:"トップ画像", class: "product-show__item-top-img"
            .product-show__item-box__body--list
              - @images.each do |image|
                .product-show__item-box__body--sub-img
                  = image_tag image.image.url, alt:"サブ画像", class: "product-show__item-sub-img"
          .product-show__item-box--price
            %span 
            = "#{@product.price}円"
            .product-show__item-box--price-detail
              %span.product-show__item-box--price-detail-text
                (税込)
              %span.product-show__item-box--price-detail-text
                = @product.delivery_charge
          .product-show__item-box--item-detail
            = @product.name
          -# 商品出品者であれば表示させない
          - if user_signed_in? && (current_user.id == @product.user_id)
          - else
            .product-show__transaction
              .product-show__transaction-box
                -# 商品購入済みであればSOLD OUT
                - if @product_id.present?
                  .product-transaction-btn
                    SOLD OUT
                - else
                  = link_to "購入画面に進む", pay_index_transactions_path(product_id: @product), class: "product-transaction-btn"
```

##購入データの確認
購入ができていれば下記のURLにて履歴が確認できます。
https://pay.jp/d/charges

##以上です