51
58

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RailsでStripeを使って決済機能を実装する(仮払い機能付き)

Last updated at Posted at 2019-07-08

サービスを開発していると決済機能をサービス内に実装したいことがよくあるかと思います。
今までちょくちょくstripeを利用してきたので、その経験を元に仮払い(オーソリ)込みの決済の実装方法を記述していこうと思います。

##Stripeとは
Stripeとはインターネットビジネス内に組み込むことができる決済サービスの1つです。Stripeを使えば、自分が開発しているサービスにクレジットカードを使った決済機能を組み込むことができます。決済方法が銀行振込のみのサービスにはぜひ導入を検討してみてください。

##stripeの手数料
stripeの手数料は決済毎に3.6%かかるのみです。導入費用や月ごとの利用料等は全て無料です。つまり、使った分だけ手数料がかかるという仕組みです。固定で発生する費用がないため、個人開発で売り上げがなかなか見込めない小さなサービスにもぴったりだと思います。

##Railsプロジェクトにstripeを導入する

それではstripeをRailsに導入してみましょう。
stripeはgemを用意してくれているので、導入はただgemをbundle installするだけです。

Gemfile
  gem 'stripe'

stripeのgemを入れると、Rails内でstripeの様々なモデルを利用することができます。
単発の決済に利用するのは以下の2つのモデルです。

###Stripe::Customer
決済の際にまずstripeを利用する顧客情報を登録します。
emailやsourceなどのパラメータを渡します。

###Stripe::Charge
単発決済を行う際の決済情報を登録します。
決済金額(amount)や決済通貨(currency)などのパラメータを渡します。

###Stripe::Refund
決済のキャンセルを登録します。
仮払いの状態であれば全額返金可能。本決済完了後であればstripeの手数料(3.6%)が引かれた96.4%の金額が返金されます。

##Stripe Checkoutを実装する

それでは今回はAirbnbのような自分の部屋を貸すサービスを例にしてコードを書いていきたいと思います。
部屋の管理はRoomモデル、支払いの管理はPaymentモデルで行なっているとします。(1対1の関係)

まずはStripeのAPIキーをstripe.rbに入れておきます。

stripe.rb

#開発環境とテスト環境にはstripeのテストモードのAPIキーを使用する
if Rails.env.development? || Rails.env.test?
  Rails.configuration.stripe = {
    :publishable_key => ENV['STRIPE_PUBLISHABLE_KEY'],
    :secret_key      => ENV['STRIPE_SECRET_KEY']
  }
end

#本番環境にはstripeの本番環境用APIキーを使用する
if Rails.env.production?
  Rails.configuration.stripe = {
    :publishable_key => ENV['STRIPE_PUBLISHABLE_KEY_PRODUCTON'],
    :secret_key      => ENV['STRIPE_SECRET_KEY_PRODUCTION']
  }
end

#それぞれの環境に適したstripeAPIキーをセットしておく。
Stripe.api_key = Rails.configuration.stripe[:secret_key]

次にroom_controllerというコントローラーにstripeを実装していきます。

room_controller.rb

def create
    @room = Room.find(params[:room_id])

    begin

      ActiveRecord::Base.transaction  do
        @room.reserve = "仮予約"
        @room.save!
        #二重決済を防ぐためにすでにpaymentテーブルがユニークか調べる      
        raise "Already paid" if @room.payment.present? 
        #後でPaymentをnewする際に、万が一エラーが起こらないように、transactionの中でpayment_paramsの値を確認しておく
        raise "prameter error" unless params[:amount].present? && params[:stripeEmail].present? && params[:stripeToken].present?
      end

      ##############Stripe(決済)#########################################
        customer = Stripe::Customer.create(
          email: params[:stripeEmail],
          source: params[:stripeToken]
        )

        charge = Stripe::Charge.create(
          customer:       customer.id,
          amount:         params[:amount],
          description:    "「#{@room.name}」の決済",
          currency:       'jpy',
          receipt_email:  params[:stripeEmail],
          metadata: {'仮払い' => "1回目"},
          capture: false #capture:falseにすると仮払いで処理してくれる。
        )
      #####################################################################

      ###############決済記録を作成###################################################
        # stripeのcheckoutフォームから送られてきたパラメーターでpaymentのインスタンスを作成
        payment = Payment.new(payment_params)

        payment.email = customer.email # 支払った人がstripeのcheckoutフォームに入力したemail(支払い完了後、stripeからこのメールアドレスに支払い完了メールが送られる)
        payment.description = charge.description #決済の概要
        payment.currency = charge.currency  #通貨
        payment.customer_id = customer.id   # stripeのcustomerインスタンスのID
        payment.payment_date = Time.current # payment_date(支払いを行った時間)は現在時間を入れる
        payment.payment_status = "仮払い" # payment_status(この支払い)は仮払い状態(stripeのcaptureをfalseにしている)
        payment.uuid = SecureRandom.uuid  # 請求書の番号としてuuidを用意する
        payment.charge_id = charge.id  # 返金(refund)の時に使うchargeのIDをpaymentに保存しておく
        payment.stripe_commission = (charge.amount * 0.036).round  # stripeの手数料(3.6%)分の金額
        payment.amount_after_subtract_commission = charge.amount - payment.stripe_commission  # stripeの手数料(3.6%)分を引いた金額(依頼者が払った96.4%の金額)
        payment.receipt_url = charge.receipt_url  # この決済に対するstripeが用意してくれる領収書のURL
        payment.save!
      #############################################################################

      redirect_to temporary_complete_path #仮払い完了画面へ

    # stripe関連でエラーが起こった場合
    rescue Stripe::CardError => e
    flash[:error] = "#決済(stripe)でエラーが発生しました。{e.message}"
    render :new

    # Invalid parameters were supplied to Stripe's API
    rescue Stripe::InvalidRequestError => e
      flash.now[:error] = "決済(stripe)でエラーが発生しました(InvalidRequestError)#{e.message}"
      render :new

    # Authentication with Stripe's API failed(maybe you changed API keys recently)
    rescue Stripe::AuthenticationError => e
      flash.now[:error] = "決済(stripe)でエラーが発生しました(AuthenticationError)#{e.message}"
      render :new

    # Network communication with Stripe failed
    rescue Stripe::APIConnectionError => e
      flash.now[:error] = "決済(stripe)でエラーが発生しました(APIConnectionError)#{e.message}"
      render :new

    # Display a very generic error to the user, and maybe send yourself an email
    rescue Stripe::StripeError => e
      flash.now[:error] = "決済(stripe)でエラーが発生しました(StripeError)#{e.message}"
      render :new

    # stripe関連以外でエラーが起こった場合
    rescue => e
      flash.now[:error] = "エラーが発生しました#{e.message}"
      render :new
    end

  end

Paymentは領収書の役割を果たすクラスです。
gemのprownやwicked_pdfを使えば、簡単に領収書を作成できるかと思います。
ただ、stripeでは自動的に領収書が作成され、receipt_urlを使えば、作成された領収書のURLを引っ張ってくることもできます。

次にStripe Checkoutのフォームを作ります。

new.html.erb
<div>
    <%= form_tag room_path, id:"payForm" do %>

        <script src="https://checkout.stripe.com/checkout.js"></script>
        <%= hidden_field_tag 'stripeToken' %>
        <%= hidden_field_tag 'amount', @room.price %>
        <%= hidden_field_tag 'stripeEmail' %>
        <!-- 仮払いしたユーザーのID -->
        <%= hidden_field_tag 'user_id', current_user.id %>
        <!-- この仮払いのroomインスタンスを特定 -->
        <%= hidden_field_tag "room_id", @room.id %>

        <!-- 支払い済みであれば決済させない -->
        <% if @room.payment.present? %>
          <p class="btn btn-error">支払い済み</p>
        <% else %>
          <button id="btn-pay" type="button" class="btn btn-primary">仮払いする</button>
        <% end %>

        <script>
          // #ボタンを押した際のcheckoutのフォームはStripeCheckout.configureで設定する
            var handler = StripeCheckout.configure({
                //StripeのAPIキーを引っ張ってくる
                key: '<%= Rails.configuration.stripe[:publishable_key] %>',
                locale: 'auto', //言語の設定(autoの場合、ユーザのブラウザ規定言語が呼び出される)
                currency: 'jpy',
                // image: "image/directory", もしstripe checkoutのフォーム上部に画像を入れたい場合はここで指定する
                panelLabel: "{{amount}}のお支払い", //checkoutボタンの表示文字、{{amount}}の中に金額が入る
                allowRememberMe: false, //RememberMeを使いたい場合はここをtrueにする
                token: function(token,arg) { //ここでstripeTokenとstripeEmailに値を入れてsubmitする
                    document.getElementById('stripeToken').value = token.id;
                    document.getElementById('stripeEmail').value = token.email;
                    document.getElementById('payForm').submit();
                }
            });
            //Stripe Checkoutのフォームに表示される情報をここで指定する
            document.getElementById('btn-pay').addEventListener('click', function(e){
                handler.open({
                    name: '<%= @room.name %>',
                    description: '<%= @room.price %>',
                    amount: document.getElementById("amount").value
                });
                e.preventDefault();
            })
        </script>
    <% end %>
</div>

これで仮払い機能の実装はできました。
あとはお部屋の貸し出しを大家さんがOKしてくれたら、本決済してあげるだけです。
つまり、capturedパラメータをtrueに変更してあげるだけです。(captureメソッドを使う)

room_controller.rb
def pay_complete
  room = current_user.rooms.find(params[:room_id])

  begin

    ActiveRecord::Base.transaction  do

      room.reserve = "予約完了" #予約状況
      room.save!
      room.payment.payment_status = "完了" #支払い状況
      room.payment.save!

      #仮払いをcaputureして決済を完了させる
      charge_id = room.payment.charge_id
      charge = Stripe::Charge.retrieve(charge_id) #retrieveメソッドで仮払いしたchargeインスタンスを引っ張ってくることができる
      charge.capture #captureメソッドを使うと、本決済となり、実際にクレジットカードから決済が行われる。
    end

    redirect_to  complete_path #本決済完了画面へ

  #stripe関連でエラーが起こった場合
  rescue Stripe::CardError => e
  flash.now[:error] = "決済でエラーが発生しました。#{e.message}"
  render :new

  # Invalid parameters were supplied to Stripe's API
  rescue Stripe::InvalidRequestError => e
    flash.now[:error] = "決済(stripe)でエラーが発生しました(InvalidRequestError)#{e.message}"
    render :new

  # Authentication with Stripe's API failed(maybe you changed API keys recently)
  rescue Stripe::AuthenticationError => e
    flash.now[:error] = "決済(stripe)でエラーが発生しました(AuthenticationError)#{e.message}"
    render :new

  # Network communication with Stripe failed
  rescue Stripe::APIConnectionError => e
    flash.now[:error] = "決済(stripe)でエラーが発生しました(APIConnectionError)#{e.message}"
    render :new

  # Display a very generic error to the user, and maybe send yourself an email
  rescue Stripe::StripeError => e
    flash.now[:error] = "決済(stripe)でエラーが発生しました(StripeError)#{e.message}"
    render :new
  ########################################################################################
  # 下のレスキューはstripe関係以外でのエラーが起こった時に発動する
  rescue => e
    flash.now[:error] = "エラーが発生しました#{e.message}"
    render :new
  end

stripeの場合、仮払いの状態が7日間続いたものは自動的にキャンセルされるようになっています。
7日間以上仮払いの状態を保ちたい場合は、rakeタスクにて一旦Stripe::Refundクラスを使って既存の仮払いをキャンセルし、再度Stripe::Chargeから仮払いを行うと良いかと思います。

##さいごに
Stripeを使えば、自分のサービスに決済機能がつけられるので、サービス開発がさらに楽しくなりますね!

51
58
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
51
58

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?