第2章:Next.js と Stripe Checkout の統合
この章では、Stripeの強力な機能であるStripe Checkoutの使用方法を学びます。
もし台湾の決済サービス(例:緑界)を統合したことがあれば、自分で決済ページを作成し、データをパッケージ化、暗号化、リダイレクトする必要があったでしょう。Stripe Checkoutはこのプロセスを完全に革新します。 Stripe CheckoutはStripeがホストする決済ページで、数十億回の取引を通じたA/Bテストが行われ、非常に高いコンバージョン率を誇ります。多言語自動切り替え、通貨のローカライズをサポートするだけでなく、Apple PayとGoogle Payのワンクリック決済機能も標準装備しています!
私たちがこれから行うのは、Next.jsのバックエンドAPIを通じてStripeに専用のCheckout URLを「申請」し、ユーザーをそこに誘導することです。
💻 実装:Checkout Session APIの作成
App Routerアーキテクチャの下で、APIルートを作成します:src/app/api/stripe/checkout/route.ts。
import { NextResponse } from 'next/server';
import Stripe from 'stripe';
import { createClient } from '@/utils/supabase/server'; // Supabase使用を想定
// Stripeインスタンスの初期化
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16', // APIバージョンを固定することを推奨
});
export async function POST(request: Request) {
try {
// 1. ユーザー認証 (ログインユーザーのみが購入可能)
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// 2. フロントエンドから購入するPrice IDを取得
const body = await request.json();
const { priceId } = body;
// 3. Stripe Checkout Sessionの作成
// これが最も重要なAPI呼び出しです!
const session = await stripe.checkout.sessions.create({
// Stripeに今回の取引に含まれる商品を伝える
line_items: [
{
price: priceId, // 前章でDashboardに作成したPrice IDを渡す
quantity: 1, // 数量は1
},
],
// modeは'payment'(単回支払い)または'subscription'(サブスクリプション)が可能
mode: 'subscription',
// 成功時と失敗時のリダイレクトURL (ReturnURLのフロントエンド版)
// 注意:デプロイ後にlocalhostに戻らないよう、環境変数で動的にURLを決定する必要があります!
success_url: `${process.env.NEXT_PUBLIC_SITE_URL}/payment-success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_SITE_URL}/pricing`,
// データベースのUser IDをメタデータ(metadata)に埋め込む
// これにより、支払い成功時にStripe WebhookからこのIDが返されます
metadata: {
userId: user.id,
},
// ユーザーのEmailを事前入力。入力時間を節約しコンバージョン率向上!
customer_email: user.email,
});
// 4. 作成したCheckout URLをフロントエンドに返す
return NextResponse.json({ url: session.url });
} catch (error: any) {
console.error('Stripe Checkout Error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
💡 コードのハイライト解説
mode: 'subscription':この1行だけで、Stripeが毎月の定期課金ロジックを自動処理します。自分でCron Job(スケジューラ)を書いて定期課金する必要はありません!買い切りのコースを販売する場合は、mode: 'payment'に変更するだけです。metadata: { userId: user.id }:これは非常に重要なテクニックです!緑界で使うCustomFieldのようなものです。ユーザーがStripeページで支払いを完了すると、StripeはWebhookを送信します。このmetadata.userIdを使って、データベースのどのアカウントに権限を付与するかを判断します。success_url:ユーザーが支払いに成功した後に表示されるページです。URLに{CHECKOUT_SESSION_ID}変数を含めることができ、Stripeが自動的に実際のSession IDに置き換えます。これによりフロントエンドページはこのIDを使ってデータを取得できます。
🖥️ フロントエンド統合:決済リクエストの開始
バックエンドAPIが完成したら、フロントエンドのボタンは非常にシンプルです。Pricingページに以下を追加します:
'use client';
import { useState } from 'react';
export default function PricingButton({ priceId }: { priceId: string }) {
const [loading, setLoading] = useState(false);
const handleCheckout = async () => {
setLoading(true);
try {
const response = await fetch('/api/stripe/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId }),
});
const data = await response.json();
if (data.url) {
// ブラウザをStripeの高コンバージョン決済ページに直接リダイレクト
window.location.href = data.url;
}
} catch (error) {
console.error('Error:', error);
} finally {
setLoading(false);
}
};
return (
<button
onClick={handleCheckout}
disabled={loading}
className="bg-indigo-600 text-white font-bold py-3 px-6 rounded-xl hover:bg-indigo-700 transition"
>
{loading ? '決済準備中...' : 'Apple Pay / クレジットカードで支払う'}
</button>
);
}
✅ 本章のまとめ
これで、ユーザーがボタンをクリックすると、シームレスにStripeの公式決済ページに誘導され、Apple PayやGoogle Payのオプションが表示されます!
しかし、ユーザーが支払いに成功してsuccess_urlにリダイレクトされた後、どのように権限を付与すればよいでしょうか?
次の章では、決済システムの核心であるWebhookサーバー間通信について探求します。これにより、ユーザーのブラウザが支払い時にクラッシュした場合でも、データベースに正確に取引記録が残るようになります!