Rails + Stripe API を利用したコーディングサンプル
Stripe Gemを利用したRails APIのサンプルを作りました。Githubのリポジトリは以下になります。 acotie/rails_stripe_connect_api: Stripe charge & subscription sample code w/ Rails 5.1.x + Grape 1.0
Stripe公式サイト
メインのGemたち
- rails 5.1.4+
- devise
- devise_token_auth
- grape
- stripe
- dotenv-rails
UserはDeviseで作り、重要なことはstripeのapiとRails側のUserデータをどのように連携させるか?というアプローチについて。
当時1週間以内にStripeの課金APIを実装しなければならず、参考にGithubをめちゃくちゃ検索した。
User
モデルに、stripe_customer_id
を持たせるというアイディアを知って模倣して作る。
Stripeのインストール、使い方
Stripeのダッシュボード画面 左上メニュー +新規アカウント
> API KEYを取得する。
Gemfileに追加
1
gem 'stripe'
bundle install でStripe::
ネームスペースが使えるようになる。
1
$ bundle install --path vendor/bundle
RubyファイルからのCharge呼び出しサンプル。$ rails c
でも試せる。
1
2
3
4
5
6
7
8
9
10
11
12
require "stripe"
Stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
Stripe.api_version = "2017-12-14"
Stripe::Charge.list()
Stripe::Charge.create(
:amount => 2000,
:currency => "usd",
:source => "tok_amex", # obtained with Stripe.js
:description => "Charge for elizabeth.taylor@example.com"
)
Stripeの構造について
それぞれオブジェクトがあります。
Customers (ユーザ)
ref: Stripe API Reference
- id :
cus_96gOUYURouuWjz
- description
- currency :
usd
- card_id :
card_18oXxZJgEbT6Ft4J0QBohKDi
Charge (都度課金)
ref: Stripe API Reference
- id :
ch_1BmybM2eZvKYlo2Cdl1GKXze
- amount :
1500
- currency
Subscription (定額課金)
ref: Stripe API Reference
- id :
sub_95lUmykFOcUUMH
- amount :
500
,3000
,6000
- plan_id :
silver
,gold
ref: Stripe API Reference- StripeでPlan.createしたユニークなIDの文字列。
- status
- interval
- interval_count
Plan (定額課金用のプラン) ref: Stripe API Reference
- plan_id :
silver
,gold
実装について
流れとしては、以下の通りとなる。
1. DeviseでUserの作成、ログイン可能なユーザの作成
$ rails g
コマンドでdeviseを利用したユーザ作成を行う。
1
2
$ rails generate devise User
$ rails g devise:views
アプリケーション全般の設定ファイルはconfig内、Deviseを使ったUserの機能制御はmodel内で行える。
- config/initializers/devise.rb
- app/models/user.rb
2. UserモデルにStripe用のIDを追加する
前述の通りUser
モデルに、stripe_customer_id
, stripe_card_id
を持たせる。
1
$ rails generate migration AddStripeCustomerIdToUsers stripe_customer_id:string
3. Chargeモデルの作成
Charge
モデルの作成。stripe_charge_id
とuser_id
を追加する。
Strip内のChargeのIDだけだと都度HTTPリクエストしないといけなくなるので、Railsモデル内に金額のamount
、通貨の単位currency
を追加した。
1
$ rails g model Charge user_id:integer stripe_charge_id:string amount:integer currency:string
4. Subscriptionモデルの作成
Subscription
モデルの作成。stripe_subscription_id
とuser_id
、stripe_customer_id
を追加する。
Strip内のSubscriptionのIDだけだと都度HTTPリクエストしないといけなくなるので、Railsモデル内にamount
は金額、後から周期のinterval
、interval_count
を追加した。
1
$ rails g model Subscription user_id:integer amount:integer stripe_customer_id:string stripe_subscription_id:string
Rails側のModelまとめ
- User
- stripe_customer_id
- stripe_card_id
- subscribed
- subscribed_at
- Charge (都度課金)
- user_id
- stripe_charge_id
- amount
- currency
- Subscription (定額課金)
- user_id
- amount
- stripe_subscription_id
- stripe_plan_id
- interval
- interval_count
- stripe_status
実際のコードサンプル
app/models/user.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :subscription
has_many :charges
#############################################
##### Start Connection Stripe Objects #######
#############################################
# stripe customer_id
# return string e.g. "cus_96gOUYURouuWjz"
def customer_id
customer.id
end
# stripe card_id
# return string e.g. "card_18oXxZJgEbT6Ft4J0QBohKDi"
def card_id
card.id
end
# stripe card_id
# return string e.g. "card_18oXxZJgEbT6Ft4J0QBohKDi"
# def card_id
# customer.sources.first.id
# end
# stripe subscription_id
# return string e.g. "sub_95lUmykFOcUUMH"
def subscription_id
customer.subscriptions.first.id
end
# Stripe::Card object
# see https://stripe.com/docs/api#card_object
def card_data
customer.sources.first
end
# Stripe::Subscription object
# see https://stripe.com/docs/api#subscription_object
def subscription_data
customer.subscriptions.first
end
# for API
# Stripe::Customer object
# see https://stripe.com/docs/api#customer_object
# TODO: plan, cardは後からupdate
def customer
if self.stripe_customer_id.present?
Stripe::Customer.retrieve(self.stripe_customer_id)
else
create_customer
end
end
# TODO Research STRIPE updated new API (create & update card)
def card
if self.stripe_card_id.present?
customer.sources.retrieve(self.stripe_card_id)
else
create_card
end
end
#############################################
###### End Connection Stripe Objects ########
#############################################
private
def create_customer
customer = Stripe::Customer.create(
email: self.email,
description: "Customer for #{self.email}",
)
self.stripe_customer_id = customer.id
self.save
customer
end
end
app/api/sample/charges.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Sample::Charges < Grape::API
resource "charges" do
desc "create new Charge"
params do
requires :amount, type: Integer, desc: "amount (ドル OR 円)"
requires :currency, type: String, default: 'usd', values: ['usd', 'jpy']
end
post do
error!('not subscribed', 400) if !current_user.subscribed? # [MUST]メンバーシップ加入済み
amount = params[:amount] * 100 if params[:currency] == 'usd'
amount = params[:amount] if params[:currency] == 'jpy'
customer = current_user.customer
charge = Stripe::Charge.create(
customer: customer,
currency: params[:currency],
amount: amount,
description: "Charge for #{current_user.email}",
)
charges = current_user.charges.create({
stripe_charge_id: charge.id,
amount: charge.amount,
currency: charge.currency,
})
charges.save
present :charges, charges, with: Sample::Entities::Charge, user: current_user
end
end
end
Subscription APIも用意してみた。 app/api/sample/subscriptions.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Sample::Subscriptions < Grape::API
resource "subscriptions" do
desc "create new Subscription (new: only subscription)"
params do
requires :plan_id, type: String, desc: "stripe plan_id (text)"
end
post do
error!('already subscription', 400) if current_user.subscribed?
customer = current_user.customer
card = current_user.card
customer.plan = params[:plan_id]
customer.save
# update credit card
#current_user.save_credit_card(customer.sources.first)
subscription = current_user.create_subscription({
stripe_subscription_id: customer.subscriptions.data[0].id,
stripe_plan_id: customer.subscriptions.data[0].plan.id,
amount: customer.subscriptions.data[0].plan.amount,
interval: customer.subscriptions.data[0].plan.interval,
interval_count: customer.subscriptions.data[0].plan.interval_count,
stripe_status: customer.subscriptions.data[0].status,
})
subscription.save
current_user.update_attributes(subscribed: true, subscribed_at: Time.now.utc)
# TODO create OK/NG response
present :subscription, subscription, with: Sample::Entities::Subscription, user: current_user
end
end
end