Loading...

2023-09-11(月) 15:00

💳 Stripe のイベントを処理する Webhook を Supaase Edge Function で作成する

SupabaseStripeNext.js
Stripe の決済情報を受け取るための Webhook を Supabase の Edge Function で作成する手順を紹介します。

目次

前提

この記事では以下を前提としています。

  • Supabase CLI をインストール済みであり、使い方を把握していること
  • Stripe アカウントを作成済みで Stripe 管理画面にアクセスできること
  • Supabase アカウントを作成済みであること

参考サイト

主に以下の記事を参考にしました。

Supabase stripe webhooks

Supabase stripe webhooks

github.com

この記事のゴール

この記事では、以下を目標としています。

  • Supabase の Edge Function で 作成した Webhook で Stripe のイベントを受け取る

動作確認方法として、Stripe の決済をcurlコマンドで実行し、実行した決済に関連するイベントが Supabase の Edge Function のログに出力されることを確認します。 ここでは、イベントを受け取るところまでとし、イベントを受け取ってイベントの内容に基づいて Supabase のデータベースのデータを更新したり、追加したりというところまでは扱いません。

作業内容

  1. Supabase Edge Function を作成する
  2. Stripe からのイベントを受け取るための内容に修正する
  3. Supabase Edge Function をデプロイする
  4. Stripe の管理画面で Webhook を登録する
  5. Edge Function 用の環境変数を設定する
  6. 動作確認する

Supabase Edge Function を作成する

Stripe のイベントを受け取って処理するための Webhook を Supabase Edge Function で作成します。 ここでは、my-projectというプロジェクト用のディレクトリがあり、その中に Edge Function を作ります。 なお、my-projectは例えば、Next.js や Nuxt.js のプロジェクトかもしれませんし、その他のフレームワークの中のプロジェクトかもしれません。 フレームワークの中でなくても、独立したディレクトリでももちろん OK です。

まず空の Edge Function を作成するために、以下のようにsupabase functions newコマンドを実行します。 ここでは、handle-stripe-eventという名前の Edge Function を作成します。

ターミナル
$ cd ~/my-project
$ supabase functions new handle-stripe-event
Created new Function at supabase/functions/handle-stripe-event

supabase functions newによってfunctionsのhandle-stripe-eventが作成されます。 .branchesや.gitignore、.temp、migrationsは、supabase start時など、初めて Supabase CLI のコマンドを実行した際に作成されます。

/my-project/supabase
.
├── .branches
│   └── _current_branch
├── .gitignore
├── .temp
│   └── import_maps
│       └── 392j9d39sofal2391f32308i42083j42083nsa40ak2d208td0283.json
├── config.toml
├── functions
│   ├── .vscode
│   │   ├── extensions.json
│   │   └── settings.json
│   └── handle-stripe-event
│       └── index.ts
├── migrations
└── seed.sql

functionsの中にあるhandle-stripe-eventのindex.tsが Edge Function の中身です。これはデフォルトですと以下のようになっています。 (ただし、Supabase は頻繁にアップデートされているため、デフォルトの中身も変わる可能性が高いです。)

handle-stripe-event/index.ts
// Follow this setup guide to integrate the Deno language server with your editor:
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
 
import { serve } from 'https://deno.land/[email protected]/http/server.ts';
 
console.log('Hello from Functions!');
 
serve(async (req) => {
  const { name } = await req.json();
  const data = {
    message: `Hello ${name}!`,
  };
 
  return new Response(JSON.stringify(data), {
    headers: { 'Content-Type': 'application/json' },
  });
});
 
// To invoke:
// curl -i --location --request POST 'http://localhost:54321/functions/v1/' \
//   --header 'Authorization: Bearer sej9jmi923mpg-924mpsj39j9ahs8nd9a8caibhngnkjnwe' \
//   --header 'Content-Type: application/json' \
//   --data '{"name":"Functions"}'

上記は、呼び出されるとHello from Functionsと出力して、reqに含まれるnameの値を使ってHello ${name}!というメッセージを返す処理になっています。 デフォルトの関数なのでこの中身を Stripe のイベントを受け取って処理できるように修正します。

Stripe からのイベントを受け取るための内容に修正する

デフォルトの状態の Edge Function を、Stripe からのイベント情報を受け取ってログに出力する内容に修正します。 参考にしたサンプルコードは以下の Supabase の公式リポジトリ内にあります。

Supabase stripe webhooks

Supabase stripe webhooks

github.com

ここでは、使用する Stripe のapiVersionやインポートする Stripe のパッケージ部分等を修正して、以下のようにしました。

stripe-webhooks/index.ts
// Follow this setup guide to integrate the Deno language server with your editor:
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
 
import { serve } from 'std/server'
import Stripe from 'https://esm.sh/[email protected]?target=deno';
 
const stripe = new Stripe(Deno.env.get('STRIPE_API_KEY') as string, {
  // This is needed to use the Fetch API rather than relying on the Node http
  // package.
  apiVersion: '2023-08-16',
  httpClient: Stripe.createFetchHttpClient(),
})
// This is needed in order to use the Web Crypto API in Deno.
const cryptoProvider = Stripe.createSubtleCryptoProvider()
 
console.log('Hello from Stripe Webhook!')
 
serve(async (request) => {
  const signature = request.headers.get('Stripe-Signature')
 
  // First step is to verify the event. The .text() method must be used as the
  // verification relies on the raw request body rather than the parsed JSON.
  const body = await request.text()
  let receivedEvent
  try {
    receivedEvent = await stripe.webhooks.constructEventAsync(
      body,
      signature!,
      Deno.env.get('STRIPE_WEBHOOK_SIGNING_SECRET')!,
      undefined,
      cryptoProvider
    )
  } catch (err) {
    return new Response(err.message, { status: 400 })
  }
  console.log(`🔔 Event received: ${receivedEvent.id}`)
  return new Response(JSON.stringify({ ok: true }), { status: 200 })
})

apiVersionの最新の値は、以下の Stripe 公式ドキュメント内で確認できます。

Stripe API バージョンを設定する

こちらのガイドラインに従って、Stripe の実装全体で API バージョンを統一します。

stripe.com

なお、上記のコード内でDeno.env.get('STRIPE_API_KEY')とDeno.env.get('STRIPE_WEBHOOK_SIGNING_SECRET')の2つの環境変数を使用しています。 これらの値は Stripe の管理画面にて取得します。取得方法と設定方法は後述します。 なお、設定には Supabase CLI を使用します。

次に Supabase CLI を使って Edge Function をデプロイします。

Supabase Edge Function をデプロイする

作成した Edge Function handle-stripe-eventを 以下のコマンドで Supabase にデプロイします。

/my-project/supabase
$ supabase functions deploy --no-verify-jwt handle-stripe-event

ここでオプションとして指定している--no-verify-jwtは、Stripe 側からこの Edge Function にアクセスできるようにするために、Supabase 側でアクセス元の認証をしないための指定になります。 これだけだとどこからでもこの Edge Function を呼び出せてしまいセキュリティリスクが高いです。 そのため、上記の Edge Function の中では、 Stripe 公式ドキュメントに記載されている通りにアクセス元が Stripe からであるか否かのチェックを 20 行目、29 行目部分で行っています。 もし外部から呼び出せる必要がない場合は、--no-verify-jwtを指定する必要はありません。 なお、--no-verify-jwtの使用は Supabase の公式リポジトリにあるサンプルコードにも含まれています。

README.md

Supabase stripe webhooks

github.com

デプロイが完了すると以下のように表示されます。

/my-project/supabase
$ supabase functions deploy handle-stripe-event
You can find your project ref from the project's dashboard home page, e.g. https://app.supabase.com/project/<project-ref>.
Enter your project ref: iqjduehnshcuentoie
Version 1.30.3 is already installed
Bundling handle-stripe-event
Deploying handle-stripe-event (script size: 400.8kB)
Deployed Function handle-stripe-event on project iqjduehnshcuentoie
You can inspect your deployment in the Dashboard: https://app.supabase.com/project/iqjduehnshcuentoie/functions/handle-stripe-event/details

Edge Function のデプロイに成功すると、Supabase の管理画面でも確認できます。

Supabase Edge Function

上記に表示されている URL を Stripe の Webhook として Stripe の管理画面から登録します。

Supabase プロジェクトの Reference ID の確認方法

もし以下のように表示された場合は、デプロイ先となるプロジェクトの Project Reference ID を入力して Enter を押下してください。

/my-project/supabase
$ supabase functions deploy handle-stripe-event
You can find your project ref from the project's dashboard home page, e.g. https://app.supabase.com/project/<project-ref>.
Enter your project ref:

Supabase の Project Reference ID は、以下のように Supabase の管理画面の中のサイドバー下部の歯車マークから「Settings」ページに遷移し、「General」の中にある「Reference ID」が該当します。

Supabase Project Reference ID

Stripe の管理画面で Webhook を登録する

以下のように、Stripe の管理画面の上部にある「開発者」ページを開き、その中の「Webhook」タブを開きます。まだ Webhook を作成したことがない場合は、以下のように表示されていると思います。 なお、まずはテスト環境で試したい場合は、右上にある「テスト環境」をオンにしてください。以下のような見た目になります。

Stripe Dashboard

上記で「エンドポイントを追加」ボタンをクリックします。 以下のように、エンドポイントの URL 入力フォームや、サンプルまで表示してくれます。

Stripe Webhook

上記の「エンドポイント URL」には、デプロイした Edge Function の URL(https://iqjduehnshcuentoie.supabase.co/functions/v1/stripe-event-handlerのような URL) を入力します。 また、ここで受け取るイベントの種類を選択できます。
上記画像内にある「リッスンするイベントの選択」項目の「+イベントを選択」をクリックすると、エンドポイント(ここではデプロイした Edge Function )で受け取りたいイベントを選択する画面が表示されます。

Stripe Webhook

イベントは項目毎に分かれており、試しに一番上にあるAccountをクリックして展開してみると、Accountに関連する4つのイベントを選択できることがわかります。 「全ての Account イベントを選択する」にチェックを入れると、以下のように右側に表示されているサンプルコードも自動で更新してくれます。

Stripe Webhook

ここでは、決済関係の通知を受け取りたいため、CheckoutとPayment Intentの全てのイベントを選択します。(以下の画像ではCheckoutのみ選択しています。)

Stripe Webhook

選択した状態で「イベントを追加」ボタンをクリックします。 以下のように選択したイベントが表示されるので、問題なければ再度「イベントを追加」ボタンをクリックします。

added-checkout-and-payment-intent-events

すると、Webhook が作成されて以下のように表示されます。

Stripe Webhook

上記で、「署名シークレット」という項目に「表示」というリンクがあるので、これをクリックします。表示された値を Supabase Edge Function のSTRIPE_WEBHOOK_SIGNING_SECRETで使用します。

また、STRIPE_API_KEYは以下のように「開発者」ページの「API キー」タブの中にあるシークレットキーを使用します。公開可能キーではありませんので注意が必要です。

Stripe Webhook

なお、上記の画像ではテスト環境の表示になっているため「テストキーを表示」というボタンがありますが、本番環境の場合は「本番キーを表示」というボタンが表示され、一度だけ表示できます。 後からダッシュボードで再確認ができないため、表示されたシークレットキーをどこかにメモしておく必要があります。 もし紛失した場合は、シークレットキーを削除して新たに作成できます。

Edge Function 用の環境変数を設定する

Edge Function で使用する 上記のコードの中では、以下の2つの環境変数を使用しています。

  • STRIPE_API_KEY
  • STRIPE_WEBHOOK_SIGNING_SECRET

Supabase の Edge Function の中で使用する環境変数は、Supabase CLI のsupabase secrets set コマンドを使用して設定します。 具体的には、以下を実行することで環境変数を設定できます。

/my-project/supabase
$ supabase secrets set STRIPE_API_KEY=sk_test_skwie82l39mxnjskihxbfgh397dh37fh38dfhcfhvjyqg3474jcv8hdfjkjhchy2839ujks
$ supabase secrets set STRIPE_WEBHOOK_SIGNING_SECRET=whsec_22jd8jx38djdjdhwqoyr83i8ghq083hflhsbfoug082bouf8d

上記を見てわかるとおり、以下のようにして使用できます。

ターミナル
$ supabase secrets set 環境変数名=値

環境変数の設定が成功すると以下のようなレスポンスが返ってきます。

/my-project/supabase
$ supabase secrets set STRIPE_API_KEY=sk_test_skwie82l39mxnjskihxbfgh397dh37fh38dfhcfhvjyqg3474jcv8hdfjkjhchy2839ujks
You can find your project ref from the project's dashboard home page, e.g. https://app.supabase.com/project/<project-ref>.
Enter your project ref: iqjduehnshcuentoie
Finished supabase secrets set.
 
$ supabase secrets set STRIPE_WEBHOOK_SIGNING_SECRET=whsec_22jd8jx38djdjdhwqoyr83i8ghq083hflhsbfoug082bouf8d
You can find your project ref from the project's dashboard home page, e.g. https://app.supabase.com/project/<project-ref>.
Enter your project ref: iqjduehnshcuentoie
Finished supabase secrets set.

設定済みの環境変数一覧を確認するには、supabase secrets listを使用します。

/my-project/supabase
$ supabase secrets list

なお、supabase secrets setやsupabase secrets listを実行した時に以下のように表示される場合は、まず環境変数を設定したい Supabase のプロジェクトの Reference ID を指定する必要があります。(Reference ID の確認方法はこちら )

/my-project/supabase
$ supabase secrets list
You can find your project ref from the project's dashboard home page, e.g. https://app.supabase.com/project/<project-ref>.
Enter your project ref:

具体的には、上記のように表示された時に、Enter your project ref:に続けてプロジェクトの ID を貼り付けて Enter を押下します。 すると、以下のように一覧が表示されます。

/my-project/supabase
$ supabase secrets list
You can find your project ref from the project's dashboard home page, e.g. https://app.supabase.com/project/<project-ref>.
Enter your project ref: iqjduehnshcuentoie
 
 
               NAME              │              DIGEST
  ────────────────────────────────┼───────────────────────────────────
    STRIPE_API_KEY                │ 38dh588sk39hsfl38sf082j07jgay2o3
    STRIPE_WEBHOOK_SIGNING_SECRET │ 297dnlfjas97311nkfds97sanle98h3o

なお、Edge Function をデプロイ後に環境変数を新しく設定したり変更したりした場合は、その後に再度デプロイしないと変更内容が反映されないので注意が必要です。 以下のように再度デプロイしてください。

/my-project/supabase
$ supabase functions deploy --no-verify-jwt handle-stripe-event

動作確認する

動作確認を行うためにcurlコマンドを使って Stripe に決済情報を送信します。 例えば、以下を実行すると、Stripe にクレジットカードを使った決済を実行できます。 以下は、クレジットカードで日本円 1,099 円の決済を実行する例です。

curlを使ったPayment Intentの作成
curl https://api.stripe.com/v1/payment_intents \
  -u "sk_test_skwie82l39mxnjskihxbfgh397dh37fh38dfhcfhvjyqg3474jcv8hdfjkjhchy2839ujks:" \
  -d amount=1099 \
  -d currency=jpy \
  -d "payment_method_types[]"=card

上記が成功すると、以下のようなレスポンスが返ってきます。

Stripeからの応答内容
{
  "id": "pi_3NozUVEnJYjxbzcQ06i9TNVH",
  "object": "payment_intent",
  "amount": 1099,
  "amount_capturable": 0,
  "amount_details": {
    "tip": {}
  },
  "amount_received": 0,
  "application": null,
  "application_fee_amount": null,
  "automatic_payment_methods": null,
  "canceled_at": null,
  "cancellation_reason": null,
  "capture_method": "automatic",
  "client_secret": "pi_3skdj2082jifwsfd8024h08gaj08fj028j0a8efj0a8d",
  "confirmation_method": "automatic",
  "created": 1694397883,
  "currency": "jpy",
  "customer": null,
  "description": null,
  "invoice": null,
  "last_payment_error": null,
  "latest_charge": null,
  "livemode": false,
  "metadata": {},
  "next_action": null,
  "on_behalf_of": null,
  "payment_method": null,
  "payment_method_options": {
    "card": {
      "installments": null,
      "mandate_options": null,
      "network": null,
      "request_three_d_secure": "automatic"
    }
  },
  "payment_method_types": [
    "card"
  ],
  "processing": null,
  "receipt_email": null,
  "review": null,
  "setup_future_usage": null,
  "shipping": null,
  "source": null,
  "statement_descriptor": null,
  "statement_descriptor_suffix": null,
  "status": "requires_payment_method",
  "transfer_data": null,
  "transfer_group": null
}

Stripe の Webhook のログを確認する

上記のログは、Stripe のダッシュボードからも確認できます。具体的には、以下のように開発者ページの中の「Webhook」タブの中にある、作成した Webhook の URL をクリックします。

Stripe Webhook

すると、以下のように Stripe からのイベントのログを確認できます。

Stripe Webhook

上記で確認したログと同じ内容が Supabase の Edge Function でも表示されていることを確認します。

Supabase Edge Function のログを確認する

Supabase にログインして、管理画面のサイドバーにある Edge Function メニューをクリックして Edge Function 一覧ページに遷移します。

Supabase Edge Functions 一覧

そこで Stripe の Webhook として登録した Edge Function をクリックして Edge Function の詳細ページにアクセスします。そこにある「Logs」を開いてみると、 Edge Function のログとして、Stripe から受け取ったイベント情報が表示されるはずです。

すると、以下のように Stripe からのイベントのログを確認できます。

Supabase Edge Function Logs

なお、上記で実行したcurlコマンドは、以下の Stripe 公式ドキュメントに記載のものです。以下のドキュメントには、他の処理についての実行方法や色々な言語での実行方法などが詳しく記載されています。 このドキュメントを見ながら色々と試してみると良いと思います。

Payment Intents API

PaymentIntent を作成する

stripe.com

まとめ

Stripe の決済情報に関連するイベントを Supabase の Edge Function で受け取る例をまとめました。この記事ではcurlコマンドによる動作確認を行っただけですが、実際にはブラウザ上で決済用ボタンを用意し、そこから Stripe の決済ページへ遷移して実際に決済するような形で実装することになると思います。