こんにちは!前回のエピソードでは、Zustandを使ってカート機能を実装し、商品の追加や数量変更を可能にしました。今回は、Stripeを統合して安全でスムーズな決済機能を追加します。チェックアウトページを構築し、フォームバリデーションや決済処理の実装を通じて、ユーザーが簡単に購入を完了できるようにします。
このエピソードのゴール
- Stripe CheckoutをNext.jsに統合。
- チェックアウトページを構築し、フォームバリデーションを実装。
- カートデータを基に決済セッションを作成。
- Tailwind CSSでレスポンシブなチェックアウトUIをデザイン。
必要なもの
- 前回のプロジェクト(
next-ecommerce
)がセットアップ済み。 - Stripeアカウントと公開鍵/秘密鍵(Stripeダッシュボードから取得)。
-
@stripe/stripe-js
とstripe
パッケージ。 - 基本的なTypeScript、React、Next.jsの知識。
ステップ1: Stripeのセットアップ
Stripeをプロジェクトに統合するために、必要なパッケージをインストールします:
npm install @stripe/stripe-js stripe
Stripeの秘密鍵を安全に管理するため、.env.local
に以下を追加:
STRIPE_SECRET_KEY=あなたのStripe秘密鍵
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=あなたのStripe公開鍵
注意: 秘密鍵はサーバーサイドでのみ使用し、公開鍵はクライアントサイドで使用します。
ステップ2: Stripe APIクライアントの作成
サーバーサイドでStripeと通信するためのクライアントを作成します。src/lib/stripe.ts
ファイルを作成し、以下のコードを追加:
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-09-30.acacia',
});
このコードは、Stripeの秘密鍵を使ってAPIクライアントを初期化します。
ステップ3: チェックアウトページの構築
チェックアウトページを作成し、カートデータを表示して決済に進むフォームを構築します。src/app/checkout/page.tsx
ファイルを作成し、以下のコードを追加:
import { useCartStore } from '@/lib/cartStore';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export default function CheckoutPage() {
const { items, clearCart } = useCartStore();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const router = useRouter();
const totalPrice = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const handleCheckout = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items }),
});
const { url } = await response.json();
if (url) {
clearCart();
router.push(url);
} else {
setError('決済セッションの作成に失敗しました。');
}
} catch (err) {
setError('エラーが発生しました。もう一度お試しください。');
} finally {
setLoading(false);
}
};
return (
<main className="container mx-auto p-4">
<h1 className="text-3xl font-bold mb-6">チェックアウト</h1>
{items.length === 0 ? (
<p className="text-gray-600">カートに商品がありません。</p>
) : (
<div className="grid md:grid-cols-2 gap-8">
{/* カート内容 */}
<div>
<h2 className="text-xl font-semibold mb-4">注文内容</h2>
{items.map((item) => (
<div key={item.variantId} className="flex justify-between border-b py-2">
<span>
{item.title} x {item.quantity}
</span>
<span>
{(item.price * item.quantity).toFixed(2)} JPY
</span>
</div>
))}
<div className="text-right font-bold mt-4">
合計: {totalPrice.toFixed(2)} JPY
</div>
</div>
{/* 決済フォーム */}
<div>
<h2 className="text-xl font-semibold mb-4">決済情報</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium">メールアドレス</label>
<input
type="email"
className="w-full border p-2 rounded"
placeholder="example@email.com"
required
/>
</div>
<div>
<label className="block text-sm font-medium">配送先住所</label>
<input
type="text"
className="w-full border p-2 rounded"
placeholder="東京都渋谷区..."
required
/>
</div>
{error && <p className="text-red-500">{error}</p>}
<button
onClick={handleCheckout}
disabled={loading}
className="w-full bg-primary text-white px-6 py-3 rounded hover:bg-opacity-90 disabled:opacity-50"
>
{loading ? '処理中...' : '決済に進む'}
</button>
</div>
</div>
</div>
)}
</main>
);
}
このコードは:
- カート内容と合計金額を表示。
- メールアドレスと住所の簡易フォームを用意(本番ではバリデーションを強化)。
-
/api/checkout
エンドポイントにリクエストを送信してStripe Checkoutセッションを作成。
ステップ4: Stripe Checkout APIエンドポイントの作成
Stripe Checkoutセッションを作成するAPIルートを追加します。src/app/api/checkout/route.ts
ファイルを作成し、以下のコードを追加:
import { NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
export async function POST(request: Request) {
try {
const { items } = await request.json();
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: items.map((item: any) => ({
price_data: {
currency: 'jpy',
product_data: {
name: item.title,
images: [item.image],
},
unit_amount: Math.round(item.price * 100), // 円をセントに変換
},
quantity: item.quantity,
})),
mode: 'payment',
success_url: `${request.headers.get('origin')}/success`,
cancel_url: `${request.headers.get('origin')}/cart`,
});
return NextResponse.json({ url: session.url });
} catch (error) {
console.error('Stripeエラー:', error);
return NextResponse.json({ error: '決済セッションの作成に失敗しました' }, { status: 500 });
}
}
このコードは:
- カートアイテムを基にStripe Checkoutセッションを作成。
- 成功時またはキャンセル時のリダイレクトURLを指定。
- セッションのURLをクライアントに返す。
ステップ5: 決済成功ページの作成
決済完了後に表示するページを作成します。src/app/success/page.tsx
ファイルを作成:
export default function SuccessPage() {
return (
<main className="container mx-auto p-4">
<h1 className="text-3xl font-bold mb-6">ご購入ありがとうございます!</h1>
<p className="text-gray-600">
ご注文が正常に処理されました。詳細はメールでご確認ください。
</p>
<a href="/" className="mt-4 inline-block bg-primary text-white px-6 py-3 rounded hover:bg-opacity-90">
ホームに戻る
</a>
</main>
);
}
このページは、決済成功後にユーザーに感謝のメッセージを表示します。
ステップ6: 動作確認
-
.env.local
にStripeの公開鍵と秘密鍵を設定。 - 開発サーバーを起動(
npm run dev
)。 - カートに商品を追加し、
/checkout
にアクセス。 - フォームにテスト情報を入力し、「決済に進む」をクリック。
- Stripeのテスト決済ページにリダイレクトされ、以下の点を確認:
- 商品名、数量、金額が正しく表示される。
- 決済完了後に
/success
ページにリダイレクトされる。 - カートが空になる(
clearCart
が機能)。
- エラーがあれば、コンソールログやStripeダッシュボードを確認。
テストカード: Stripeのテスト環境では、4242 4242 4242 4242
(有効期限: 未来の日付、CVC: 任意の3桁)を使用。
まとめと次のステップ
このエピソードでは、Stripe Checkoutを統合し、チェックアウトページと決済機能を構築しました。フォームバリデーションやエラーハンドリングも追加し、ユーザーが安全に購入を完了できるようにしました。
次回のエピソードでは、パフォーマンス最適化に焦点を当て、Core Web Vitals(LCP、CLSなど)を改善します。Lighthouseを使った分析やReact Server Componentsの活用も紹介しますので、引き続きお楽しみに!
この記事が役に立ったと思ったら、ぜひ「いいね」を押して、ストックしていただければ嬉しいです!次回のエピソードもお楽しみに!