Cloudflare WorkersはV8ベースのランタイムで動作しています。
そのため、npmに公開されているライブラリの中には、Cloudflare Workersで動作しないものも存在します。
stripe-nodeはCloudflare Workersでも利用可能
StripeのNode.js向けラリブラリであるstripe-node
は、2023年4月にCloudflare Workersへの対応を完了しました。
Stripe Webhookを処理するAPIも、Cloudflare Workersを利用して構築できます。
この記事では、Cloudflare WorkersでStripeのWebhookイベントを検証する方法を紹介します。
HonoでAPIを作成する
Cloudflare WorkersでAPIを作成するには、Honoを利用すると便利です。
npm create
でアプリをセットアップします。
$ npm create hono hono-cfw
どのサービスにデプロイするかを尋ねられます。
cloudflare-workers
を選びましょう。
? Which template do you want to use? › - Use arrow-keys. Return to submit.
aws-lambda
bun
cloudflare-pages
❯ cloudflare-workers
deno
fastly
lagon
lambda-edge
netlify
↓ nextjs
テンプレートからファイルが生成されます。
ディレクトリに移動して、ライブラリをインストールしましょう。
% cd hono-cfw
% npm install
最後にWranglerでローカル環境にてAPIを立ち上げましょう。
% npm run dev
warning package.json: No license field
$ wrangler dev src/index.ts
⛅️ wrangler 3.5.1
------------------
wrangler dev now uses local mode by default, powered by 🔥 Miniflare and 👷 workerd.
To run an edge preview session for your Worker, use wrangler dev --remote
⎔ Starting local server...
[mf:inf] Ready on http://127.0.0.1:65373/
cURLでAPIが動作することを確認しましょう。
% curl http://127.0.0.1:65373/
Hello Hono
Honoで、Webhook用のAPIを追加する
Webhook用のパスを追加しましょう。
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
+app.post('/webhook', async (c) => {
+ console.log(await c.req.json())
+ return c.json({
+ message: 'ok'
+ })
+})
export default app
これで/webhook
へのPOST
リクエストを受け付けることができます。
Stripe CLIでStripe WebhookをローカルのAPIに中継する
Stripeで発生したイベントをローカルのAPIに中継するには、Stripe CLIを使います。
% stripe listen --forward-to http://127.0.0.1:65373/webhook
--forward-to
のURLは {npm run dev
で表示されたURL}/webhook を指定しましょう。
Webhookのシークレットが発行されますので、コピーしましょう。
Your webhook signing secret is whsec_xxxxx (^C to quit)
環境変数は、.dev.vars
ファイルへ
Wranglerを使って開発する場合、ローカルで利用する環境変数は.dev.vars
に保存します。
先ほどコピーしたシークレットも、.dev.vars
に保存しましょう。
.dev.vars
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
あわせて、Stripeのシークレットキーも保存しておきましょう。
ダッシュボード直リンク: https://dashboard.stripe.com/test/apikeys
.dev.vars
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
STRIPE_SECRET_KEY=sk_test_xxxx
アプリを再起動して、環境変数を読み込み直す
環境変数を設定しましたので、npm run dev
を実行し直しましょう。
% npm run dev
warning package.json: No license field
$ wrangler dev src/index.ts
⛅️ wrangler 3.5.1
------------------
wrangler dev now uses local mode by default, powered by 🔥 Miniflare and 👷 workerd.
To run an edge preview session for your Worker, use wrangler dev --remote
⎔ Starting local server...
[mf:inf] Ready on http://127.0.0.1:123456/
APIのURL(ポート番号)が変わりますので、stripe listen --forward-to
も再度実行します。
Webhookのシークレットは、stripe listen
を再起動しても変更されません。
% stripe listen --forward-to http://127.0.0.1:123456/webhook
「Stripeからのリクエストか」を検証する処理を追加する
作成したWebhook APIを、Stripe Webhook以外から呼び出されないようにしましょう。
Stripeの場合、Stripe SDKを利用して検証処理を追加します。
% npm i stripe
続いて作成したAPIパスに、検証処理を追加しましょう。
import { Hono } from 'hono'
import { env } from 'hono/adapter'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
app.post('/webhook', async (c) => {
const { STRIPE_SECRET_API_KEY, STRIPE_WEBHOOK_SECRET } = env(c)
const stripe = new Stripe(STRIPE_SECRET_KEY, {
apiVersion: "2023-08-16",
});
const signature = c.req.headers.get("stripe-signature");
try {
if (!signature) {
return c.json({
message: 'Bad Request'
}, 400)
}
const body = await c.req.text();
const event = await stripe.webhooks.constructEventAsync(
body,
signature,
STRIPE_WEBHOOK_SECRET
);
switch(event.type) {
case 'payment_intent.created': {
console.log(event.data.object)
break
}
default:
break
}
return c.json({
message: 'ok'
}, 200)
} catch (err) {
const errorMessage = `⚠️ Webhook signature verification failed. ${err instanceof Error ? err.message : 'Internal server error'}`
console.log(errorMessage);
return c.json({
message: errorMessage
}, 400)
}
})
export default app
Cloudflare WorkersでStripe Webhookを処理する際のポイント
Webhook APIを開発されたことがある方は、Next.jsやAWS Lambda / firebaseなどで処理する際と実装が少し異なることに気づかれるかもしれません。
ドキュメントで掲載している実装と、Cloudflare Workersで動かすための実装の差分を以下にDiffとして紹介します。
https://stripe.com/docs/webhooks/quickstart
const stripe = new Stripe(STRIPE_SECRET_KEY, {
apiVersion: "2023-08-16",
});
const signature = c.req.headers.get("stripe-signature");
try {
if (!signature) {
return c.json({
message: 'Bad Request'
}, 400)
}
const body = await c.req.text();
- const event = stripe.webhooks.constructEvent(
- request.body,
- signature,
- endpointSecret
- );
+ const event = await stripe.webhooks.constructEventAsync(
+ body,
+ signature,
+ STRIPE_WEBHOOK_SECRET
+ );
switch(event.type) {
case 'payment_intent.created': {
console.log(event.data.object)
break
}
default:
break
}
return c.json({
message: 'ok'
}, 200)
} catch (err) {
const errorMessage = `⚠️ Webhook signature verification failed. ${err instanceof Error ? err.message : 'Internal server error'}`
console.log(errorMessage);
return c.json({
message: errorMessage
}, 400)
}
})
export default app
もし「Cloudflare Workers / Pagesでは、Stripe Webhookがうまく動かない・・・」というケースに遭遇された場合は、この実装方法の違いに対応できているかをご確認ください。
Stripe CLIでWebhookイベントをテストする
実際に検証やWebhookのリクエストが処理できているかを確認しましょう。
curl
で直接APIを呼び出すと、エラーが出ます。
% curl -XPOST http://127.0.0.1:51351/webhook
{"message":"Bad Request"}
Stripe CLIでイベントをシミュレートしましょう。
% stripe trigger payment_intent.created
Setting up fixture for: payment_intent
Running fixture for: payment_intent
Trigger succeeded! Check dashboard for event details.
npm run dev
を実行している画面にPayment Intentの情報が表示されます。
[mf:inf] Updated and ready on http://127.0.0.1:51351/
Object {
id: pi_3NhnLTL6R1kGwUF41dAgicMq,
object: payment_intent,
amount: 2000,
amount_capturable: 0,
amount_details: Object
...
}
StripeとCloudflareを利用したアプリを開発する際のリソース
StripeとCloudflareを組み合わせた例については、Zennにワークショップ記事をいくつか公開しています。
こちらも併せてお試しください。
More resource