クレジット決済サービス(pay.jp)の実装

# はじめに
pay.jpを用いたクレジットサービスの実装手順を記録に残したいと思います。
なお、購入金額を記録に残すMVCの一連の流れは、すでに実装できているものとする。

## 1.クレジット決済サービス実装の流れ
2018年6月から、APIが提供する安全な処理を利用して、クライアントサイド側でカード情報を決済サービス側に送りトークン化し、そのトークン情報をサーバーサイドに送信して決済処理を行うことが義務付けられている。
つまり、アプリケーションのサーバーサイドにおいて、クレジットカード情報を保持しないようにすることが義務付けられた。

- トークンとは
セキュリティーを担保するために用いられる、一度だけ使用可能なパスワードのこと。
今回使用するトークンは、クレジットカード情報を暗号化したもの。
同じカード情報で複数回決済をしたとしても、毎回異なるトークンが発行される。

- クレジット決済の流れ
①クライアントサイド側で、PAY.JPのAPIが提供する安全な処理を用いてクレジットカード情報のトークン化を行う。
②そのトークンをサーバーサイドに送信し、クレジットカード決済が行われる。
※このトークンは一度だけ有効となるものなので、同じトークンで別の決済を行うことはできない。したがって、仮に漏えいしたとしても問題ない。また、PAY.JPのシステム以外は、トークンからクレジットカード情報を復号することもできない。

## 2.フォームにクレジットカード情報の入力欄を作成する

    # 記載例
    ＜省略＞
    <div class='form-wrap'>
        <%= f.label :number,  "カード番号" %>
        <%= f.text_field :number, class:"number", placeholder:"カード番号(半角英数字)", maxlength:"16" %>
      </div>
      <div class='form-wrap'>
        <%= f.label :cvc ,"ＣＶＣ" %>
        <%= f.text_field :cvc, class:"cvc", placeholder:"カード背面4桁もしくは3桁の番号", maxlength:"4" %>
      </div>
      <div class='form-wrap'>
        <p>有効期限</p>
        <div class='input-expiration-date-wrap'>
          <%= f.text_field :exp_month, class:"exp_month", placeholder:"例)3" %>
          <p>月</p>
          <%= f.text_field :exp_year, class:"exp_year", placeholder:"例)24" %>
          <p>年</p>
        </div>
      </div>
    ＜省略＞

## 3.JavaScriptの処理を無効にする機能を取り除く
turbolinksという機能がデフォルトで備わっている。
こちらは簡単にAjax機能などを実装する際に使用されるもの。
しかし、手作業でJavaScriptを記述してフォーム送信処理などを実装する場合、
このturbolinksがその処理を無効にしてしまうことがある。

以下の2つのファイルを編集し、turbolinksを使用しないようにする。

    ① app/views/layouts/application.html.erb内
    <%# 省略 %>
        <%= stylesheet_link_tag 'application', media: 'all'  %>　⇦ turbolinksを使用しないため、不必要な記述を削除
        <%= javascript_pack_tag 'application' %>　⇦ turbolinksを使用しないため、不必要な記述を削除
    <%# 省略 %>

    ② app/javascript/packs/application.js内
    // 省略
    require("@rails/ujs").start()
    // require("turbolinks").start() // コメントアウトする
    require("@rails/activestorage").start()
    require("channels")
    // 省略

## 4.決済機能のクライアントサイド実装（クレジットカード情報のトークン化）：クライアントとPAY.JPのやりとり
- 大まかな流れ
    1. クライアントサイドでPAY.JPのAPIを使用するために必要なJavaScriptを読み込む
    2. 実際にトークン化を行うJavaScriptファイルを作成する
    3. PAY.JPのテスト公開鍵を取得し設定する
    4. トークン化の処理を行う

### 4-1. クライアントサイドでPAY.JPのAPIを使用するために必要なJavaScriptを読み込む
クライアントサイドからPAY.JPのAPIを使用するためには、payjp.jsというライブラリを読み込む。

JavaScriptにおけるライブラリとは、RubyにおけるGemと同じような、便利な処理を提供してくれるまとまりのこと。

また、<strong>JavaScriptのライブラリを使用するためには、HTMLのhead要素に以下のように記述</strong>する。

    <script type="text/javascript" src="ライブラリのURL"></script>

application.html.erbのhead要素内に、以下の記述をする。
このように記述することで、ブラウザでアプリケーションを開いたときに、payjp.jsが読み込まれる。

    <%# 省略 %>
        <%= csrf_meta_tags %>
        <%= csp_meta_tag %>
        <script type="text/javascript" src="https://js.pay.jp/v1/"></script> ⇦ この行の追記
        <%= stylesheet_link_tag 'application', media: 'all' %>
        <%= javascript_pack_tag 'application' %>
    <%# 省略 %>

    ※payjp.jsとは
    クライアントサイドからPAY.JPのAPIを使用するために読み込むJavaScriptのライブラリ。
    この中に、安全にクレジットカード情報をトークン化するための処理などが含まれている。

### 4-2. トークン化を行うファイルを作成する
今回は、card.jsというファイルを作成し、そのファイルにトークン化の処理を記述していく。
また、この段階でフォーム送信時にイベントが発火することを確かめる。

    # イメージ図
    app
     |
     javascript
         |
         card.js

    # app/javascript/packs/application.js内(jsファイル読み込みのため)
    // 省略
    require("@rails/ujs").start()
    // require("turbolinks").start()
    require("@rails/activestorage").start()
    require("channels")
    require("../card")　⇦追記(card.jsの追記)
    // 省略

## 5.PAY.JPのテスト公開鍵を取得し設定する
クレジットカード情報をPAY.JP側に送付し、トークン化するためには、公開鍵と呼ばれる鍵が必要。

- 公開鍵とは
クライアントサイド側からPAY.JPへクレジットカード情報を送り、トークン化するために必要となる鍵。
公開鍵が無いと、どのユーザーからのクレジットカード情報かわからず、PAY.JPはトークン化を行ってくれない。
なお、公開鍵はこの後のサーバーサイドの実装で登場する<strong>秘密鍵とセットで保有</strong>する。

公開鍵は、自動で生成されている。
公開鍵の情報は、PAY.JPにログイン後、ページの左側の「API」をクリックすると確認できる。

    const pay = () => {
      Payjp.setPublicKey("pk_test_******************"); ⇦　PAY.JPテスト公開鍵を設定
      const submit = document.getElementById("button");
      submit.addEventListener("click", (e) => {
        e.preventDefault();
        console.log("フォーム送信時にイベント発火")
      });
    };
    
    window.addEventListener("load", pay);

なお、鍵情報は公開してはいけない。
このコードの状態でGitHubにPushするなどしてしまうと、コードに含まれる鍵情報が公開されてしまい、不正請求などの被害にあうリスクがある。
この鍵の情報は環境変数に設定するようにする（後述）。

## 6. フォームに入力されたカードの情報を、JavaScriptで取得してトークン化する作業を行う
### 6-1. フォーム情報を取得する

    # card.js内
    const pay = () => {
      Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
      const submit = document.getElementById("button");
      submit.addEventListener("click", (e) => {
        e.preventDefault();
        
        <フォーム要素を取り出す記述>
        const formResult = document.getElementById("charge-form");
        const formData = new FormData(formResult);
    
        const card = {
          number: formData.get("order[number]"),
          cvc: formData.get("order[cvc]"),
          exp_month: formData.get("order[exp_month]"),
          exp_year: `20${formData.get("order[exp_year]")}`,
        };
        (生成したFormDataオブジェクトから、クレジットカードに関する情報を取得し、変数cardに代入するオブジェクトとして定義している。"order[number]"などは、以下のように各フォームのname属性の値のこと。)
        <フォーム要素を取り出す記述>

      });
    };
    
    window.addEventListener("load", pay);

### 6-2. カード情報をトークン化する
カード情報をPAY.JP側に送りトークン化するためには、pay.jsが提供するPayjp.createToken(card, callback)というオブジェクトとメソッドを使用する。

- 第一引数のcard
PAY.JP側に送るカードの情報で、直前のステップで定義したカード情報のオブジェクトが入る。

- 第二引数のcallback
PAY.JP側からトークンが送付された後に実行する処理を記述する。
    
        # card.js内
            const pay = () => {
              Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
              const submit = document.getElementById("button");
              submit.addEventListener("click", (e) => {
                e.preventDefault();
        
            const formResult = document.getElementById("charge-form");
            const formData = new FormData(formResult);
        
            const card = {
              number: formData.get("order[number]"),
              cvc: formData.get("order[cvc]"),
              exp_month: formData.get("order[exp_month]"),
              exp_year: `20${formData.get("order[exp_year]")}`,
            };
        
            ＜追記箇所（ここから）＞
            Payjp.createToken(card, (status, response) => {
              if (status == 200) {
                const token = response.id;
                console.log(token)
              }
            });
            ＜追記箇所（ここまで）＞
          });
        };
        
        window.addEventListener("load", pay);

createTokenメソッドの第一引数には、前述で定義したクレジットカード情報が入る。
第二引数にはアロー関数を用いて、レスポンスを受け取ったあとの処理を記述した。
アロー関数の引数に指定した変数には、PAY.JP側からのレスポンスとステータスコードが含まれている。

今回、アロー関数の引数としては<strong>status</strong>と<strong>response</strong>を定義した。
statusはトークンの作成がうまくなされたかどうかを確認できる、HTTPステータスコードが入る。
responseはそのレスポンスの内容が含まれ、response.idとすることでトークンの値を取得することができる。

HTTPステータスコードが200のとき、すなわちうまく処理が完了したときだけ、トークンの値を取得するように実装している。

## 7. 決済機能のクライアントサイド実装（サーバーサイドへのトークン送付）：クライアントとサーバーサイドのやりとり
### 7-1. トークンの情報フォームに追加する
これまでのステップで、トークンを取得することができた。
しかし、このままではトークンはフォーム情報に含まれないため、サーバーサイドに送付することはできない。

card.jsを以下のように編集し、トークンの値をフォームに含める。
JavaScriptでinput要素を生成しフォームに加え、その値としてトークンをセットする。

    const pay = () => {
      Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
      const submit = document.getElementById("button");
      submit.addEventListener("click", (e) => {
        e.preventDefault();
    
        const formResult = document.getElementById("charge-form");
        const formData = new FormData(formResult);
    
        const card = {
          number: formData.get("order[number]"),
          cvc: formData.get("order[cvc]"),
          exp_month: formData.get("order[exp_month]"),
          exp_year: `20${formData.get("order[exp_year]")}`,
        };
    
        Payjp.createToken(card, (status, response) => {
          if (status == 200) {
            const token = response.id;
            ＜追記箇所（ここから）＞
            const renderDom = document.getElementById("charge-form");
            const tokenObj = `<input value=${token} name='token' type="hidden">`;
            renderDom.insertAdjacentHTML("beforeend", tokenObj);
            debugger;
            ＜追記箇所（ここまで）＞
          }
        });
      });
    };
    
    window.addEventListener("load", pay);

HTMLのinput要素にトークンの値を埋め込み、フォームに追加する。
valueは実際に送られる値、nameはその値を示すプロパティ名（params[:name]のように取得できるようになる）を示す。
そして、renderDom.insertAdjacentHTMLで、フォームの中に作成したinput要素を追加している。
23行目のdebuggerで処理が停止するようにしている。
ブラウザ上で実際に送信してみると、フォーム内にトークンの値が追加されることが確認できる。(beforeendの位置)
（確認できたら、debuggerの記述を消去）

type="hidden"でブラウザ上への表示を非表示に。

### 7-2.クレジットカードの情報を取り除く
現状では、フォームにクレジットカードの情報が残っています。このまま次のサーバーサイドへの送信処理を行ってしまうと、クレジットカードの情報がサーバーサイドに送付されてしまい、今回トークンを生成した意味がなくなってしまいます。また、クレジットカードの情報は、すでにトークン生成が完了しているため以降は不要です。

したがって、この段階でフォームにあるクレジットカード情報は削除するようにしましょう。

    const pay = () => {
      Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
      const submit = document.getElementById("button");
      submit.addEventListener("click", (e) => {
        e.preventDefault();
    
        const formResult = document.getElementById("charge-form");
        const formData = new FormData(formResult);
    
        const card = {
          number: formData.get("order[number]"),
          cvc: formData.get("order[cvc]"),
          exp_month: formData.get("order[exp_month]"),
          exp_year: `20${formData.get("order[exp_year]")}`,
        };
    
        Payjp.createToken(card, (status, response) => {
          if (status == 200) {
            const token = response.id;
            const renderDom = document.getElementById("charge-form");
            const tokenObj = `<input value=${token} name='token' type="hidden"> `;
            renderDom.insertAdjacentHTML("beforeend", tokenObj);
          }
  
    ＜追記箇所（ここから）＞  
          document.getElementById("order_number").removeAttribute("name");
          document.getElementById("order_cvc").removeAttribute("name");
          document.getElementById("order_exp_month").removeAttribute("name");
          document.getElementById("order_exp_year").removeAttribute("name");
    ＜追記箇所（ここまで）＞

        });
      });
    };
    
    window.addEventListener("load", pay);

### 7-3. サーバーサイドにフォーム情報を送る
e.preventDefault();で通常のRuby on Railsにおけるフォーム送信処理はキャンセルされています。
したがって、上記のようにJavaScript側からフォームの送信処理を行う必要があるのです。


    ・・・省略・・・
          document.getElementById("order_number").removeAttribute("name");
          document.getElementById("order_cvc").removeAttribute("name");
          document.getElementById("order_exp_month").removeAttribute("name");
          document.getElementById("order_exp_year").removeAttribute("name");
    
          ＜追記箇所（ここから）＞
          document.getElementById("charge-form").submit();
          ＜追記箇所（ここまで）＞
        });
      });
    };
    
    window.addEventListener("load", pay);


## 8.決済機能のサーバーサイド実装：サーバーサイドとPAY.JPのやりとり
これまでの実装で、サーバーサイドにトークンと金額の情報を送信することができました。
続いて、サーバーサイドで受け取った情報をもとに決済処理を行えるようにします。
つまり、クレジットカード情報を入力して購入ボタンをクリックしたら、PAY.JP側で決済が記録される。

### 8-1. サーバーサイドでトークンの情報を受け取れるようにする
現状のコントローラーの記述では、ストロングパラメーターでpriceの情報のみ取得できるようにしています。
しかし、決済処理にはトークン（token）の値も必要になるため、tokenを受け取れるようにストロングパラメーターを編集しましょう。
<strong>mergeメソッド</strong>を用いてこのtokenの値を結合するようにしましょう。

    #省略
    
      private
    
      def order_params
        params.require(:order).permit(:price).merge(token: params[:token]) ⇦こちらを追記
      end
    
    end

上記のように設定することで、order_params[:price]としてpriceの情報が、order_params[:token]としてtokenの情報が取得できるようになります。

### 8-2. Orderモデルにトークンの値を追加する
このままでは「Orderモデル（Orderクラス）」にtokenという属性が存在しないため、tokenについてもOrderモデルで使用できるようにするためには、<strong>attr_accessor :token</strong>という記述をOrderモデルに記述します。

    # orderモデル内
    class Order < ApplicationRecord
      attr_accessor :token　⇦追記箇所
      validates :price, presence: true
    end

Ruby on RailsにおけるモデルはActiveRecordを継承しています。
そしてActiveRecordを継承することで、モデル名に対応したテーブルのカラム名をもとに、自動的に以下の2点を行います。

- 値の取得ができるメソッドの定義（ゲッター）
- 値の更新ができるメソッドの定義（セッター）

Orderモデルと連携するordersテーブルには、priceカラムが存在します。
したがって、これまでのアプリケーションでは@order.priceでpriceの情報が取得できていましたし、@order.price = 5000のように上書きすることができていました。

クレジットカード決済機能導入前のOrderモデル（Orderクラス）のイメージとしては以下のようになります。 
あくまで以下はイメージであり、Ruby on Railsの挙動を正確に表現したものではありません。

    【例】クレジットカード決済機能導入前のOrderモデルにおけるゲッターとセッターのイメージ
    
    # 以下はOrderモデルのイメージ
    class Order
      def price #ゲッター
        @price
      end
    
      def price=(price) #セッター
        @price = price
      end
    end
    
    
    # 以下はコントローラーからの呼び出しイメージ
    
    @order = Order.new
    @order.price = 3000
      #=> セッターが設定してるので、priceを3000と設定できる
    puts @order.price
      #=> ゲッターが設定してあるので、3000が出力される

上記の記述は、Rubyのattr_accessorメソッドを使用することで、以下のように省略することができます。

    【例】attr_accessorを用いたゲッターとセッターの省略
    # 以下はOrderモデルのイメージ
    class Order
      attr_accessor :price
    end
    
    
    # 以下はコントローラーからの呼び出しイメージ
    
    @order = Order.new
    @order.price = 3000
      #=> セッターが設定してるので、priceを3000と設定できる
    puts @order.price
      #=> ゲッターが設定してあるので、3000が出力される

これがattr_accessorメソッドの役割です。

- attr_accessor
記述したクラスに、ゲッターとセッターを定義してくれるメソッドです。
与えられた引数をもとに属性を設定し、これを取得するメソッド（ゲッター）と更新するメソッド（セッター）を定義してくれます。

Ruby on Railsにおいては、ActiveRecordを継承することで、テーブルに存在するカラムについて上記までを自動的に行っています。

そして、今回実装の途中で直面したunknown attribute 'token' for Orderというエラーは、Orderモデルでordersテーブルには存在しないtokenの属性が使えないことが原因でした。いわば、以下のような記述を行ってエラーが生じていたといえます。

    【例】今回直面したエラーの状態
    # 以下はOrderモデルのイメージ
    class Order
      attr_accessor :price
    end
    
    
    # 以下はコントローラーからの呼び出しイメージ
    
    @order = Order.new
    @order.price = 3000
      #=> セッターが設定してるので、priceを3000と設定できる
    puts @order.price
      #=> ゲッターが設定してあるので、3000が出力される
    @order.token = "tok_0000000000"
      #=> token属性を定義していないのでエラーがでる
    puts @order.token
      #=> token属性を定義していないのでエラーがでる
そこで、今回の実装と同様にattr_accessorメソッドでtokenを指定します。
すると、エラーが出現せずに処理が完了することがわかります。

    【例】tokenについても取り扱えるようにする
    # 以下はOrderモデルのイメージ
    class Order
      attr_accessor :price, :token
    end
    
    
    # 以下はコントローラーからの呼び出しイメージ
    
    @order = Order.new
    @order.price = 3000
      #=> セッターが設定してるので、priceを3000と設定できる
    puts @order.price
      #=> ゲッターが設定してあるので、3000が出力される
    @order.token = "tok_0000000000"
      #=> セッターが設定してるので、tokenを"tok_0000000000"と設定できる
    puts @order.token
      #=> ゲッターが設定してあるので、"tok_0000000000"が出力される

実際には、Ruby on Railsにおけるモデルの処理はもっと複雑な機構が絡み合っています。しかし、大まかな流れは上記で説明したとおりです。

<strong>「モデルに対応するテーブルのカラム名以外の属性を扱いたい場合はattr_accessorを用いて追加する」</strong>と覚えておきましょう。
この考え方は、フォームオブジェクトにおいても応用することができます。

### 8-3. 決済処理を実装する
サーバーサイドからPAY.JPのAPIを利用して決済処理が行えるようにします。
サーバーサイドでは、PAY.JPが提供しているAPIのGem、'payjp'を利用します。

    # 省略（Gemfile内最下層）
    gem 'payjp'　⇦追記箇所

    ※bundle installを忘れないこと

### 8-4. 秘密鍵を取得する
- 秘密鍵とは
サーバーサイド側からPAY.JPへ情報を送り、決済処理をするために用いられる鍵です。この秘密鍵が、トークンを生成したときに使用した公開鍵とセットで取得したものでないと、決済の処理を行うことができません。
なお、この<strong>秘密鍵は外部に漏らしてはいけないもの</strong>です。

秘密鍵は、公開鍵と同様に自動で生成されています。秘密鍵の情報は、PAY.JPにログイン後、ページの左側の「API」をクリックすると確認できます。
この秘密鍵をコピーしておく。

### 8-5.　決済処理を記述する
コントローラーに決済処理を記述していきます。

決済処理を行うには、秘密鍵をPAY.JP側へ送付する必要があります。
そのために、先ほど導入したPAY.JPのAPIのGem「payjp」が提供する、<strong>Payjpクラスのapi_keyというインスタンスに秘密鍵を代入します</strong>。

また、<strong>決済に必要な情報は同様にGemが提供する、Payjp::Charge.createというクラスおよびクラスメソッドを使用します。</strong>

    # ordersコントローラー内
    class OrdersController < ApplicationController
    
      def index
        @order = Order.new
      end
    
      def create
        @order = Order.new(order_params)
        if @order.valid?
          ＜追記箇所（ここから）＞
          Payjp.api_key = "sk_test_***********"  # 自身のPAY.JPテスト秘密鍵を記述しましょう
          Payjp::Charge.create(
            amount: order_params[:price],  # 商品の値段
            card: order_params[:token],    # カードトークン
            currency: 'jpy'                 # 通貨の種類（日本円）
          )
          ＜追記箇所（ここまで）＞
          @order.save
          return redirect_to root_path
        else
          render 'index'
        end
      end
    
      private
    
      def order_params
        params.require(:order).permit(:price).merge(token: params[:token])
      end
    
    end

バリデーションを正常に通過した時のみに、決済処理が行われるようにしました。

- <strong>amount</strong>
実際に決済する金額が入ります。

- <strong>card</strong>
トークン化されたカード情報が入ります。

- <strong>currency</strong>
取引に使用する通貨の種類を記述します（今回は日本円を指定しています）。

なお、秘密鍵は公開してはいけません。
したがって、 この状態のコードをGitHubにpushしてはいけません。
この後実装する環境変数化のステップを経たのちに、コードはGitHub上で公開することができます。

今回はテストモードの秘密鍵を用いているため、万が一流出した場合も不正請求される心配はありません。
ただし、アプリケーションを実装していく中で、鍵情報を含んだコードを公開してしまい、不正請求等の被害に合うリスクが高まります。
仮にテストモードの秘密鍵だったとしても、GitHubのコード上でそれらが公開されている場合はコードを見ている人が実装者に不信感をもち、ポートフォリオとしての評価が著しく低くなります。

### 8-6.　決済処理を確認する
環境変数に変更できていないのでセキュリティ面の問題は解決できていませんが、
決済処理が問題なくできるか確認することはできます。

- 金額がordersテーブルに保存されるか
- PAY.JP上で決済の記録が残るか
 
を確認しましょう。
決済の記録は、PAY.JPにログインし、「売上」の項目を選択することで閲覧できます。

### 8-7. リファクタリング（整理整頓）する
現状のコントローラーの記述はやや複雑で読みにくいものになっています。
新たにメソッドを定義し、その中に決済処理を移動しましょう。

    class OrdersController < ApplicationController
    
      def index
        @order = Order.new
      end
    
      def create
        @order = Order.new(order_params)
        if @order.valid?
          pay_item　⇦　リファクタリングしたメソッドの呼び出し
          @order.save
          return redirect_to root_path
        else
          render 'index'
        end
      end
    
      private
    
      def order_params
        params.require(:order).permit(:price).merge(token: params[:token])
      end

      ＜リファクタリング箇所（ここから）＞
      def pay_item
        Payjp.api_key = "sk_test_***********"  # 自身のPAY.JPテスト秘密鍵を記述しましょう
        Payjp::Charge.create(
          amount: order_params[:price],  # 商品の値段
          card: order_params[:token],    # カードトークン
          currency: 'jpy'                 # 通貨の種類（日本円）
        )
      end
      ＜リファクタリング箇所（ここまで）＞
    
    end

### 8-8. tokenのバリデーションを実装し、テストコードを実装しよう
このままでは、tokenが空のまま送信すると、コントローラーでエラーが生じてしまう。
モデルにtokenが空では保存できないというバリデーションを設定しましょう。
そうすることによって、コントローラー9行目のif @order.valid?でfalseとなり、エラーメッセージをブラウザ上に表示してくれるようになります。

    # orderモデル内
    class Order < ApplicationRecord
      attr_accessor :token
      validates :price, presence: true
      validates :token, presence: true　⇦ 追記箇所
    end

tokenはordersテーブルに存在しないため、本来はOrderモデルのバリデーションとしては記載することはできません。
しかしながら、前のステップでattr_accessor :tokenと記載したことにより、tokenについてのバリデーションを記述することができます。

### （8-9. tokenに関するテストコードを編集して実行する）

    #　spec/factories/orders.rb内
    FactoryBot.define do
      factory :order do
        price {3000}
        token {"tok_abcdefghijk00000000000000000"}　⇦追記箇所
      end
    end
    
    # spec/models/order_spec.rb内
    require 'rails_helper'
    
    RSpec.describe Order, type: :model do
      before do
        @order = FactoryBot.build(:order)
      end
    
      context '内容に問題ない場合' do
        it "priceとtokenがあれば保存ができること" do
          expect(@order).to be_valid
        end
      end
    
      context '内容に問題がある場合' do
        it "priceが空では保存ができないこと" do
          @order.price = nil
          @order.valid?
          expect(@order.errors.full_messages).to include("Price can't be blank")
        end
        
        ＜追記箇所（ここから）＞
        it "tokenが空では登録できないこと" do
          @order.token = nil
          @order.valid?
          expect(@order.errors.full_messages).to include("Token can't be blank")
        end
        ＜追記箇所（ここまで）＞
      end
    end

    # ターミナル上
    % bundle exec rspec spec/models/order_spec.rb

## 9.環境変数を設定する（MacOSがCatalina以降の場合）
ターミナルで以下のコマンドを実行して環境変数を設定しましょう。

    # ターミナル上
    % vim ~/.zshrc
    # iを押してインサートモードに移行し、下記を追記する。既存の記述は消去しない。
    export PAYJP_SECRET_KEY='sk_test_*************'
    export PAYJP_PUBLIC_KEY='pk_test_*************'
    # 編集が終わったらescキーを押してから:wqと入力して保存して終了

記述ができたら、以下のコマンドを実行して設定を反映させましょう。

    # 編集した.zshrcを読み込み直して、追加した環境変数を使えるようにする
    % source ~/.zshrc

ここまで来たら環境変数が正しく設定できているか確認しましょう。

    % rails c
    
    [1] pry(main)> ENV["PAYJP_SECRET_KEY"]
    → 登録した鍵名が表示されれば問題なし
    [2] pry(main)> ENV["PAYJP_PUBLIC_KEY"]
    → 登録した鍵名が表示されれば問題なし

サーバーを再起動し、問題なく決済処理がなされるか改めて再確認する。

なお、環境変数の設定時は、以下のことに気をつけましょう。

- 環境変数を設定したターミナルのタブでサーバーを起動すること
- 環境変数を設定したターミナルのタブを見失った場合は、全てのターミナルのタブでsource ~/.zshrc（またはsource ~/.bash_profile）を実行すること

上記を徹底しても環境変数に関連したエラーが発生する場合は、ターミナルを終了して、再起動してから確かめましょう。

## 10. サーバーサイドで環境変数を読み込めるようにしよう

    # app/controllers/orders_controller.rb 内
    # 省略
    def pay_item
       Payjp.api_key = ENV["PAYJP_SECRET_KEY"]　⇦ 編集箇所
       Payjp::Charge.create(
         amount: order_params[:price],
         card: order_params[:token],
         currency:'jpy'
       )
    end

## 11. クライアントサイドで環境変数を読み込めるようにしよう
クライアントサイド、つまりJavaScriptのファイルには公開鍵が設定されています。厳密に言えば、公開鍵についてはそのままコード上に記述していても問題ありません。
しかしながら、「鍵情報は外部に公開しないようにする」ためにも、今回は公開鍵も環境変数として設定し、JavaScriptのファイルから読み込むようにします。

### 11-1. JavaScriptで環境変数を呼び込みましょう
今までの記述では、Railsで設定した環境変数をJavaScriptで呼び出せません。
その際は、下記のようにwebpackerを用いることで呼び出せます。

まず、以下のコマンドを実行してwebpacker.rbを作成してください。

    # ターミナル上
    % touch config/initializers/webpacker.rb
    ※　touchコマンドでは、空のファイルを作成することができる

次に以下のように編集する。

    # config/initializers/webpacker.rb 内
    Webpacker::Compiler.env["PAYJP_PUBLIC_KEY"] = ENV["PAYJP_PUBLIC_KEY"]

    # app/javascript/card.js 内
    const pay = () => {
     Payjp.setPublicKey(process.env.PAYJP_PUBLIC_KEY);　⇦　編集箇所
      // 省略

サーバーを再起動して問題なく使用できるか再確認する。

## 12. Heroku（本番環境）に環境変数を設定しよう
    # ターミナル上
    % heroku config:set PAYJP_SECRET_KEY='sk_test_*************'
    % heroku config:set PAYJP_PUBLIC_KEY='pk_test_*************'

### 12-1. Heroku上で環境変数を確認しましょう

    # ターミナル上
    % heroku config

一覧に、PAYJP_SECRET_KEYとPAYJP_PUBLIC_KEYという変数名で、値が設定されていれば成功です。

ここまでの作業で環境変数を本番環境に設定することができました。
しかし、このままでは本番環境には反映されません。

本番環境に反映させるためには、Herokuへのデプロイ作業を行う必要があります。
本番環境のプロセスを再起動するためです。

    # ターミナル上
    % git push heroku master

<strong>コミットをHerokuへプッシュした後に、環境変数を設定すると反映されません。
コミットをHerokuへプッシュする前に設定しましょう。</strong>