🪝
個人開発者のためのStripe超入門②基本実装-単発決済(後半)
個人開発者のためのStripe超入門②基本実装-単発決済(後半)
第2章(後半) Webhook処理の実装
概要
Stripe決済における重要な機能の一つがWebhookです。決済完了・失敗、サブスクリプション更新などのイベントをStripeからリアルタイムで受け取り、アプリケーションの状態に反映させることができます。
Webhookとは
WebhookはStripeからアプリケーションに送信されるHTTPリクエストです。決済関連のイベントが発生すると、Stripeが自動的にアプリケーションのエンドポイントに、あらかじめ設定したPOSTリクエストを送信します。
主要なWebhookイベント(Checkout関連)
-
checkout.session.completed
: Checkout決済完了 -
checkout.session.expired
: Checkout セッション期限切れ -
checkout.session.async_payment_succeeded
: 非同期決済成功(銀行振込等) -
checkout.session.async_payment_failed
: 非同期決済失敗 -
payment_intent.succeeded
: 決済成功(Payment Intent使用時) -
payment_intent.payment_failed
: 決済失敗(Payment Intent使用時)
ここでは最初の2つを使用します。
Webhook対応 Edge Functionの実装
Stripe Webhook処理用のSupabase Edge Functionを作成します。
// supabase/functions/stripe-webhook/index.ts
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
import Stripe from 'https://esm.sh/stripe@14.21.0?target=deno';
const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY') || '', {
apiVersion: '2023-10-16',
});
const supabase = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
);
Deno.serve(async (req) => {
const signature = req.headers.get('stripe-signature');
const webhookSecret = Deno.env.get('STRIPE_WEBHOOK_SECRET');
if (!signature || !webhookSecret) {
return new Response('Missing signature or webhook secret', { status: 400 });
}
try {
const body = await req.text();
// Webhook署名がStripe由来かを検証
const event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
// 重複処理がないかチェック
const { data: existingEvent } = await supabase
.from('webhook_events')
.select('id')
.eq('stripe_event_id', event.id)
.single();
if (existingEvent) {
return new Response('Event already processed', { status: 200 });
}
// イベントログ記録
await supabase
.from('webhook_events')
.insert({
stripe_event_id: event.id,
event_type: event.type,
});
// イベント種別に応じて処理
switch (event.type) {
case 'checkout.session.completed':
await handleCheckoutSessionCompleted(event.data.object as Stripe.Checkout.Session);
break;
case 'checkout.session.expired':
await handleCheckoutSessionExpired(event.data.object as Stripe.Checkout.Session);
break;
default:
console.log(`未処理のイベント: ${event.type}`);
}
// 処理完了マーク
await supabase
.from('webhook_events')
.update({ processed: true })
.eq('stripe_event_id', event.id);
return new Response('OK', { status: 200 });
} catch (error) {
console.error('Webhook処理エラー:', error);
return new Response('Webhook Error', { status: 400 });
}
});
// 決済セッション完了処理
async function handleCheckoutSessionCompleted(session: Stripe.Checkout.Session) {
const customerId = session.customer as string;
// 顧客情報取得
const { data: customer } = await supabase
.from('customers')
.select('*')
.eq('stripe_customer_id', customerId)
.single();
if (!customer) {
console.error('顧客が見つかりません:', customerId);
return;
}
// 単発決済の処理
const paymentIntent = session.payment_intent as string;
await supabase
.from('payments')
.insert({
customer_id: customer.id,
stripe_payment_intent_id: paymentIntent,
amount: session.amount_total!,
currency: session.currency!,
status: 'completed',
});
console.log(`単発決済完了: ${paymentIntent}`);
}
// 決済セッション期限切れ処理
async function handleCheckoutSessionExpired(session: Stripe.Checkout.Session) {
const customerId = session.customer as string;
// 顧客情報取得
const { data: customer } = await supabase
.from('customers')
.select('*')
.eq('stripe_customer_id', customerId)
.single();
if (!customer) {
console.error('顧客が見つかりません:', customerId);
return;
}
// 期限切れログ記録(必要に応じて)
console.log(`決済セッション期限切れ: ${session.id}`);
}
Webhook設定
Stripeダッシュボードでの設定
- Stripeダッシュボード → 開発者 → Webhook
- エンドポイントを追加をクリック
-
エンドポイントURL:
https://your-project.supabase.co/functions/v1/stripe-webhook
-
監視するイベント(単発決済用):
checkout.session.completed
checkout.session.expired
- エンドポイントを追加をクリック
Webhook Secretの取得と設定
作成したWebhookエンドポイントから署名シークレットを取得し、環境変数に設定します:
- 作成したWebhookエンドポイントをクリック
- 署名シークレットの表示をクリック
-
whsec_...
で始まるシークレットをコピー - Supabaseプロジェクトの環境変数に設定:
# Supabase CLI
supabase secrets set STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxx
# または Supabaseダッシュボード → 設定 → Edge Functions → Secrets
重要な注意点
- Webhook Secretは、Webhookエンドポイントを作成した後にStripeから取得
- テスト環境と本番環境でそれぞれ別のWebhookエンドポイントが必要
- 各環境で異なるWebhook Secretが発行される
- 環境変数は環境に応じて適切に設定する:
-
テスト:
whsec_test_...
-
本番:
whsec_live_...
-
テスト:
セキュリティ考慮事項
- 署名検証: 必ずWebhook署名がStripe由来であることを検証
- 重複処理防止: 同一イベントIDの重複処理をチェック
- エラーハンドリング: 処理失敗時のリトライ機構
- ログ記録: 処理履歴の保存と監視
まとめ
Webhook処理により、Stripe決済とアプリケーションの状態を確実に同期できます。重複処理防止と適切なエラーハンドリングが重要です。
次章では、サブスクリプション決済の実装について説明します。
Discussion