• Home
  • Akiko Yokoyama
  • Contact
  • Feed
ja | en |

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公式サイト

  • Stripe.com - Payment processing for internet businesses
  • Documentation
  • Rails Checkout Guide

メインの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
  • email
  • 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
user-image
Akiko yokoyama in Coding
10 minute read

Similar Posts

3年前にブログの多言語化対応を実はしていた話

Leica Q2 を購入して1年経ちました &レビュー

ブログの再構築を Jekyll + Netlify + Github で行った話

Excel Tips vol.1

WSL / Windows Subsystem for Linux + Ubuntuのセットアップ方法

user-image

Published Jan 13, 2018

Akiko yokoyama in Coding

Also found in

  • Coding

Share this article

3年前にブログの多言語化対応を実はしていた話

Leica Q2 を購入して1年経ちました &レビュー

ブログの再構築を Jekyll + Netlify + Github で行った話

Excel Tips vol.1

WSL / Windows Subsystem for Linux + Ubuntuのセットアップ方法

チャットボット hubot adapterを利用した Slack と Chatwork連携コードサンプル