3
2

More than 1 year has passed since last update.

StripeのWebhook処理にAzure Functionsを使ってみる

Last updated at Posted at 2021-12-26

はじめに

最近、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というファイルが入ってますのでそちらを書き換えて下さい)

  • Stripeのシークレットキー

まずはテスト環境のシークレットキーを使用します。ダッシュボードから取得できます。

Image from Gyazo

  • Webhookシークレット

Webhookシークレットはローカル用の値を取得します。↓のstripeコマンドを打つとローカル環境で使う用の webhookシークレットが払い出されますのでそちらを使用します。

 stripe listen

Image from Gyazo

4. ローカル上で Azure Functions を実行

 func start

Image from Gyazo
Azure Functions の関数が起動し、localhostで検証が行えるようになります。

5. Stripeイベントをlocalhostで受け取る

次に Stripe CLI を使用してローカルでイベントを傍受します。

新しくターミナルを立ち上げて、以下のコマンドを実行します。--forward-to に先ほどのエンドポイントを指定することで、Stripeイベントがフォワーディングされるようになります。

 stripe listen --forward-to localhost:7071/api/webhook

Image from Gyazo
参照: Send real-time Stripe events to your server | Stripe のドキュメント

6. Stripe のイベントを発火する

ローカルアプリでWebhookを受け取れるかテストをしてみましょう。Stripe イベントは CLI から簡単に発火させることができます。

さらに別のターミナルを起動して、コマンドを実行します。

stripe trigger customer.created

Image from Gyazo
参照: Trigger Stripe events to test a webhooks integration | Stripe のドキュメント

7. Azure Functions のログを確認

Azure Functions を起動しているターミナルでログを確認してみると、、

Image from Gyazo

Stripeイベント customer.created の受信に成功していることが分かります。

問題なく動いていることが確認できたので、ローカルでの検証はここまでとします。これまでに起動したターミナルはすべて終了して構いません。

次はこの関数アプリを Azure 上にデプロイしていきます。

Azure上にリソースを作成

1. 関数アプリを新しく作成

Azureの管理コンソールを開いて、新しく関数アプリを作成します。先ほど作成した関数アプリを乗せる器を用意するイメージです。

Image from Gyazo

左端のペインから「関数アプリ」を選択して、新しくリソースを作成します。

2.「基本」

使用するサブスクリプションとリソースグループ、関数アプリ名、ランタイムスタック、バージョン、地域をそれぞれ入力します。

Image from Gyazo

今回のアプリは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.「ホスティング」

Image from Gyazo
オペレーティングシステムはWindowsでもLinuxでもどちらでもokです。

4.「ネットワーク」

Image from Gyazo

5.「監視」

Image from Gyazo
必要に応じて有効・無効を切り替えて下さい。

6.「タグ」

Image from Gyazo
必要に応じてタグを設定して下さい。

7.「確認および作成」

img
内容を確認して、「作成」します。

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となります。

Image from Gyazo

2. StripeにWebhookエンドポイントを追加

StripeのWebhook一覧画面からエンドポイントを追加します。エンドポイントURLとリッスンするイベントを選択して「イベントを追加」します。
Image from Gyazo
Image from Gyazo

3. Webhook シークレットキーを取得

署名シークレットをメモしておきます。あとで使います。
Image from Gyazo

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行目をコメントアウトします。

index.js
// require('dotenv').config(); // Remove it when runs on prod

2. アプリケーション設定を追加

先ほど作成した関数アプリのコンソール画面から、アプリケーション設定(環境変数)を追加します。
Image from Gyazo

設定に「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」というファイル名で保存します。
Image from Gyazo

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!」とレスポンスが返ってくれば成功です。
Image from Gyazo
Image from Gyazo

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で簡単に実装することができます。
Image from Gyazo
⇩⇩⇩
Image from Gyazo

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以降のイベント処理を冪等(べきとう)にします。
Image from Gyazo

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の項目には作成したキューの名前を入れます。

webhook/function.json
{
  "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へメッセージ送信するようにしてみます。編集したら再度デプロイします。

webhook/index.js(一部抜粋)
// 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に内容を通知する簡単なフローです。
Image from Gyazo

7. Stripeに顧客を追加

コンソールから新しい顧客を追加して、フローが起動するか確認します。
Image from Gyazo

8. 実行結果

無事、Teamsにメッセージが届きました!フローを編集すればOutlookやDataverseなど他のサービスにも連携できそうです!
Image from Gyazo

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

コマンド実行後に再度コンソール画面を確認するとアクセス制限が適用されているはずです。
Image from Gyazo
Image from Gyazo

2. アクセスしてみる

試しにブラウザからアクセスしようとするとしっかりと拒否されました。設定が効いているようです。
Image from Gyazo

アクセス制限を設定すると、自分のPCからデプロイもできなくなる のですべての機能を実装した後に行ってください。

StripeのWebhookサーバーが追加された場合はアクセス制限を変更する必要があります。最新情報を追うために公式のメーリングリストの購読をおすすめします。 Stripe API Announcements - Googleグループ

コールドスタートに注意!

Azure Functions の従量課金プランを使用する場合、最後のリクエストから20分を経過するとインスタンス数が 0 にスケールダウンしてしまうため次回要求への応答が数秒遅れる場合があります。このとき、Stripe側ではレスポンスエラーと判断され、数分後に再度Webhookリクエストが送られてきます。
image.png

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はその思いを強力にサポートしてくれます。

最後まで読んでいただき、ありがとうございました。この記事を読まれた方の開発の足掛かりになれば幸いです。

3
2
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
3
2