第三章:完璧なWebhook処理とデータベース同期

前章では、ユーザーをStripeの決済ページに送る方法を学びました。 しかし、決済業界で最も重要な格言を覚えておく必要があります:「フロントエンド画面の『支払い成功』は、決して実際の支払いの根拠として扱ってはいけない」

ユーザーがカード決済に成功した瞬間、ネットワークが切断されたりブラウザタブを閉じたりする可能性があり、success_urlページに到達しない場合があります。データベースへの書き込みロジックをフロントエンドに置いていると、ユーザーは「カードは引き落とされたが、商品が受け取れない」という悲惨なクレームに直面することになります。

唯一信頼できる方法は、Webhookを使用することです。これはStripeのサーバーがバックグラウンドであなたのサーバーに「ねえ、お金を受け取ったよ。この注文の詳細は...」と電話をかけるようなものです。この「電話」はユーザーのブラウザの状態に影響されず、最も安定かつ安全な決済確認メカニズムです。


🔒 1. Webhook署名(Signature):セキュリティの核心

WebhookがStripeから送信されるHTTP POSTリクエストであるなら、ハッカーがStripeを装って「100万ドル支払った」という偽リクエストを送ってきたらどうしますか?

Stripeの解決策は:**Webhook署名(Signature)**です。 StripeダッシュボードでWebhook URLを設定する際、Stripeは専用のWebhook Secretwhsec_で始まる)を発行します。 Stripeがリクエストを送るたびに、HTTPヘッダーにこのSecretで計算された「署名」が含まれます。私たちはリクエストを受け取ったら、同じSecretを使ってこの署名を検証し、一致しない場合はハッカーによる偽造と判断して拒否します!


💻 2. Stripe Webhook APIの実装

Next.js App Routerでは、このAPIを作成します:src/app/api/stripe/webhook/route.ts

import { NextResponse } from 'next/server';
import Stripe from 'stripe';
import { createClient } from '@supabase/supabase-js'; // ここではAdmin権限で書き込む必要があることに注意

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2023-10-16',
});

// 決済Webhookは非常に特殊で、署名を正しく検証するためには「生のバイトデータ(Raw Body)」を受け取る必要がある
// Next.jsではデフォルトのJSON解析を無効にする必要がある(ただしApp Routerでは直接request.text()を読めば良い)

export async function POST(request: Request) {
  const payload = await request.text();
  const sig = request.headers.get('stripe-signature');

  let event: Stripe.Event;

  try {
    // 1. コア防御:署名検証!この行が通れば、確実に本物のStripeからのリクエスト
    event = stripe.webhooks.constructEvent(
      payload,
      sig!,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err: any) {
    console.error('⚠️ Webhook署名検証に失敗しました.', err.message);
    return NextResponse.json({ error: 'Webhook signature verification failed' }, { status: 400 });
  }

  // 2. 高権限のデータベース接続を初期化(Webhookはバックグラウンドで実行されるため、現在のユーザーのCookieがない)
  const supabaseAdmin = createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.SUPABASE_SERVICE_ROLE_KEY!
  );

  // 3. イベントタイプに基づいて処理
  try {
    switch (event.type) {
      // イベントA:チェックアウトプロセス完了(一回限りの支払いまたはサブスクリプション初回)
      case 'checkout.session.completed': {
        const session = event.data.object as Stripe.Checkout.Session;
        
        // 前章で密かに保存したuserIdを取得
        const userId = session.metadata?.userId;
        
        if (userId) {
          console.log(`💰 支払い成功!ユーザー: ${userId}, 金額: ${session.amount_total}`);
          
          // データベースに書き込み、正式に権限を付与!
          await supabaseAdmin.from('vt_purchases').insert({
            user_id: userId,
            item_id: 'vip-all-access', // 全サイトアクセスパスと仮定
            order_no: session.id,
            amount: session.amount_total ? session.amount_total / 100 : 0 // Stripeの金額は最小単位(セント)で計算されるため、100で割る
          });
        }
        break;
      }

      // イベントB:サブスクリプションの月次課金成功(Recurring Payment)
      case 'invoice.paid': {
        const invoice = event.data.object as Stripe.Invoice;
        // 毎月自動更新機能を実装する場合は、ここでユーザーのサブスクリプション有効期限を更新!
        break;
      }

      // イベントC:サブスクリプション課金失敗(カード期限切れまたは残高不足)
      case 'invoice.payment_failed': {
        const invoice = event.data.object as Stripe.Invoice;
        // ここでユーザーのVIP権限を一時停止し、カード更新を通知
        break;
      }

      default:
        console.log(`未処理のイベントタイプ ${event.type}`);
    }

    // 4. 【極めて重要】必ず200 OKをStripeに返す
    // 返さない場合、Stripeはサーバーが故障したと判断し、その後3日間APIを叩き続ける
    return NextResponse.json({ received: true });

  } catch (err) {
    console.error('Webhookロジック処理中にエラーが発生しました:', err);
    return NextResponse.json({ error: 'Webhook handler failed' }, { status: 500 });
  }
}

🚧 3. LocalhostでWebhookをテストする方法

前章のECPayセクションで説明した「大きな落とし穴」:ECPayのサーバーはあなたのlocalhostにアクセスできません。 では、Stripeの開発段階で、1行コードを書くたびにVercelにデプロイしてテストしなければならないのでしょうか?

Stripeは、世界最高の開発者体験を提供する企業としての実力を発揮し、公式ツールを提供しています:Stripe CLI

  1. あなたのPCにStripe CLIをインストール。
  2. ターミナルでStripeアカウントにログイン:stripe login
  3. コマンドを実行し、StripeのWebhookをローカルネットワーク経由で直接localhostに転送:
    stripe listen --forward-to localhost:3000/api/stripe/webhook
    
  4. このコマンドを実行すると、ターミナルに次の行が表示されます: Your webhook signing secret is whsec_xxxxxxxx... これがローカル開発用のSTRIPE_WEBHOOK_SECRETなので、.env.localに貼り付けましょう!

これで、ローカルで決済ページからカード決済を行うと、StripeのクラウドからCLI経由でWebhook信号が送られ、ファイアウォールを越えてあなたのPCのAPIに正確に届きます!このような最高級の開発体験こそ、世界中の開発者がStripeを愛する理由です。

🏆 結語

Stripe国際決済の連携が完了しました! あなたは今、「Apple Payクイック決済」、「多通貨対応」、「安全なWebhook決済確認」、「サブスクリプション対応」を備えた国際級SaaSインフラを手に入れました。

ソフトウェアに国境はありません。あなたのコードにも国境があってはいけません。準備を整えて、あなたのソフトウェアを世界中に販売しましょう!🚀

完全なチュートリアルをロック解除

このチャプターは有料コンテンツです。プロジェクトに参加して、10以上の神レベルのPromptや実際のソースコード例を含む、5000字以上の深い分析をロック解除してください!