8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ElixirAdvent Calendar 2024

Day 23

[Elixir] Stripeで従量課金を実現する

Last updated at Posted at 2024-12-22

この記事は Elixir Advent Calendar 2024 シリーズ4の23日目です。昨日は @piacerex さんでした!


執筆時点ではまだベータテストという建付けですが、imagepix というサービスを開発・運営しています。

imagepixの管理コンソールElixirのWebアプリケーションフレームワーク Phoenix で開発していて、従量課金を実現するために言わずと知れた Stripe を使っています。

公式ドキュメントが充実しているので困ることはないのですが、従量課金の解説記事はあまり見かけません。そこで、今回はElixirでStripeのAPIを使って従量課金を実現する流れを解説してみます :money_with_wings:

Stripeダッシュボードでの事前準備

アカウントを持っていない場合は、以下を参考に作っておきましょう。

私はもちろんアカウントを持っていますが、テスト環境を汚したくないこともあり、今回たまたま発見した サンドボックス を使うことにします。もちろんテスト環境を使っても構いません :ok_hand:

メーターの作成

https://dashboard.stripe.com/test/meters

以下のようにメーターを作成します。メーター名は後からでも変えられますが、イベント名やその他の項目は一度決めると変えられないので要注意です。

image.png

商品の作成

https://dashboard.stripe.com/test/products

以下のように商品を作成します。今回は1ユニットあたり100円という商品にしてみます。

image.png

「その他の料金体系オプション」をクリックすると従量課金の料金体系を設定できます。

  • 料金モデル
  • 金額
  • メーター

をそれぞれ以下のように入力しましょう。

image.png

LivebookでのElixir実行環境の準備

本題ではないので、Dockerでサクッと起動しましょう。

docker run --env LIVEBOOK_TOKEN_ENABLED=false -p 8080:8080 -p 8081:8081 --pull always ghcr.io/livebook-dev/livebook

http://localhost:8080

本記事の内容をとりあえず動かしたい方は、以下の方法を試してみてください :information_desk_person:

Live markdown

以下の内容を http://localhost:8080/open/source の「Notebook source」にコピペしてインポートするだけで動かせます。

# Stripeで従量課金を実現する

```elixir
Mix.install([
  {:stripity_stripe, "~> 3.2"},
  {:kino, "~> 0.14.2"}
])
```

## Stripe Checkout Sessionでのサブスクリプションの作成

```elixir
secret_key_input = Kino.Input.password("SECRET_KEY")
```

```elixir
price_input = Kino.Input.password("PRICE")
```

```elixir
secret_key = Kino.Input.read(secret_key_input)
price = Kino.Input.read(price_input)

{:ok, session} =
  Stripe.Checkout.Session.create(
    %{
      line_items: [
        %{price: price}
      ],
      mode: :subscription,
      success_url: "https://dashboard.stripe.com/test/subscriptions"
    },
    api_key: secret_key
  )

IO.puts(session.url)
```

## Stripe Meter Eventでの使用量の記録

```elixir
{:ok, %{data: [subscription | _]}} = Stripe.Subscription.list(%{}, api_key: secret_key)
customer = subscription.customer
```

```elixir
path = Stripe.OpenApi.Path.replace_path_params("/v1/billing/meter_events", [], [])

Stripe.Request.new_request(api_key: secret_key)
|> Stripe.Request.put_endpoint(path)
|> Stripe.Request.put_params(%{
  event_name: "sample",
  payload: %{
    value: 10,
    stripe_customer_id: customer
  }
})
|> Stripe.Request.put_method(:post)
|> Stripe.Request.make_request()
```

ライブラリのインストール

Livebookの「Notebook dependencies and setup」に以下のコードを入力して実行し、ライブラリをインストールします。

Mix.install([
  {:stripity_stripe, "~> 3.2"},
  {:kino, "~> 0.14.2"}
])

stripity_stripe は、StripeのAPIをElixirで実行するためのサードパーティライブラリです。公式ライブラリは本記事の執筆時点では存在しません…

kino は、Livebook上で便利なウィジェットを使えるようにするライブラリです。本記事では秘匿情報の入力フォームのために使います。

秘匿情報の入力

Kino.Input.password/2 を使って秘匿情報の入力フォームを用意します。

Stripeシークレットキー

Livebookで以下のコードを実行すると入力フォームが表示されるので、シークレットキーを入力しましょう。

secret_key_input = Kino.Input.password("SECRET_KEY")

シークレットキーは 開発者ページ で確認できます。

image.png

Stripe価格ID

Livebookで以下のコードを実行すると入力フォームが表示されるので、価格IDを入力しましょう。

price_input = Kino.Input.password("PRICE")

価格IDは 商品カタログページ から確認できます。

image.png

Stripe Checkoutでのサブスクリプションの作成

Stripeでは、Checkout を使ってStripeが提供してくれる決済画面で手続きさせることができます。

Stripe.Checkout.Session/create/2 を使って決済画面のURLを発行します。秘匿情報は Kino.Input.read/1 で取得できます。

secret_key = Kino.Input.read(secret_key_input)
price = Kino.Input.read(price_input)

{:ok, session} =
  Stripe.Checkout.Session.create(
    %{
      line_items: [
        %{price: price}
      ],
      mode: :subscription,
      success_url: "https://dashboard.stripe.com/test/subscriptions"
    },
    api_key: secret_key
  )

IO.puts(session.url)

表示されるURLにアクセスすると以下のような決済画面が表示されるので、カード情報(テストカード を使います)など諸々入力して申し込みましょう。

image.png

申し込みが完了すると、サブスクリプションページ から内容を確認できます。

image.png

この時点では使用量を記録していないので、次回請求は0円になっています。

Stripe Meter Eventでの使用量の記録

使用量を記録すると、次回請求に反映されます。

使用量を記録するには顧客IDが必要です。今回は、作成済みのサブスクリプションから顧客IDを取得します。

{:ok, %{data: [subscription | _]}} = Stripe.Subscription.list(%{}, api_key: secret_key)
customer = subscription.customer

ここで、使用量を記録しますが、残念ながらstripity_stripeは執筆時点でMeter Events APIに対応していないため、顧客作成APIのコード を参考に自前で実装しましょう。

今回は10ユニット使用したと想定します。event_name にはメーターを作る際に入力した「sample」を指定します。

path = Stripe.OpenApi.Path.replace_path_params("/v1/billing/meter_events", [], [])

Stripe.Request.new_request(api_key: secret_key)
|> Stripe.Request.put_endpoint(path)
|> Stripe.Request.put_params(%{
  event_name: "sample",
  payload: %{
    value: 10,
    stripe_customer_id: customer
  }
})
|> Stripe.Request.put_method(:post)
|> Stripe.Request.make_request()

少し待ってからサブスクリプションページを見ると、次回請求が1,000円になっています :tada: 100円の商品を10ユニット使用した想定なので、計算も合っていますね :sparkles:

image.png

最後に

今回はLivebookで完結させられるようにシークレットキーを都度指定していますが、Elixirアプリケーションを開発する際は config/*.exs で設定するのが良いでしょう。

StripeではAPIのバージョンが更新されていき、挙動が変わってしまうこともあるので、明示的にバージョンを指定しておくとより安心です :blush:

config :stripity_stripe,
  api_key: System.get_env("STRIPE_SECRET"),
  api_version: "2024-11-20.acacia"

今回はかなり単純な例でしたが、Stripeでは段階的な価格にしたり複数の商品を組み合わせたりといった複雑な料金体系も簡単に実現できるので、ぜひ試してみてください :moneybag:

それにしても、一昔前と比べて決済を組み込むの楽になりましたねぇ(遠い目)。まあ、法律やセキュリティ基準もだいぶ厳しくなりましたが…


明日の Elixir Advent Calendar 2024 シリーズ4の24日目は @RyoWakabayashi さんです!
お楽しみに :rocket:

8
1
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
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?