こんにちは、サイオステクノロジーの佐藤 陽です。
今回は、Stripe の Webhook イベントの処理に関する方法を紹介します。
- Webhookをトリガーとした想定外の処理が走ってしまう
- Event の内容に基づき、処理を細かく制御したい
という方は是非、最後までご覧下さい!
はじめに
Stripe では Webhook の機能が提供されています。
Webhook に関する基本的な説明は、公式ドキュメントを参照してください。
なお、今回は.NET の環境で実装していきます。
少し環境依存の部分もありますが、大きな流れはどれも似た感じかなと思います。
困りごと
現状の実装
はじめに、自分が直面した困りごとについて解説します。
早く実装内容を知りたい方は、ここはスキップして次の章へお進みください!
今回、アプリの機能として、Subscription Schedule の機能を使った実装をしています。
たとえばこんな感じで Subscription の Schedule を作成します。
- 最初、A の SubscriptionItem で Subscription を契約
- 次の Phase で B の SubscriptionItem に更新
- Phase 終了時に Release し、Subscription を継続
といった流れです。
この時、アプリの操作によって、以下のようにSubscriptionItemが更新されるような機能があるとします。(B->C)
処理内容は割愛しますが、UpdateSubscriptionScheduleAPI を利用すれば問題なく実装できるかと思います。
ここで本題ですが、
【 Schedule が更新されたタイミングで、何かしらのバッチ処理を走らせたい】
という要件があるとします。
StripeのWebhook には、subscription_schedule.updatedというイベントが用意されているので、これを使えば実現できそうですね。
早速 Webhook のエンドポイントを作成し、イベントを追加します。
curl https://api.stripe.com/v1/webhook_endpoints \
-H "Authorization: Bearer ${STRIPE_SECRET_KEY}" \
--data-urlencode "url"="https://stripe.sios.com" \
-d "description"="Webhook Endpoint" \
-d "enabled_events[]"="subscription_schedule.updated"
イベントキャッチ側の実装コードを抜粋すると、以下のような感じになります。
string message; // Webhookのメッセージ
Event stripeEvent = EventUtility.ParseEvent(message);
switch (stripeEvent.Type){
case "subscription_schedule.updated":
/****処理を行う ****/
}
これで準備が完了です。
アプリから UpdateSubscriptionScheduleAPI を実行し、Schedule の SubscriptionItem を B から C に更新します。
すると、狙い通り Webhook のイベントが飛び、バッチ処理が走りだしました。
めでたしめでたし!!
問題発生
さーて、次は Schedule を Release するため、テストクロックを進めてみるかー!
と思ったら、ScheduleがRelease されたタイミングでも Webhook のイベントが飛び、
バッチ処理が走り出しましてしまいました。予期せぬ事態です。
StripeのDashboard から Webhook のログを見てみると、確かにイベントが飛んでいました。
ただ、Update の API を実行した記憶はない…。
ということで少し調査をすると
どうやら、Release されたタイミングでも、SubscriptionSchedule の更新イベントが飛んでいるようです。
何が Update されたかというと、Schedule の Status がactiveからreleasedに変更されたようです。
納得です。
ただ、今回の要件を満たすためには
- Item が更新された時は処理を走らせる
- それ以外の場合は無視する
としたいですね。
この方法を次の章から説明していきます。
Webhook イベントを細かく制御する
それでは、Webhook イベントを制御し、必要なタイミングでのみ処理を走らせる方法を紹介していきます。
わざわざ書くまでの事でもないですが
- 現在のオブジェクトの値から判断
- 過去の値からの変更点から判断
の 2 つが想定されます。
現在のオブジェクトの値から判断
こちらは特段、説明することはないかと思います。
キャッチしたイベントは、各 Stripe の Object でキャスト可能です。
今回であれば、SubscriptionSchedule に関するイベントなので、以下のようにキャストします。
すると、SubscriptionSchedule の各プロパティにアクセスできるようになります。
あとは各値を見て、処理を走らせれば OK です。
string message; // Webhookのメッセージ
Event stripeEvent = EventUtility.ParseEvent(message);
SubscriptionSchedule subscriptionSchedule = stripeEvent.Data.Object as SubscriptionSchedule;
過去の値からの変更点から判断
こっちが今回の本題です。
現在の値だけではなく、過去の値からの変更点を見たいケースは往々にして存在します。
そんな時に役立つのが、【Previous Attributes】 というパラメータです。
Previous Attributes
Update 系の Webhook イベントには、Previous Attributes というパラメータが含まれています。
その名の通り、Update の前後での値の変化情報が含まれています。
痒い所に手が届く非常にありがたい機能ですね!
例えば、先ほどの Schedule が Release されたタイミングで飛んだイベントを確認してみます。
- 現在のstatus が released に変更になっている
- previous attributesの status が active になっている
ことが分かるので、
今回のEvent内容から、statusが active -> released に変更になったことが伺えます。
このように、飛んできたイベント名や、現在のObject値だけで判断するのではなく
previous attributesの値も確認することで、より細かな制御が行えそうです。
やってみる
Previous Attributes を利用した実装を行ってみたいと思います。
ただ、上のケースであげた「SubscriptionItem が更新された時」に限定すると、やや階層が深く、実装が複雑になるので
ここまで色々書いておいて申し訳ないですが
今回は代わりに「Schedule の Status」の Previous Attribute をターゲットにして実装してみます。mm
あとは各々、応用で試してみたください!
なお、最初にも書きましたが今回の私の環境が.NET です。
各言語で少し実装方法が異なってくる部分かなと思いますので、参考程度に。
SDK のコードを見ると、Previous Attributes は dynamic な型として定義されていました。
/// <summary>
/// Object containing the names of the attributes that have changed, and their previous
/// values (sent along only with *.updated events).
/// </summary>
[JsonProperty("previous_attributes")]
public dynamic PreviousAttributes { get; set; }
今回は JObject の型として Cast して利用していきます。
以下のように実装することで、現在の Status と過去の Status を取得することができます。
//イベントをParse
Event stripeEvent = EventUtility.ParseEvent(message);
switch (stripeEvent.Type){
case "subscription_schedule.updated":
SubscriptionSchedule subscriptionSchedule = stripeEvent.Data.Object as SubscriptionSchedule;
//現在のStatus取得
string currentStatus = subscriptionSchedule.Status;
//Update前のStatus取得
string previousStatus;
JObject previousAttributes = stripeEvent.Data.PreviousAttributes as JObject;
if (previousAttributes.TryGetValue("status", System.StringComparison.OrdinalIgnoreCase, out JToken status))
{
//Previous Attributeに含まれていれば更新
previousStatus = status.ToString();
}else{
//含まれていなかったら現在状態
previousStatus= subscriptionSchedule.Status;
}
}
まとめ
今回はPrevious Attributesを利用して、Webhook イベントを制御する方法を紹介しました。
Stripeから発行される[*.update]のイベントは発行される条件が広く、ただ単純にキャッチするだけでは予期せぬ処理が走ってしまいそうです。
Previous Attributesも含めてイベントの内容を判断し、必要なタイミングでのみ処理を走らせるようにしたいですね。
ではまた!