サービスを開発していると決済機能をサービス内に実装したいことがよくあるかと思います。
今までちょくちょくstripeを利用してきたので、その経験を元に仮払い(オーソリ)込みの決済の実装方法を記述していこうと思います。
##Stripeとは
Stripeとはインターネットビジネス内に組み込むことができる決済サービスの1つです。Stripeを使えば、自分が開発しているサービスにクレジットカードを使った決済機能を組み込むことができます。決済方法が銀行振込のみのサービスにはぜひ導入を検討してみてください。
##stripeの手数料
stripeの手数料は決済毎に3.6%かかるのみです。導入費用や月ごとの利用料等は全て無料です。つまり、使った分だけ手数料がかかるという仕組みです。固定で発生する費用がないため、個人開発で売り上げがなかなか見込めない小さなサービスにもぴったりだと思います。
##Railsプロジェクトにstripeを導入する
それではstripeをRailsに導入してみましょう。
stripeはgemを用意してくれているので、導入はただgemをbundle installするだけです。
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のテストモードの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を実装していきます。
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のフォームを作ります。
<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メソッドを使う)
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を使えば、自分のサービスに決済機能がつけられるので、サービス開発がさらに楽しくなりますね!