【Rails / 外部API】Payjpを使った購入機能を実装する

こんばんは！
某フリマサイト開発でpayjpでAPIを使ったクレジットカード購入機能を実装したので備忘録

## 完成形

![demo](https://gyazo.com/badd4c094e907d27fdd411eb3f78be4f/raw)


![demo]
(https://gyazo.com/46bfce5c462972409f215f84bbddbaba/raw)

## 参考

>Rails で Payjp を使って決済システムを導入する
https://qiita.com/hirotakasasaki/items/794c920016ac7c33da74
Pay.jpを使用してクレジットカード登録削除機能の実装をしてみた
https://qiita.com/wrtenniss/items/75dc631778506f8bce16
【Rails】payjpを使用した決済機能を実装する①〜クレジットカードの登録編〜
https://qiita.com/dice9494/items/4aa04da1056d1f15919e
Railsで単発決済システム(Pay.jp)を実装
https://qiita.com/nobu0717/items/52b744350ef24047e1c1

payjpAPIの使い方は上記の記事を確認頂ければ丁寧に書いてくれてますので割愛

## 躓いたところ

#### 環境変数どこに書くの？
秘密鍵や公開鍵をコードに直張りはセキュリティの都合上よろしくないので、
`Payjp.api_key = ENV['PAYJP_PRIVATE_KEY']`
こんなふうにコード書いて.bash_profileへ記述します

```terminal:ターミナル
$ vim ~/.bash_profile
```

```terminal:.bash_profile
//まず「i」押下して入力モードにしましょう
export PAYJP_ACCESS_KEY='sk_test_*************'
export PAYJP_PUBLIC_KEY='pk_test_*************'
//escキー=> : => w => qの順で.bash_profileから抜けます
```

```terminal:ターミナル
//環境変数が設定できているか確認
$ printenv
```
`exportコマンド`で定義した環境変数を確認する時は`printenvコマンド`使いましょう。`環境変数名=値`と出力されればokです

#### これどこに書くの？

```ruby:payjpアクション
    Payjp.api_key = ENV['PAYJP_PRIVATE_KEY']
    charge = Payjp::Charge.create(
    amount: 300,#決済金額の事。変数も使えるので後で解説
    card: params['payjp-token'],
    currency: 'jpy'
    )
```

上記コードはフォームに入力したカード情報をトークン化して支払い処理を行う為の処理。APIの定型文です。決済するアクションは`item(productとか)コントローラ`が振る舞いとしてよろしいでしょう

```ruby:items_controller.rb
class ItemsController < ApplicationController
  def index
    #省略
  end

  def new
    #省略
  end

  def create
    #省略
  end

  def pay
    Payjp.api_key = ENV['PAYJP_PRIVATE_KEY']
    charge = Payjp::Charge.create(
    amount: 300,
    card: params['payjp-token'],
    currency: 'jpy'
    )
  end
end
```

#### 決済される金額どうするの？

今のコードでは常に300円の決済しかできません。@itemに格納されているpriceを取り出しましょう

```ruby:items_controller.rb
class ItemsController < ApplicationController

  def pay
    Payjp.api_key = ENV['PAYJP_PRIVATE_KEY']
    charge = Payjp::Charge.create(
    amount: @item.price,
    card: params['payjp-token'],
    currency: 'jpy'
    )
  end
end
```
これでok!
にはなりません。私はここでピュアRubyの理解が全くできていない事が分かりました。Railsは私のようなRuby知識しょぼ人間でも動いてくれていたんですね...

#### price for nill:NilClass

![スクリーンショット 2019-09-05 1.37.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428016/be70d49d-f96d-d296-f46a-ffc9fb37033d.png)

`amount: @item.price`と書いただけでは怒られます
priceって値は@itemの中にnameやtextなどと一緒に格納されています。（語彙力ないので適切な表現でない事をお詫びします）
Railsではハッシュであるparamsを使ってビューからキーと一緒にパラメーターとして送られてきます。`params[:キー名]`という形で使用できます

```ruby:items_controller.rb
class ItemsController < ApplicationController

  def pay
    Payjp.api_key = ENV['PAYJP_PRIVATE_KEY']
    charge = Payjp::Charge.create(
    amount: @item.price,
    card: params['payjp-token'],
    currency: 'jpy'
    )
  end

  private

  def item_params
    params.require(:item).permit(
      :name,
      :text,
      :price,
      #この辺の他コードは関係ない部分なので省略してます
    ).merge(user_id: current_user.id)
  end
end
```

`item_params`の中にpriceがいますね。この値をpayアクションに渡してあげればよさそうです

#### @item = Item.find(params[:id])
itemモデルからfindメソッドでid（キー）を取り出して@itemに格納します。その@itemにはgifでいう所のクロノ・トリガーの画像や商品名、商品説明、価格などが格納されています。その中から価格(price)を取り出してあげれば、その価格をpayjpで決済できますね

#### controller完成コード

```ruby:items_controller.rb
class ItemsController < ApplicationController

  def pay
    @item = Item.find(params[:id])
    Payjp.api_key = ENV['PAYJP_PRIVATE_KEY']
    charge = Payjp::Charge.create(
    amount: @item.price,
    card: params['payjp-token'],
    currency: 'jpy'
    )
  end

  private

  def item_params
    params.require(:item).permit(
      :name,
      :text,
      :price,
      #この辺の他コードは関係ない部分なので省略してます
    ).merge(user_id: current_user.id)
  end
end
```

これでpayjpを使った決済機能の基幹部分が完成しました！
![スクリーンショット 2019-09-05 2.11.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428016/0bc8149c-2164-7bd2-5cf2-78cbc04f69d1.png)
payjp側でもちゃんと決済されています

#### 画面遷移ってどうするの？

```haml:show.html.haml
= form_tag(action: :pay, method: :post) do
  %script.payjp-button{:src => "https://checkout.pay.jp", :type => "text/javascript" ,"data-text" => "購入する" ,"data-key" => "pk_test_**************"}
```
この二行を表示したいビューに書けばボタンと入力画面（モーダルウィンドウ）が勝手に出現します。ダミーカード番号を入力すれば決済できるのですが、間違っても自分のカード番号は入れないで下さい

`カード番号: 4242424242424242`※visaカードのテストカード番号
`有効期限: 12/20`※未来の日付なら何でもok
`CVC: 123`※３桁なら何でもok
`名前: YUI ARAGAKI`可愛い娘の名前なら何でもok

ビューはオリジナルでも問題ありません。ただ私はまだそこまで実装できていないのでこの記事には書けません。参考記事にあるのでそちらでご確認お願いします

#### 複数画面の遷移はどうするの？

ここまでのコードではpayアクションは`form_tag(action: :pay, method: :post) do ~`を埋め込んだビューでしか動きません。私の場合は商品詳細画面〜購入確認画面〜購入完了画面〜ホーム画面と遷移させたかったので
`商品詳細画面=>show`
`購入確認画面=>purchase`※ここにform_tagを埋め込んだ
`購入完了画面=>done`
`ルートパスへ戻る`
としました

```ruby:items_controller.rb
class ItemsController < ApplicationController
  before_action :set_item, only: [:show, :purchase, :pay]
  #これは繰り返しの記述を避けたいのでprivate以下にset_itemアクションとして記述しbefore_actionで呼び出しています

  def index
    @items = Item.order('id DESC').limit(4)
  end

  def show
  end


  def new
    @item = Item.new
    @item.item_images.build
    @item.build_shipping
  end

  def create
    @item = Item.new(item_params)
    if @item.save
      redirect_to action: :index
    end
  end

  def purchase
  end

  def pay
    Payjp.api_key = ENV['PAYJP_PRIVATE_KEY']
    charge = Payjp::Charge.create(
    amount: @item.price,
    card: params['payjp-token'],
    currency: 'jpy'
    )
    redirect_to action: :done
  end

  def done
  end

  private
  def set_item
    @item = Item.find(params[:id])
  end

  def item_params
    params.require(:item).permit(
      :name,
      :text,
      :price,
    ).merge(user_id: current_user.id)
  end
end
```

これで後はルーティングを設定すれば3画面遷移ができます

```ruby:routes.rb
#payjpに必要な箇所だけ抜粋
  resources :items do
    collection do
      get  'purchase/:id'=>  'items#purchase', as: 'purchase'
      post 'pay/:id'=>   'items#pay', as: 'pay'#httpメソッドはpostなので注意
      get  'done'=>      'items#done', as: 'done'
    end
  end
```

## まとめ
Rubyの理解不足でハマっていた部分を除けば難しい事はなく実装できると思います。ただテストコードの書き方が分からずなので、誰かご教授頂きたいです。次は購入したら出品ステータスが取引中や取引完了になって購入できなくなる部分の実装とクレジットカード登録の実装が待っていますので、出力の低い脳みそフルフル回転させていきます！

#### 終わり