💳 Stripe サブスクリプション実戦:持続可能な収益を構築

前章までで「ワンタイム決済(One-time Payment)」の実装方法を学びました。 しかしSaaSプラットフォームや有料会員制コミュニティを運営する場合、**「継続収益(MRR, Monthly Recurring Revenue)」**こそが経済的自由を実現する鍵です!

本章ではStripeサブスクリプションの構築プロセスを完全ガイド。プラン作成、Checkout Sessionの開始、そして最も重要な「月次自動課金成功を監視するWebhook」の設定までを解説します。


1. Stripeサブスクリプションとは?

Stripeにおけるサブスクリプションとワンタイム決済の最大の違いは「周期性」にあります。

  • ワンタイム決済 (Payment Intents):1回の決済で終了
  • サブスクリプション (Subscriptions):カード登録後、Stripeが毎月同日に自動課金を実行。顧客が解約するまで継続

サブスクリプションの主要要素:

  1. Product (商品):例「Vibe Tutorプレミアム会員」
  2. Price (価格):単なる数値ではなく「周期(Recurring)」属性を持つオブジェクト。例「月額299円」
  3. Customer (顧客):カード情報と連絡先を紐付ける必須オブジェクト
  4. Subscription (契約):CustomerとPriceを結びつける契約関係

2. Stripeダッシュボードでプラン作成

コーディング前に、Stripe管理画面で商品と価格を設定しましょう。

  1. Stripeダッシュボードで ProductsAdd Product を選択
  2. Nameに「Vibe Tutorプレミアム会員」と入力
  3. Pricing modelは「Standard pricing」を選択
  4. Priceに「299」と入力
  5. Billing periodは「Monthly (月次)」を選択
  6. Save product をクリック

保存後、価格リストにprice_で始まるID(例:price_1Nxyz...)が表示されます。 このPrice IDをコピーしてください。後の実装で必要になります!


3. サブスクリプションCheckout Sessionの開始

Price IDを取得したら、Next.jsバックエンドにチェックアウトURL生成APIを作成します。 ワンタイム決済との主な違いは2点:

  1. mode'subscription'に設定
  2. 課金対象を特定するためcustomer_emailまたはcustomerの指定が必要

API Routeの実装

作成ファイル:src/app/api/stripe/create-subscription/route.ts

import { NextResponse } from 'next/server';
import Stripe from 'stripe';

// Stripe初期化(.env.localにSecret Keyを設定)
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2023-10-16',
});

export async function POST(request: Request) {
  try {
    const body = await request.json();
    const { email, userId } = body;

    // 1. パラメータ検証
    if (!email || !userId) {
      return NextResponse.json({ error: 'Missing email or userId' }, { status: 400 });
    }

    // 2. Checkout Session作成
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      mode: 'subscription', // 重要1:サブスクリプション指定
      customer_email: email, // 重要2:顧客紐付け用メール
      line_items: [
        {
          // ダッシュボードでコピーしたPrice IDを貼り付け
          price: 'price_1NxyzYOUR_PRICE_ID_HERE', 
          quantity: 1,
        },
      ],
      // 決済成功/キャンセル時のリダイレクト先
      success_url: `${process.env.NEXT_PUBLIC_BASE_URL}/payment-success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${process.env.NEXT_PUBLIC_BASE_URL}/pricing`,
      // 重要3:Webhookでユーザー特定するためのメタデータ
      subscription_data: {
        metadata: {
          vibe_user_id: userId,
        },
      },
    });

    // 生成されたチェックアウトURLを返却
    return NextResponse.json({ url: session.url });
    
  } catch (error: any) {
    console.error('Stripe Subscription Error:', error);
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
}

4. サブスクリプション状態を監視するWebhook

顧客がチェックアウトでカード登録後、Stripeは毎月自動課金を実行します。 WebhookでStripe通知を受け取り、データベースのVIPステータスを更新しましょう。

サブスクリプションで監視すべき主要イベント:

  1. checkout.session.completed:初回カード登録&決済成功時
  2. invoice.payment_succeeded:月次自動課金成功時(MRRの核心)
  3. customer.subscription.deleted:解約またはカード失効時

Webhookロジック実装

作成/編集ファイル:src/app/api/stripe/webhook/route.ts

import { NextResponse } from 'next/server';
import Stripe from 'stripe';
import { headers } from 'next/headers';
// データベース更新ロジック(仮想インポート)
import { updateUserSubscription } from '@/lib/db'; 

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

const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET!;

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

  let event: Stripe.Event;

  try {
    // Stripeからの正当なリクエストか検証
    event = stripe.webhooks.constructEvent(body, sig!, endpointSecret);
  } catch (err: any) {
    return NextResponse.json({ error: `Webhook Error: ${err.message}` }, { status: 400 });
  }

  // イベントタイプ別処理
  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object as Stripe.Checkout.Session;
      if (session.mode === 'subscription') {
        // 初回登録成功
        const userId = session.subscription_data?.metadata?.vibe_user_id;
        const subscriptionId = session.subscription as string;
        
        console.log(`User ${userId} started subscription ${subscriptionId}`);
        // TODO: subscriptionIdをDBに保存しVIP権限を付与
        await updateUserSubscription(userId, 'active', subscriptionId);
      }
      break;
    }
      
    case 'invoice.payment_succeeded': {
      const invoice = event.data.object as Stripe.Invoice;
      if (invoice.subscription) {
        // 月次課金成功!継続処理
        const subscriptionId = invoice.subscription as string;
        console.log(`Payment succeeded for subscription ${subscriptionId}`);
        // TODO: サブスクリプション有効期限を1ヶ月延長
      }
      break;
    }
      
    case 'customer.subscription.deleted': {
      const subscription = event.data.object as Stripe.Subscription;
      const subscriptionId = subscription.id;
      
      console.log(`Subscription ${subscriptionId} was canceled.`);
      // TODO: ユーザーのVIP権限を解除
      break;
    }
      
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  return NextResponse.json({ received: true });
}

5. 顧客が自分で契約を管理(Customer Portal)

サブスクリプション運用で最も避けたいのは「カスタマーサポート地獄」。 Stripeの無料機能Customer Portalを使えば、顧客が自分で:

  • カード情報更新
  • サブスクリプション停止/解約
  • 過去の領収書ダウンロード を可能にします。

Customer Portal URL生成

// src/app/api/stripe/portal/route.ts
import { NextResponse } from 'next/server';
import Stripe from 'stripe';

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

export async function POST(request: Request) {
  try {
    const { customerId } = await request.json(); // データベースから取得した顧客ID

    const session = await stripe.billingPortal.sessions.create({
      customer: customerId,
      return_url: `${process.env.NEXT_PUBLIC_BASE_URL}/dashboard`, // 管理後のリダイレクト先
    });

    return NextResponse.json({ url: session.url });
  } catch (error: any) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
}

フロントエンドに<button onClick={handleManageBilling}>契約を管理</button>ボタンを設置し、このAPIを呼び出すだけで、サポートコストをゼロに近づけられます!

これがSaaSプラットフォームの魅力です。一度コードを書けば、疲れ知らずのロボットが毎月自動で課金、請求書発行、解約処理を行います。さあ、最初のMRRを受け取る準備はできましたか?🚀

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

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