How to build a Stripe API x Rails (include code sample)
I’ve created a sample Rails API using the Stripe Gem, which can be found in the Github repository below.
acotie/rails_stripe_connect_api: Stripe charge & subscription sample code w/ Rails 5.1.x + Grape 1.0
Assumption - References of Stripe official web site:
Using Gems
- rails 5.1.4+
- devise
- devise_token_auth
- grape
- stripe
- dotenv-rails
The User is created in Devise, and the important thing is how to link the stripe api with the Rails User data. This was the approach we took. I had to implement Stripe’s billing API within a week at the time, and I searched Github like harder for reference.
I learned about the idea of having a stripe_customer_id
in the User
model, and mimicked it.
Installing and using Stripe
Stripe’s dashboard screen upper left menu +New Account
> Get API KEY.
Add to Gemfile
1
gem 'stripe'
With bundle install, you can use Stripe::
namespace.
1
$ bundle install --path vendor/bundle
Sample Charge call from Ruby file. You can also try it with $ rails c
.
stripe-sample.rb
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"
)
About the structure of Stripe
The Stripe library has the following objects.
Customers (User)
ref: Stripe API Reference
- id :
cus_96gOUYURouuWjz
- description
- currency :
usd
- card_id :
card_18oXxZJgEbT6Ft4J0QBohKDi
Charge (One-time billing)
ref: Stripe API Reference
- id :
ch_1BmybM2eZvKYlo2Cdl1GKXze
- amount :
1500
- currency :
usd
Subscription (Flat rate billing)
ref: Stripe API Reference
- id :
sub_95lUmykFOcUUMH
- amount :
500
,3000
,6000
- plan_id :
silver
,gold
ref: Stripe API Reference- This is the unique ID string that I created in Plan.create in Stripe.
- status
- interval
- interval_count
Plan (Plan for flat rate billing) ref: Stripe API Reference
- plan_id :
silver
,gold
About Implementation
The flow is as follows.
1. Create a User in Devise, and create a user who can login.
Create a user using devise with the $ rails g
command. The details of devise are omitted.
1
2
$ rails generate devise User
$ rails g devise:views
The configuration file of the application in general can be found in config, and the function control of User using Devise can be found in model.
- config/initializers/devise.rb
- app/models/user.rb
2. Add ID for Stripe to the User
model
As mentioned above, you will add stripe_customer_id
and stripe_card_id
to the User
model.
1
$ rails generate migration AddStripeCustomerIdToUsers stripe_customer_id:string
3. Create the Charge model.
Create the Charge
model. Add stripe_charge_id
and user_id
.
If you only have the ID of the charge in the stripe, you have to make an HTTP request every time, we added the amount of money amount
and the unit of currency currency
in the Rails model.
1
$ rails g model Charge user_id:integer stripe_charge_id:string amount:integer currency:string
4. Create the Subscription model.
Create the Subscription
model. Add stripe_subscription_id
, user_id
and stripe_customer_id
.
The amount
is the amount of money, and the interval
and interval_count
of the cycle are added later in the Rails model, because we have to make HTTP requests every time if we only have the ID of the subscription in the strip.
1
$ rails g model Subscription user_id:integer amount:integer stripe_customer_id:string stripe_subscription_id:string
Summary of Model in Rails side
- 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
Actual code sample
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
I also prepared the 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