Astroでは、静的なページだけでなくREST APIを作ることもできます。
APIを作成する場合、src/pages
ディレクトリ内に、[API Path name].js
(TypeScriptでは[API Path name].ts
)を作成し、次のようなコードを追加します。
import { APIRoute } from "astro";
export const get: APIRoute = async () => {
return {
body: JSON.stringify({
message: "Hello World"
})
}
}
Stripe Webhookの「署名チェック」を行う方法
AstroやNext.jsなどのウェブフレームワークでは、リクエスト内容を簡単に取得できるようなパーサー処理が内蔵されています。
しかしStripeなどの一部サービスでは、Webhookの署名チェック時に「パース前のリクエストBody」が必要になることがあります。
Astroでは2023/04時点でBodyのパーサーをオフにする方法が見つかりませんでしたので、次のような実装で対応します。
import { APIRoute } from "astro";
import Stripe from "stripe";
const STRIPE_SECRET_API_KEY = import.meta.env.STRIPE_SECRET_API_KEY;
const STRIPE_WEBHOOK_SECRET = import.meta.env.STRIPE_WEBHOOK_SECRET;
const stripe = new Stripe(STRIPE_SECRET_API_KEY, {
apiVersion: "2022-11-15"
});
export const post: APIRoute = async (context) => {
const { request } = context;
const signature = request.headers.get("stripe-signature");
try {
const body = await request.arrayBuffer();
const event = stripe.webhooks.constructEvent(
Buffer.from(body),
signature,
STRIPE_WEBHOOK_SECRET
);
return {
body: JSON.stringify({
message: "ok",
})
};
} catch (err) {
const errorMessage = `⚠️ Webhook signature verification failed. ${err.message}`
console.log(errorMessage);
return new Response(errorMessage, {
status: 400
})
}
}
リクエストBodyをrequest.json()
やrequest.text()
ではなく、request.arrayBuffer()
で取得します。
その後、Buffer.from()
を通すことでパース前と同等のデータをconstructEvent
へ渡すことができます。
[Appendix] 汎用的なハンドラーにする方法
TypeScriptの型をより活用したい場合には、次のような関数を作ることもできます。
export const post: APIRoute = async (context) => {
return withStripeWebhookVerification(context, async (event) => {
return {
body: JSON.stringify(event)
}
})
}
まず、Stripe Webhookの型定義をまとめたライブラリをインストールしましょう。
% npm install -D stripe-event-types
続いてWebhookの署名チェックや型定義のカスタマイズを行うwithStripeWebhookVerification
関数を作ります。
/// <reference types="stripe-event-types" />
import { EndpointOutput } from "astro";
import { APIContext, APIRoute } from "astro";
import Stripe from 'stripe';
const STRIPE_SECRET_API_KEY = import.meta.env.STRIPE_SECRET_API_KEY;
const STRIPE_WEBHOOK_SECRET = import.meta.env.STRIPE_WEBHOOK_SECRET;
const stripe = new Stripe(STRIPE_SECRET_API_KEY, {
apiVersion: '2022-11-15'
});
export type StripeWebhookAPIRoute = (event: Stripe.DiscriminatedEvent, context: APIContext) => EndpointOutput | Response | Promise<EndpointOutput | Response>
export const withStripeWebhookVerification = async (context: APIContext, callback: StripeWebhookAPIRoute) => {
const { request } = context;
const signature = request.headers.get('stripe-signature');
try {
const body = await request.arrayBuffer();
const event = stripe.webhooks.constructEvent(
Buffer.from(body),
signature,
STRIPE_WEBHOOK_SECRET
);
return callback(event as Stripe.DiscriminatedEvent, context);
} catch (err) {
const errorMessage = `⚠️ Webhook signature verification failed. ${err.message}`
console.log(errorMessage);
return new Response(errorMessage, {
status: 400
});
};
};
あとはWebhook APIにしたいコードで、withStripeWebhookVerification
を呼び出すだけです。
export const post: APIRoute = async (context) => {
return withStripeWebhookVerification(context, async (event) => {
switch (event.type) {
case 'customer.created': {
console.log(event.data.object.email)
break
}
case 'customer.subscription.created': {
console.log(event.data.object.billing_cycle_anchor)
break
}
default:
break
}
return {
body: JSON.stringify(event)
}
})
}
参考資料など