ino-akiのブログ

ITエンジニアを目指して学習したことをアウトプットするブログ

決済機能のサーバーサイド実装

決済機能のサーバーサイド実装
1. サーバーサイドでトークンの情報が受け取れるようにする
2. Orderモデルにトークンの値を追加する
3. PAY.JPによる決済処理を行う
4. バリデーションを設定し、テストコードを実装する

 

サーバーサイドでトークンの情報を受け取る
決済処理にはトークン(token)の値も必要になるため、tokenを受け取れるようにストロングパラメーターを編集

paramsを確認 binding.pryを設定
app/controllers/orders_controller.rb
#省略
  def create
    binding.pry
    @order = Order.new(order_params)
    if @order.valid?
      @order.save
      return redirect_to root_path
    else
      render 'index', status: :unprocessable_entity
    end
  end
#省略
フォームの送信を行い、paramsの中身を確かめ
priceの情報はorderというキーのバリューとして、ハッシュ形式で含まれていることがわかる
一方、tokenの情報はorderというキーのバリューとしては含まれていない
したがって、mergeメソッドを用いてこのtokenの値を結合する

 

ストロングパラメーターを編集
app/controllers/orders_controller.rb
#省略
  private

  def order_params
    params.require(:order).permit(:price).merge(token: params[:token])
  end
#省略
order_params[:price]としてpriceの情報が、order_params[:token]としてtokenの情報が取得できる
order_paramsの中にそれぞれの情報が含まれていることを確認
確認できたら、binding.pryの記述は削除

 

Orderモデルにトークンの値を追加
tokenの値もOrderモデルで取り扱えるようにする
app/models/order.rb
class Order < ApplicationRecord
  attr_accessor :token
  validates :price, presence: true
end


決済処理を実装

決済処理を行うときに必要となるGem
Gemfile最下部に記述
# 省略
gem 'payjp'
記述したら、bundle installを実行し、導入


秘密鍵を取得
PAY.JPにログイン後、ページの左側の「API」をクリックすると確認できる
※公開鍵同様、この秘密鍵も外部に漏らしてはいけない、環境変数化した上で公開することができる


決済処理を記述
app/controllers/orders_controller.rb
#省略
  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', status: :unprocessable_entity
    end
  end
#省略
バリデーションを正常に通過した時のみに、決済処理が行われる
amountには、実際に決済する金額が入る
cardには、トークン化されたカード情報が入る
currencyには取引に使用する通貨の種類を記述

決済処理が行われるか確認
サーバーを再起動
必要な情報を入力し、金額がordersテーブルに保存されるか、PAY.JP上で決済の記録が残るかを確認
PAY.JPにログインし、売上の項目を選択することで閲覧できる


リファクタリングする
コントローラーの記述はやや複雑で読みにくい
新たにメソッドを定義し、その中に決済処理を移動
app/controllers/orders_controller.rb
#省略
  def create
    @order = Order.new(order_params)
    if @order.valid?
      pay_item
      @order.save
      return redirect_to root_path
    else
      render 'index', status: :unprocessable_entity
    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
#省略
pay_itemというメソッドに切り出し、コントローラーの処理の可読性を高める
再度正しく決済処理が実行されるか確かめる


バリデーションを実装し、テストコードを実装

tokenが空では保存できないというバリデーションを設定
app/models/order.rb
class Order < ApplicationRecord
  attr_accessor :token
  validates :price, presence: true
  validates :token, presence: true
end

 

テストコードを編集して実行
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


ブラウザで挙動を確認
何も入力せずに「購入ボタンをクリックして、priceとtokenについてのエラーメッセージが表示されることを確かめる


次は環境変数の設定