こんにちは、サイオステクノロジーの佐藤 陽です。
今日は、Stripe の Webhook のハンドリングを Azure 環境で実際に組み立ててみたいと思います。
- Azure上でStripe の Webhook を活用したアプリを構築したい
- Webhook のハンドリングのベストプラクティスを知りたい
- さくっと動作確認できる環境を作りたい
という方は是非最後までご覧ください!
なおこの記事は後編になります。
前編はこちら ↓
はじめに
Webhook とは、Stripe 上で何かしらのイベントが発生した場合にイベントが飛ばされる仕組みです。
例えば「Stripe 上に Customer が作成された時」や、「サブスクリプションが作成された時」などのタイミングで、Stripe から指定したエンドポイントに対してイベントが発行されます。
これに基づいてアプリケーション側で処理を行う事で、イベントドリブンな機能の実装が行えます。
要件
- Stripe 上に Customer を作成できる API を実装する
- 受け付けるパラメータとしては、「名前」,「メールアドレス」とする
- 顧客作成時にサブスクリプションの契約は不要とする
- 顧客作成が完了したタイミングで、BlobStorage に顧客情報を保存する
- 保存する情報は「CustomerId」,「名前」,「メールアドレス」,「作成日時」とする
- {CustomerId}.json」をファイル名とする json 形式で保存する
# Azure 構成図
処理の流れとしては以下のような流れです。
- クライアントが WebApps にて実装した API に対して Customer 作成の要求を出します。
- API が Stripe の CreateCustomerAPI を実行し、Stripe 上に Customer を作成します。
- Stripe 上で Customer が作成されたタイミングで、customer.created の Webhook イベントがされます。
- イベントを受けた WebApps は ServiceBus の Queue に対して Enqueue します。
- Enqueue からのメッセージをトリガーとして、Functions が実行されます。
- Functions が StorageAccount に対してファイルを保存します。
なお今回の記事では 5.~6.を扱います。 1.~4.に関しては前編で紹介済みです。
リソース準備
Azure Functions
アプリケーション作成
ServiceBusからのメッセージをトリガーとして処理が行われるAzure Functionsのプロジェクトを作成していきます。
func init StripeWebhookFunction
Use the up/down arrow keys to select a worker runtime:dotnet
Use the up/down arrow keys to select a language:c#
cd StripeWebhookFunction
func new --name ServiceBusTriggerFunction
Use the up/down arrow keys to select a template:Function name: ServiceBusTriggerFunction
Azure Functions作成
次にAzure Functionsのリソースを作成していきます。
今回は以下の内容で作成します。
- .NET 6
- OS:Windows
- サーバーレスプラン
ApplicationSettingsの設定
ServiceBusTriggerを利用する場合、Azure Functionsの設定で1点注意する点があります。
こちらのドキュメントにもかかれているのですが、接続するServiceBusのNameSpaceをApplication Settingsに設定する必要があります。
Portal画面から以下の値を追加しましょう。
{
"ServiceBusConnection__fullyQualifiedNamespace" : "sbns-stripe-webhook.servicebus.windows.net"
}
Azure Blob Storage
次にBlob Storageを作っていきます。
ただこちらは特筆するべき点は特にないので、要件に合わせて作成してください。
手元の環境ではコストを抑えるために、Standard, LRSで作成してあります。
作成後、アップロード用のBlobコンテナを作成しておきます。
今回はcustomerというBlobコンテナを作成しました。
RBAC の設定
今回もリソース間のRBACの設定を行っていきます。
今回のシステムを実装するためには
- Azure Functionsがキューのメッセージを受信できること
- Azure FunctionsがStorageAccountに対して書き込みができること
が必要です。
前編のWebApps同様、Azure Functionsの SystemAssignedManagedIdentity の設定を行い、RBAC を設定します。
まずは Azure Functions のポータル画面へと遷移し、ID ブレードから設定してきます。
「状態」をオンにして「保存」を押すと、一意の ID が割り振られます。
では次に ServiceBus の画面へ行き、権限を割り当てます。
先ほどのドキュメントに合った通り、「Azure Service Bus のデータ受信者」の Role を追加します。
StorageAccountに対しても同様に設定を行っていきます。
Blobに対する書き込みが必要なので「ストレージ BLOB データ共同作成者」を追加します。
これで準備 OK なので、実装に移りましょう。
実装
それではキューへのメッセージ追加をトリガーとするアプリケーションを作成していきます。
先ほどサンプルアプリを作成したので、それをベースに改良していきます。
まず、こちらもStripeのパッケージをインストールしておきましょう。
dotnet add package Stripe.net --version 42.4.0
また、ServiceBusのドキュメントにもあるように
ManagedIdentityを使って接続を行うためにServiceBusのSDKをインストール&アップデートしておきます。
dotnet add package Microsoft.Azure.WebJobs.Extensions.ServiceBus --version 5.2.0
実装していく処理の内容としては
- メッセージを読み取り、StripeのEventの形にパース
- イベントごとに処理分岐
- 内容を読み取り、BlobStorageに保存
という流れです。
using System;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Storage.Blobs;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Stripe;
namespace StripeWebhookFunction
{
public class ServiceBusTriggerFunction
{
[FunctionName("ServiceBusTriggerFunction")]
public async Task Run([ServiceBusTrigger("sbq-stripewebhook", Connection = "ServiceBusConnection")] string message, ILogger log)
{
Event stripeEvent = EventUtility.ParseEvent(message);
switch (stripeEvent.Type)
{
case "customer.created":
Customer customer = stripeEvent.Data.Object as Customer;
BlobContainerClient blobContainerClient = new(new Uri("https://ststripewebhook.blob.core.windows.net/customer"), new DefaultAzureCredential());
BlobClient blobClient = blobContainerClient.GetBlobClient($"{customer.Id}.json");
CustomerDataModel model = new(customer.Created, customer.Id, customer.Name, customer.Email);
await blobClient.UploadAsync(BinaryData.FromString(Newtonsoft.Json.JsonConvert.SerializeObject(model)), true);
break;
default:
break;
}
}
public class CustomerDataModel
{
public DateTime Created { get; set; }
public string Id { get; set;}
public string Name { get; set; }
public string EMailAddress { get; set; }
public CustomerDataModel(DateTime created, string id, string name, string eMailAddress)
{
Created = created;
Id = id;
Name = name;
EMailAddress = eMailAddress;
}
}
}
}
Eventオブジェクトを生成した後に、各Objectでキャストすれば、いつものStripeオブジェクトが生成できますね。
一度オブジェクトIdが分かってしまえば、そこから再度Stripeに問い合わせたりと、後はやりたい放題です。
注意点としては、
ParseEventを行う際に、Webhookのイベントのバージョンと、StripeのSDKのバージョンの不一致があるとエラーが発生してしまいます。
(自分もさっき無意識でエンドポイントの設定していたら、このエラーに直面してました。:((
その際、以下のようにエラーメッセージにそれぞれのAPIバージョンの記載があると思うので、エラーメッセージを見て対応してください。
ServiceBusTriggerFunction Received event with API version 2022-08-01, but Stripe.net 42.4.0 expects API version 2023-08-16. We recommend that you create a WebhookEndpoint with this API version.
動作確認
ではこのアプリケーションをAzure Functionsにデプロイし、動作確認しましょう。
デプロイ完了後に、前回作成したWebAppsのCreateCustomerAPIを実行します。
POST https://app-stripewebhook.azurewebsites.net/api/customers
すると、BlobStorageにファイルが作成されていることが確認できました。
これで以下のステップを全て網羅することができ
今回の要件を満たすアプリケーションを作成することができました。
- クライアントが WebApps にて実装した API に対して Customer 作成の要求を出します。
- API が Stripe の CreateCustomerAPI を実行し、Stripe 上に Customer を作成します。
- Stripe 上で Customer が作成されたタイミングで、customer.created の Webhook イベントがされます。
- イベントを受けた WebApps は ServiceBus の Queue に対して Enqueue します。
- Enqueue からのメッセージをトリガーとして、Functions が実行されます。
- Functions が StorageAccount に対してファイルを保存します。
まとめ
今回は前後編に分けて、StripeのWebhookのハンドリングをAzure上で構築してみました。
Stripeとしての主なポイントとしては
- Webhook受信時のセキュリティ面を意識すること(署名, IPアドレス等)
- Webhookのイベントを受けたら即座に2xxを返して、その後の処理は他のアプリに任せること
- Webhookのイベントのバージョンと、APIのバージョンの整合性をとること
の3点くらいかなと思います。
あとはAzureのアーキテクチャの部分として
- Managed Identityを使ったリソース間連携
- ServiceBusTriggerのAzureFunctionsの実装
など注意して実装してみてください。
今回のものをベースとしてもらえれば、他のStripeイベントも問題なく扱えるかと思いますし
イベント発行を起点としてメールを送信したり、メッセージアプリに通知を飛ばしたりなど様々なことが出来ると思います。
Webhook、非常に便利なので使いこなしていきたいですね。
長々と呼んでいただきありがとうございました!
ではまた!