この記事では、SaaSの料金や機能リストの管理をコードで管理する方法を紹介します。terraformとコミュニティプラグインであるStripe Providerを使った料金や機能リストの設定方法とデプロイの仕方、そしてエラーへの対処方法について簡単に解説します。テスト環境と本番環境での設定を統一させたい方や、料金や機能に関する設定についてもCI / CDを利用したフローでデプロイされたい方は、ぜひお読みください。
terraform Advent Calendar 2024 11日目のポストです
料金や機能リストをコードで管理する
Stripeには、開発やテストに利用するサンドボックス環境と実際の決済処理を行うLive環境があります。アプリケーションがどの環境を利用するかは、Stripe APIを呼び出す際のAPIキーを利用したい環境のものに差し替えるだけで対応できます。
商品や料金情報、Webhook APIの設定などを複数の環境間でデータを同期したい場合、一般的にはダッシュボードで都度操作を行うか、Stripe CLIまたはAPIを利用して設定を反映するスクリプトを作成します。しかし手動操作による設定ミスや同期漏れ、スクリプトの保守管理コストなどを考えると、既存のコード管理ツールを利用できるのが理想的です。
今回はコードでStripe上のデータをコードで管理するためのツールとして、コミュニティプラグインであるStripe Providerを紹介します。これはLukas Aron氏によって作成・保守されているコミュニティプラグインで、terraformで定義した設定をStripe API経由で反映するためのterraform Providerです。
今回の記事で紹介した定義や設定方法をまとめたサンプルをGitHubにて公開しています。動かしながら試されたい方は、こちらも一緒にご覧ください。
https://github.com/hideokamoto-stripe/example-stripe-terraform-plan-management
terraformとStripe Providerをセットアップする
terraformのStripe Providerを利用するには、まずterraformのセットアップが必要です。terraformのドキュメントを参考にterraformコマンドを利用する準備を行いましょう。macOSでHomebrewを利用している場合は、次のコマンドでインストールできます。
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
Stripe Providerのセットアップ方法
Stripe Providerを利用するには、tf
ファイルにProviderの設定を行います。main.tf
を作成して定義を行いましょう。Providerのドキュメントを参考に定義を行いましょう。
terraform {
required_providers {
stripe = {
source = "lukasaron/stripe"
}
}
}
provider "stripe" {
# Configuration options
}
terraform
とprovider "stripe"
の定義が終われば、terraform init
を実行します。これでProviderのインストールなどが実行され、Stripe Providerを使ったリソース定義ができるようになります。terraform init
が成功すれば、次のようなメッセージが表示されます。
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Stripe APIキーは環境変数にて設定しよう
terraformのStripe Providerは、Stripe APIを利用してリソース管理を行います。そのためこのProviderを利用するにはStripeのシークレットAPIキーまたは制限付きAPIキーが必要です。StripeのシークレットAPIキーや制限付きAPIキーは、顧客データやビジネス情報などにアクセスできるため、ソースコードに直接記述することはおすすめできません。また、利用する環境(サンドボックス・Liveなど)によってAPIキーを変更する必要もあるため、環境変数として設定しましょう。terraformでは、TF_VAR
からはじまる環境変数として設定すると、簡単にAPIキーをインポートできます。
export TF_VAR_stripe_api_key=sk_test_xxx
インポートしたい変数は、variable
で変数定義しましょう。ここで定義する変数名は、TF_VAR_
を除外した名前にします。そのためTF_VAR_stripe_api_key
でexport
した場合は、variable "stripe_api_key"
と定義しましょう。
+variable "stripe_api_key" {
+ type = string
+}
最後に定義した変数をStripe Providerのapi_key
に設定しましょう。これでStripe Prodiverが任意のStripeアカウントのリソースを管理する準備ができました。
provider "stripe" {
+ api_key = var.stripe_api_key
}
SaaSの料金プランをコードで管理しよう
セットアップができましたので、早速コードで料金プランの定義をはじめましょう。Providerのドキュメントに対応しているリソースのリストと、設定できるパラメータのリストがまとまっていますので、これを参考に値をカスタマイズしてください。
resource "stripe_product" "standard_product" {
name = "Standard license"
unit_label = "seat"
description = "Awesome SaaS license"
active = true
}
resource "stripe_price" "standard_monthly_price" {
product = stripe_product.standard_product.id
currency = "jpy"
unit_amount = 9800
lookup_key = "standard_monthly"
recurring {
interval = "month"
interval_count = 1
}
}
価格改定に備えて、lookup_key
を設定しよう
必須の値ではありませんが、lookup_key
を料金へ設定することをお勧めします。これは料金データをAPIから取得する場合に利用できる検索用キーで、プランの料金を変更する場合などに、Price APIを利用しているソースコードの変更箇所を減らすことができます。
const result = await stripe.prices.list({
lookup_keys: ['membership_free', 'membership_bronze', 'membership_silver']
})
もしlookup_key
を設定・利用しない場合、サンドボックス環境やLive環境それぞれで作成されたPrice IDを取得する処理を実装するか、別途DBを用意する必要があります。この方法では、新しい料金へ変更する際にDBやソースコードの変更が必要となるため、価格改訂に必要な工数が肥大化してしまいます。
terraform apply
でStripeアカウントへ反映する
設定した内容がどのように反映されるかは、terraform plan
でプレビューできます。ここで変更・追加削除されるリソースの一覧を確認し、意図しない変更が起きないかをチェックします。
$ terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# stripe_price.standard_monthly_price will be created
+ resource "stripe_price" "standard_monthly_price" {
+ active = true
+ billing_scheme = (known after apply)
+ currency = "jpy"
+ id = (known after apply)
+ product = (known after apply)
+ tax_behavior = "unspecified"
+ transfer_lookup_key = false
+ type = (known after apply)
+ unit_amount = 9800
+ unit_amount_decimal = (known after apply)
+ recurring {
+ interval = "month"
+ interval_count = 1
+ usage_type = "licensed"
}
}
# stripe_product.standard_product will be created
+ resource "stripe_product" "standard_product" {
+ active = true
+ description = "Awesome SaaS license"
+ id = (known after apply)
+ name = "Standard license"
+ product_id = (known after apply)
+ unit_label = "seat"
}
その後問題がなければ、terraform apply
でデプロイします。このアクションは本当に実行するかの確認ステップがあります。プレビュー内容を確認してデプロイしても問題なさそうであれば、yes
と回答してデプロイを実行します。
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
あとはリソースの作成状況が順次出力されますので、作業が完了するのを待ちましょう。
stripe_product.standard_product: Creating...
stripe_product.standard_product: Creation complete after 1s [id=prod_RMidOVSz3Q38vA]
stripe_price.standard_monthly_price: Creating...
stripe_price.standard_monthly_price: Creation complete after 1s [id=price_1QTzFVQ9mxJNCzY5GRhU9mIV]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
デプロイが完了すれば、Stripeのダッシュボードで実際にリソースが作成されていることを確認しましょう。
このようにterraformを利用して簡単にStripe上にあるリソースのコード管理が実現できます。
変更やプランの追加もterraformで
コードで設定を管理するため、新しい料金プランや商品を追加する場合のワークフローもterraformベースで行えます。たとえば下位プランの料金を追加するには、対応する商品と料金をそれぞれ追加するだけです。
resource "stripe_product" "hobby_product" {
name = "Hobby"
unit_label = "seat"
description = "Hobby and personal usage plan"
active = true
}
resource "stripe_price" "hobby_price" {
product = stripe_product.hobby_product.id
currency = "jpy"
unit_amount = 980
recurring {
interval = "month"
interval_count = 1
}
}
追加後にterraform apply
を実行すると、新規に追加したリソースのみ追加されます。
stripe_product.hobby_product: Creating...
stripe_product.hobby_product: Creation complete after 0s [id=prod_RMimUAtHdrL64t]
stripe_price.hobby_price: Creating...
stripe_price.hobby_price: Creation complete after 1s [id=price_1QTzLNQ9mxJNCzY5T3cvoOZd]
Apply complete! Resources: 2 added, 0 changed, 1 destroyed.
これで下位プランであるHobbyプランが追加できました。
コードベースで料金プランを管理することで、terraform plan
の内容を共有したりCIワークフローでレビューできるようにしたりが可能となります。それによって新プランのローンチやテストの進め方などの管理をより簡単にできます。
SaaSの機能リストをEntitlements APIで管理する
terraformで管理できるのは料金だけではありません。SaaSではほぼ必須の機能である、プランごとの権限・機能管理もコード化できます。たとえばダッシュボードへのアクセスとREST APIへのアクセスの2つをユーザーに提供するケースを考えてみましょう。この場合、それぞれの機能をstripe_entitlements_feature
リソースとして定義します。ここでもAPIからの検索などをしやすくするため、lookup_key
の設定をお勧めします。
resource "stripe_entitlements_feature" "feature_dashboard_access" {
name = "Dashboard Access"
lookup_key = "dashboard_access"
}
resource "stripe_entitlements_feature" "feature_rest_api_access" {
name = "REST API Access"
lookup_key = "rest_api_access"
}
続いて登録したリソースを商品に紐付けしましょう。stripe_product_feature
リソースを追加し、対象の商品IDと提供したい機能のリソースIDの2つを定義します。もし複数の機能を1つの料金プラン(商品)で提供したい場合は、その組み合わせの数だけリソースを定義しましょう。
。下の例では、Hobbyプランはダッシュボードへのアクセスのみ提供し、StandardプランではREST APIも利用できる設定をおこなっています
resource "stripe_product_feature" "standard_product_feature_rest_api_access" {
entitlements_feature = stripe_entitlements_feature.feature_rest_api_access.id
product = stripe_product.standard_product.id
}
resource "stripe_product_feature" "hobby_product_feature_dashboard_access" {
entitlements_feature = stripe_entitlements_feature.feature_dashboard_access.id
product = stripe_product.hobby_product.id
}
resource "stripe_product_feature" "standard_product_feature_dashboard_access" {
entitlements_feature = stripe_entitlements_feature.feature_dashboard_access.id
product = stripe_product.standard_product.id
}
この設定をterraform apply
でデプロイしましょう。それぞれのリソースにIDが割り振られますので、社内Wikiなどに記録しておきたい場合は、実行結果を控えておくとよいでしょう。
stripe_entitlements_feature.feature_dashboard_access: Creating...
stripe_entitlements_feature.feature_rest_api_access: Creating...
stripe_entitlements_feature.feature_dashboard_access: Creation complete after 0s [id=feat_test_61RdNK3uaykEjKMIF41Q9mxJNCzY5NpA]
stripe_entitlements_feature.feature_rest_api_access: Creation complete after 0s [id=feat_test_61RdNK3ElxDCVoZjF41Q9mxJNCzY5TSy]
stripe_product_feature.standard_product_feature_dashboard_access: Creating...
stripe_product_feature.hobby_product_feature_dashboard_access: Creating...
stripe_product_feature.standard_product_feature_rest_api_access: Creating...
stripe_product_feature.standard_product_feature_rest_api_access: Creation complete after 1s [id=prodft_1QTzMiQ9mxJNCzY5WYYoE4OS]
stripe_product_feature.hobby_product_feature_dashboard_access: Creation complete after 1s [id=prodft_1QTzMiQ9mxJNCzY57PhuX12n]
stripe_product_feature.standard_product_feature_dashboard_access: Creation complete after 1s [id=prodft_1QTzMiQ9mxJNCzY5lJovSY2f]
Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
デプロイが完了すると、ダッシュボードの各商品ページにて更新された機能リストが確認できます。
Standardプランは、Dashboard / REST API両方の機能が設定されています。
HobyプランはDashboardのみが設定されています。
提供する機能・権限についてもterraformで管理することによって、新機能リリース前の設定チェックをterraform plan
の出力に集約することができます。
トラブルシューティング
terraform apply
を実行すると、エラーが発生することもあります。Stripe ProviderはStripe APIのエラーレスポンスを表示しますので、この内容をみて設定値の間違いがないかをチェックしましょう。
また、レスポンスに含まれるrequest_log_url
のURLへもぜひアクセスしてください。Stripeダッシュボードが提供する開発者向けのAPIログ調査機能を使って、「どんなAPIリクエストが送信されたか」や「このエラーは何を意味しているか」などを簡単にチェックできます。
まとめ
価格改訂や継続的な機能追加による変更タスクが定期的に発生するのがSaaSの運用保守です。なかでも決済に関する機能はミスが顧客からのクレームにつながりやすく、リリース前に内容の慎重なチェックを入れたり、できるだけ環境間の差分を起こしにくい仕組みが必要とされます。
terraformのStripe Providerを活用することで、料金プランだけでなく、顧客に提供するプランごとの権限・機能リストについてもコードで管理し、CI / CDパイプラインに載せることが期待できます。
決済・サブスクリプションサービスをより効率的に、そしてトラブルの起きにくい形で運用するためにも、ぜひこのプラグインを使った新しい料金・機能管理のOpsを検討してください。
関連記事