N.N. LLC. ロゴ - 千葉県船橋市のIT企業N.N. LLC.
事例紹介

ECショッピングサイト開発事例 — 決済連携からUI設計まで

15分で読めます
1回閲覧
ECショッピングサイト開発事例 — 決済連携からUI設計まで

プロジェクト概要

あるアパレルブランドのクライアント企業から、自社ECサイトの構築を依頼されました。既存のモール型ECサイトへの出店から脱却し、独自ドメインでのD2C(Direct to Consumer)販売を実現するプロジェクトです。

決済にはStripeを採用し、Next.jsベースのフロントエンドと組み合わせることで、高速かつセキュアなECサイトを構築しました。本記事では、要件定義からリリースまでの全プロセスを解説します。

要件定義 — 独自ECサイトに求められるもの

クライアントと複数回のヒアリングを重ね、以下の要件を確定しました。

  • 商品管理: SKU(Stock Keeping Unit)単位での在庫管理。カラー・サイズのバリエーション対応
  • 決済: クレジットカード決済(Visa / Mastercard / JCB / AMEX)、コンビニ決済
  • UI/UX: モバイルファーストのレスポンシブデザイン。商品画像のズーム表示対応
  • 注文管理: 管理画面での注文一覧・ステータス管理・出荷連携
  • セキュリティ: PCI DSS準拠の決済フロー。個人情報保護法対応
  • メール通知: 注文確認、発送通知、レビュー依頼の自動送信
  • パフォーマンス: ページ読み込み3秒以内、年間稼働率99.9%

技術選定

フロントエンド: Next.js + TypeScript

ECサイトにNext.jsを採用した理由は以下の通りです。

  • SEO: 商品ページのSSRにより、検索エンジンでの商品インデックスを最適化
  • パフォーマンス: ISRで商品ページを静的生成し、在庫情報のみリアルタイムで更新
  • 型安全性: TypeScriptにより、商品データや注文データの型をフロントエンドからバックエンドまで一貫して管理

決済: Stripe

決済基盤にはStripeを選定しました。Stripeは日本国内のクレジットカード決済はもちろん、コンビニ決済やApple Payにも対応しています。

  • Stripe Checkout: Stripeがホストする決済ページにリダイレクトする方式。PCI DSSの負荷を最小化
  • Payment Intent: 自社サイト内に決済フォームを埋め込む方式。Stripe Elementsでカード情報を安全に処理
  • Webhook: 決済完了・失敗などのイベントをリアルタイムで受信し、注文ステータスを自動更新

バックエンド: Supabase

商品管理、在庫管理、注文管理のデータベースにはSupabaseを使用しました。PostgreSQLの柔軟なクエリ機能とRLS(Row Level Security)による細かなアクセス制御が、ECサイトのデータ管理に適しています。

Stripe決済連携の実装

Stripe Checkoutフロー

本プロジェクトでは、ストレスのない決済体験のためにStripe Checkoutを中心に実装しました。ユーザーは商品選択からカート確認を経てStripeの決済ページにリダイレクトされ、決済完了後に自社サイトの完了ページに戻ります。

// Stripe Checkoutセッション作成(APIルート)
import Stripe from "stripe"

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

export async function POST(request: Request) {
  const { items } = await request.json()

  // カート内の商品からStripeの明細を生成
  const lineItems = items.map((item: CartItem) => ({
    price_data: {
      currency: "jpy",
      product_data: {
        name: item.name,
        images: [item.imageUrl],
      },
      unit_amount: item.price,  // 日本円は小数なし
    },
    quantity: item.quantity,
  }))

  // Checkoutセッションを作成
  const session = await stripe.checkout.sessions.create({
    payment_method_types: ["card"],
    line_items: lineItems,
    mode: "payment",
    success_url: `${process.env.NEXT_PUBLIC_URL}/order/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_URL}/cart`,
    shipping_address_collection: {
      allowed_countries: ["JP"],
    },
  })

  return Response.json({ url: session.url })
}

Webhook検証によるセキュアな注文処理

決済完了後の注文処理は、StripeのWebhookで受け取ったイベントに基づいて実行します。Webhookの署名検証により、不正なリクエストを確実に排除しています。

// Stripe Webhook処理(APIルート)
import { headers } from "next/headers"

export async function POST(request: Request) {
  const body = await request.text()
  const headersList = await headers()
  const signature = headersList.get("stripe-signature")!

  // Webhookの署名を検証(改ざん防止)
  const event = stripe.webhooks.constructEvent(
    body,
    signature,
    process.env.STRIPE_WEBHOOK_SECRET!
  )

  // 決済完了イベントの処理
  if (event.type === "checkout.session.completed") {
    const session = event.data.object
    // 注文レコードの作成、在庫の減算、確認メール送信
    await createOrder(session)
    await updateInventory(session)
    await sendConfirmationEmail(session)
  }

  return Response.json({ received: true })
}

商品管理・在庫管理の設計

データモデル

商品データは、商品(products)、バリエーション(variants)、在庫(inventory)の3層構造で設計しました。

-- 商品テーブル
CREATE TABLE products (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  description TEXT,
  base_price INTEGER NOT NULL,  -- 基本価格(税抜)
  category_id UUID REFERENCES categories(id),
  is_published BOOLEAN DEFAULT false,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- 商品バリエーション(カラー × サイズ)
CREATE TABLE variants (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  product_id UUID REFERENCES products(id),
  color TEXT NOT NULL,
  size TEXT NOT NULL,
  price_override INTEGER,  -- 基本価格と異なる場合
  sku TEXT UNIQUE NOT NULL,
  UNIQUE(product_id, color, size)
);

-- 在庫管理
CREATE TABLE inventory (
  variant_id UUID PRIMARY KEY REFERENCES variants(id),
  quantity INTEGER NOT NULL DEFAULT 0,
  reserved INTEGER NOT NULL DEFAULT 0,  -- カートに入っている数
  updated_at TIMESTAMPTZ DEFAULT now()
);

在庫のリアルタイム更新

在庫数は、カートに商品を追加した時点で「予約済み」として確保し、決済完了時に実際に減算するTwo-Phase方式を採用しています。カートに入れたまま放置された場合は、30分経過後に予約を解放する仕組みです。

レスポンシブなカートUI/UXの設計

モバイルファーストのUI設計原則

ECサイトのトラフィックの約70%はスマートフォン経由です。以下の設計原則を適用しました。

  1. サムゾーンの活用: 購入ボタンやカートアイコンを画面下部に配置し、片手操作でもアクセスしやすく設計
  2. ステッキーカートバー: スクロールしても画面下部にカート情報(商品数と合計金額)を固定表示
  3. スワイプ操作: 商品画像のギャラリーは左右スワイプで切り替え可能。ピンチイン・ピンチアウトによるズームにも対応
  4. ワンタップ購入: 以前の注文情報を保存し、リピート購入をワンタップで完了できる機能

決済フローのUX最適化

決済プロセスは3ステップに分割し、プログレスバーで進捗を可視化しています。

  1. Step 1 — カート確認: 商品名、数量、小計を一覧表示。数量変更と削除が可能
  2. Step 2 — 配送先入力: 住所の自動補完(郵便番号から)、以前の配送先からの選択
  3. Step 3 — 決済: Stripe Checkoutへリダイレクト(または埋め込みフォーム)

各ステップ間の離脱率を計測し、離脱が多いステップのUIを重点的に改善しました。特にStep 2の住所入力フォームは、郵便番号入力だけで都道府県・市区町村が自動補完される仕組みにしたことで、入力完了率が20%向上しました。

セキュリティ対策

PCI DSS準拠

クレジットカード情報のセキュリティについて、Stripe CheckoutまたはStripe Elementsを使用することで、カード情報が自社サーバーを経由しないアーキテクチャを実現しています。これにより、PCI DSSのSAQ-A(最も簡略化された自己評価)での準拠が可能です。

その他のセキュリティ施策

  • CSRF保護: Next.jsのServer ActionsでCSRFトークンを自動検証
  • 入力検証: zodスキーマによるサーバーサイド・クライアントサイド双方でのバリデーション
  • レート制限: APIルートにレート制限を実装し、ブルートフォース攻撃を防止
  • HTTPSの強制: Vercelのデフォルト設定ですべての通信がHTTPS化
  • CSP(Content Security Policy): 外部スクリプトの実行を制限し、XSSリスクを低減

メール通知フロー

決済完了後から商品到着後まで、以下のタイミングで自動メールを送信しています。

  1. 注文確認メール: Webhook受信直後に送信。注文番号、商品明細、合計金額を記載
  2. 発送通知メール: 管理画面で出荷処理を行った際に送信。追跡番号と配送業者のリンクを含む
  3. レビュー依頼メール: 商品到着予定日の3日後に送信。購入した商品のレビューを促す

メール送信にはResend(APIベースのメール送信サービス)を使用し、ReactベースのメールテンプレートでHTMLメールをレンダリングしています。

具体的な成果

ECサイト公開後3ヶ月間での成果は以下の通りです。

  • コンバージョン率: 従来のモール型EC比で25%向上。サイト内でのブランド体験が統一されたことで、購入意欲が高まった
  • カート離脱率: 業界平均(約70%)を大幅に下回る55%を達成。3ステップの決済フロー最適化が効果的だった
  • モバイル経由売上: 全売上の68%がモバイル経由。モバイルファーストの設計方針が数値に反映された
  • 平均ページ読み込み時間: 1.8秒。目標の3秒以内を大幅に上回る速度を実現
  • リピート率: 初月購入者の22%が2ヶ月以内に再購入。ワンタップ購入機能が貢献

まとめ

ECサイトの構築において、Next.js + Stripeの組み合わせは非常に強力です。SSRによるSEO最適化、ISRによるパフォーマンスの両立、StripeのPCI DSS準拠のセキュアな決済基盤、そしてTypeScriptによるフルスタックの型安全性。これらの技術要素が組み合わさることで、ユーザー体験と運用効率の両方を高い水準で実現できます。

ECサイトの新規構築やリニューアルをご検討の方は、ぜひ当社までお気軽にご相談ください。

EC
Next.js
TypeScript
決済連携
UI/UX

関連記事