はじめに
最近、StripeのWebhookをAzure Functionsで設置する機会がありましたので、その時の手順をまとめたいと思います。
いつもはExpressでAPIサーバーを立てたり、AWS Lambda + API Gateway で処理したりしていたのですが、Azure Functionsがとても便利だったのでご紹介をかねて記事にしました。
開発環境
必要なものは以下の通りです。CLIはそれぞれ最新バージョンを使用して下さい。
- Node.js (v14.17.0)
- Stripe CLI
- Azure CLI
- Azure Functions Core Tools(Azure Functionsをローカルで実行するために使います)
ローカルで関数アプリを作成する
1. プロジェクトをダウンロードする
Node.js用のひな形が私のGitHubに入ってますのでそちらをご活用下さい。
プロジェクトをダウンロード
2. 必要なパッケージをインストール
package.json
がある階層に移動して、プロジェクトに必要なパッケージを集めます。
npm install
3. envファイルを作成
ご自身の環境に .env ファイルを作成します。(.env.sampleというファイルが入ってますのでそちらを書き換えて下さい)
Webhookシークレットはローカル用の値を取得します。↓のstripeコマンドを打つとローカル環境で使う用の webhookシークレットが払い出されますのでそちらを使用します。
stripe listen
4. ローカル上で Azure Functions を実行
func start
Azure Functions の関数が起動し、localhostで検証が行えるようになります。
5. Stripeイベントをlocalhostで受け取る
次に Stripe CLI を使用してローカルでイベントを傍受します。
新しくターミナルを立ち上げて、以下のコマンドを実行します。--forward-to
に先ほどのエンドポイントを指定することで、Stripeイベントがフォワーディングされるようになります。
stripe listen --forward-to localhost:7071/api/webhook
参照: Send real-time Stripe events to your server | Stripe のドキュメント
6. Stripe のイベントを発火する
ローカルアプリでWebhookを受け取れるかテストをしてみましょう。Stripe イベントは CLI から簡単に発火させることができます。
さらに別のターミナルを起動して、コマンドを実行します。
stripe trigger customer.created
参照: Trigger Stripe events to test a webhooks integration | Stripe のドキュメント
7. Azure Functions のログを確認
Azure Functions を起動しているターミナルでログを確認してみると、、
Stripeイベント customer.created
の受信に成功していることが分かります。
問題なく動いていることが確認できたので、ローカルでの検証はここまでとします。これまでに起動したターミナルはすべて終了して構いません。
次はこの関数アプリを Azure 上にデプロイしていきます。
Azure上にリソースを作成
1. 関数アプリを新しく作成
Azureの管理コンソールを開いて、新しく関数アプリを作成します。先ほど作成した関数アプリを乗せる器を用意するイメージです。
左端のペインから「関数アプリ」を選択して、新しくリソースを作成します。
2.「基本」
使用するサブスクリプションとリソースグループ、関数アプリ名、ランタイムスタック、バージョン、地域をそれぞれ入力します。
今回のアプリはStripeからのWebhookのみを受け取るので、StripeのWebhook送信サーバーを起点にリージョンを選びます。
Webhookを送信するIPアドレス一覧 から、それぞれのサーバーの位置情報を調べてみました。「アメリカ中心、一部インド」という地理構成のようです。
したがって、今回はCentral US を選択するのが良さそうです。
IP address | Country | States | City |
---|---|---|---|
3.18.12.63 | United States of America | Ohio | Columbus |
3.130.192.231 | United States of America | Ohio | Columbus |
13.235.14.237 | India | Maharashtra | Mumbai |
13.235.122.149 | India | Maharashtra | Mumbai |
18.211.135.69 | United States of America | Virginia | Ashburn |
35.154.171.200 | India | Maharashtra | Mumbai |
52.15.183.38 | United States of America | Ohio | Columbus |
54.88.130.119 | United States of America | Virginia | Ashburn |
54.88.130.237 | United States of America | Virginia | Ashburn |
54.187.174.169 | United States of America | Oregon | Portland |
54.187.205.235 | United States of America | Oregon | Portland |
54.187.216.72 | United States of America | Oregon | Portland |
参照: ドメインと IP アドレス | Stripe のドキュメント
3.「ホスティング」
オペレーティングシステムはWindowsでもLinuxでもどちらでもokです。
4.「ネットワーク」
5.「監視」
6.「タグ」
7.「確認および作成」
8. CLIを使った関数アプリの作成
az functionapp create --name <関数アプリ名>
--resource-group <リソースグループ名>
--storage-account <ストレージアカウント名>
--consumption-plan-location centralus
--functions-version 3
--disable-app-insights true
--os-type Windows
--runtime node
--runtime-version 14
参照: az functionapp | Microsoft Docs
Webhookエンドポイントを登録
1. Azure 上のエンドポイントURLを取得
後続で必要になるので、先にStripeにWebhook エンドポイントを追加します。
https://<関数アプリ名>.azurewebsites.net/api/webhook
がエンドポイントURLとなります。
2. StripeにWebhookエンドポイントを追加
StripeのWebhook一覧画面からエンドポイントを追加します。エンドポイントURLとリッスンするイベントを選択して「イベントを追加」します。
3. Webhook シークレットキーを取得
4. Stripe CLIを使ったエンドポイント追加
stripe webhook_endpoints create --url "https://<関数アプリ名>.azurewebsites.net/api/webhook" --data "enabled_events[]=customer.created"
参照: Stripe API Reference - Create a webhook endpoint
関数アプリに環境変数を設定
1. .envファイルを削除
.envファイルはローカル環境用なので、こちらは削除して下さい。
また、webhook/index.js
の1行目をコメントアウトします。
// require('dotenv').config(); // Remove it when runs on prod
2. アプリケーション設定を追加
先ほど作成した関数アプリのコンソール画面から、アプリケーション設定(環境変数)を追加します。
設定に「STRIPE_SECRET_KEY」と「STRIPE_WEBHOOK_SECRET」を追加します。追加後に「保存」を押すのをお忘れなく。
Key | Value |
---|---|
STRIPE_SECRET_KEY | < テスト環境のAPIキー> |
STRIPE_WEBHOOK_SECRET | < Webhookの署名シークレット> |
3. CLIを使ったアプリケーション設定の追加
az functionapp config appsettings set --name <関数アプリ名> --resource-group <リソースグループ名> --settings STRIPE_SECRET_KEY=<APIキー>
az functionapp config appsettings set --name <関数アプリ名> --resource-group <リソースグループ名> --settings STRIPE_WEBHOOK_SECRET=<Webhookの署名シークレット>
参照: Azure Functions で Function App の設定を構成する | Microsoft Docs
デプロイ
1. プロジェクトをzipファイル化
デプロイ方法は色々ありますが、今回はzipにして Azure CLI でデプロイしていきます。
まず、プロジェクト全体を圧縮して「app.zip」というファイル名で保存します。
2. デプロイする
次に Azure CLI を使ってデプロイします。少し時間がかかります。
az functionapp deployment source config-zip --name <関数アプリ名> --resource-group <リソースグループ名> --src app.zip
参照: az functionapp deployment source | Microsoft Docs
3. イベントを発火してテストしてみる
デプロイが完了したら Stripeのダッシュボード からテストイベントを送ってみます。Azure上のWebhookエンドポイントに customer.created
イベントを送信します。
エンドポイントから 「Customer created!」とレスポンスが返ってくれば成功です。
4. そのほかのデプロイ方法
エディタにVSCodeを使っている方は、拡張機能を使うと簡単にデプロイできます。おすすめです。
参照:
・ Azure Functions のデプロイ テクノロジ | Microsoft Docs
・ Visual Studio Code を使用して JavaScript 関数を作成する - Azure Functions | Microsoft Docs
Power Automateで各イベントを処理する
イベントの処理をコードで実装することもできますが、Azure Service Bus などのメッセージングサービスを使うと、Power Automate や Logic Apps のようなノーコードのサービスに連携することも可能です。
CheckoutやPayment Linksなど支払いのインターフェース部分はノーコードのソリューションがありますが、裏側の処理はある程度コーディングが必要になります。そこでPower AutomateやLogic Appsを活用して、更にコード量を減らせないか試してみました。
決済後は「顧客にメール送信する」、「受注データを登録する」といったアクションが考えられますが、これらはPower Automateで簡単に実装することができます。
⇩⇩⇩
1. Service Bus キューを作成
Service BusはAzure上のクラウドメッセージングサービスです。サービスの概要やリソースの作成方法はドキュメントをご参照下さい。
Azure portal を使用して Service Bus キューを作成する - Azure Service Bus | Microsoft Docs
2. キューの重複メッセージ検出を有効にする
Stripeのドキュメントによると、Webhook エンドポイントから同じイベントが複数回送られてくる可能性があるようです。
したがって、それらを処理するアクションが二重で行われないよう(or 二重で行われてもデータ整合性がとれるよう)アプリケーション側で対策が必要になります。
参照: Webhook 使用のベストプラクティス | Stripe のドキュメント
Service Busにはメッセージ重複を排除するオプションがありますので、キューを作成する際はそちらを有効にします。これにより、Service Bus以降のイベント処理を冪等(べきとう)にします。
Service Bus の Standard と Premium レベルで重複検出オプションを設定可能です。Basic レベルでは、重複検出はサポートされませんのでご注意ください。
料金 - Service Bus | Microsoft Azure
3. 接続文字列を環境変数に追加
アプリケーション設定に「SB_CONNECTION_STRING」を追加します。
Key | Value |
---|---|
SB_CONNECTION_STRING | < 名前空間への接続文字列 > |
az functionapp config appsettings set --name <関数アプリ名> --resource-group <リソースグループ名> --settings SB_CONNECTION_STRING=<接続文字列>
4. function.jsonを変更
function.json
をいじって、この関数がService Busへアウトプットがあることを明記します。queueNameの項目には作成したキューの名前を入れます。
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
},
+ {
+ "type": "serviceBus",
+ "queueName": "<キューの名前>",
+ "connection": "SB_CONNECTION_STRING",
+ "direction": "out",
+ "name": "queue"
+ }
]
}
5. index.jsを編集する
customer.created
イベントを受け取ったときにService Busへメッセージ送信するようにしてみます。編集したら再度デプロイします。
// Handle each events from here
if (eventType === 'customer.created') {
+ context.bindings.queue = {
+ body: data,
+ label: eventType,
+ };
context.res = {
status: 200,
body: 'customer created!'
};
}
参照: Azure Functions における Azure Service Bus の出力バインド | Microsoft Docs
6. Power Automateにフローを作成
メッセージを処理するフローをPower Automateに作成します。Teamsに内容を通知する簡単なフローです。
7. Stripeに顧客を追加
コンソールから新しい顧客を追加して、フローが起動するか確認します。
8. 実行結果
無事、Teamsにメッセージが届きました!フローを編集すればOutlookやDataverseなど他のサービスにも連携できそうです!
IPアドレスを制限してアプリケーションを保護する
1. アクセス制限を設定
最後に、より安全に利用するためにStripeのWebhookサーバー以外からのリクエストを拒否する設定をしていきます。
前述のように Webhookを送信するIPアドレスの一覧 は公開されていますので、これらのIPアドレスのみアクセスを許可して他はすべて拒否するよう設定します。
以下のコマンドを流してアクセス制限の設定を行います。コンソール画面からもできますが CLIのほうが楽です。
az functionapp config access-restriction add --resource-group <リソースグループ名> --name <関数アプリ名> --rule-name ipAddress1 --ip-address 3.18.12.63/32 --priority 100 --action Allow
az functionapp config access-restriction add --resource-group <リソースグループ名> --name <関数アプリ名> --rule-name ipAddress2 --ip-address 3.130.192.231/32 --priority 101 --action Allow
az functionapp config access-restriction add --resource-group <リソースグループ名> --name <関数アプリ名> --rule-name ipAddress3 --ip-address 13.235.14.237/32 --priority 102 --action Allow
az functionapp config access-restriction add --resource-group <リソースグループ名> --name <関数アプリ名> --rule-name ipAddress4 --ip-address 13.235.122.149/32 --priority 103 --action Allow
az functionapp config access-restriction add --resource-group <リソースグループ名> --name <関数アプリ名> --rule-name ipAddress5 --ip-address 18.211.135.69/32 --priority 104 --action Allow
az functionapp config access-restriction add --resource-group <リソースグループ名> --name <関数アプリ名> --rule-name ipAddress6 --ip-address 35.154.171.200/32 --priority 105 --action Allow
az functionapp config access-restriction add --resource-group <リソースグループ名> --name <関数アプリ名> --rule-name ipAddress7 --ip-address 52.15.183.38/32 --priority 106 --action Allow
az functionapp config access-restriction add --resource-group <リソースグループ名> --name <関数アプリ名> --rule-name ipAddress8 --ip-address 54.88.130.119/32 --priority 107 --action Allow
az functionapp config access-restriction add --resource-group <リソースグループ名> --name <関数アプリ名> --rule-name ipAddress9 --ip-address 54.88.130.237/32 --priority 108 --action Allow
az functionapp config access-restriction add --resource-group <リソースグループ名> --name <関数アプリ名> --rule-name ipAddress10 --ip-address 54.187.174.169/32 --priority 109 --action Allow
az functionapp config access-restriction add --resource-group <リソースグループ名> --name <関数アプリ名> --rule-name ipAddress11 --ip-address 54.187.205.235/32 --priority 110 --action Allow
az functionapp config access-restriction add --resource-group <リソースグループ名> --name <関数アプリ名> --rule-name ipAddress12 --ip-address 54.187.216.72/32 --priority 111 --action Allow
コマンド実行後に再度コンソール画面を確認するとアクセス制限が適用されているはずです。
2. アクセスしてみる
試しにブラウザからアクセスしようとするとしっかりと拒否されました。設定が効いているようです。
アクセス制限を設定すると、自分のPCからデプロイもできなくなる のですべての機能を実装した後に行ってください。
StripeのWebhookサーバーが追加された場合はアクセス制限を変更する必要があります。最新情報を追うために公式のメーリングリストの購読をおすすめします。
Stripe API Announcements - Googleグループ
コールドスタートに注意!
Azure Functions の従量課金プランを使用する場合、最後のリクエストから20分を経過するとインスタンス数が 0 にスケールダウンしてしまうため次回要求への応答が数秒遅れる場合があります。このとき、Stripe側ではレスポンスエラーと判断され、数分後に再度Webhookリクエストが送られてきます。
Service Busで重複メッセージは排除していますが、レスポンスエラーが気になる方は以下の対策が可能です。
-
課金する
常時稼働の App Service プランか、Premium プランにアップグレードすることで解決できます。しかし、「使った分だけ」のサーバーレスの利点はつぶれてしまいます。 -
一定時間毎にリクエストを送ってコールドスタートを回避する
私は15分ごとに関数アプリのURLにGETリクエストを送ってインスタンス数が 0 にならないように維持しています。
参照:Understanding serverless cold start | Microsoft Azure
ソースコードについて
https://github.com/kota-imai/stripe-webhook-azfunc
ご自由に使って頂いて構いません。もし間違いなどがありましたらそっと教えて頂けると幸いです。
まとめ
とても長くなってしまいました。。最後まで読んで下さった方はいるのでしょうか笑
当たり前のことですが、決済サービスが絡む =人様のお金を扱うということですのでセキュリティには十二分に気を付けたいところですね。そんな中でStripeとAzure Functionsはその思いを強力にサポートしてくれます。
最後まで読んでいただき、ありがとうございました。この記事を読まれた方の開発の足掛かりになれば幸いです。