E コマース
ルール
- カートの状態:
localStorage 永続化付き Zustand ストア — ページのリフレッシュやタブの閉鎖後も保持されます
- 商品バリアント: 共有の親商品を持つ独立した SKU としてモデル化 — 各バリアントは自身の価格、在庫、画像を持ちます
- チェックアウト: Stripe Checkout Sessions (サーバーサイド) — クライアントから価格を送信しないでください
- 在庫: チェックアウトセッションを作成する前にサーバーサイドで在庫を確認 — データベーストランザクションで競合状態を防止
- 注文ライフサイクル:
pending → paid → shipped → delivered — クライアントのアクションではなく Webhook イベントで更新
- 商品一覧: サーバーサイドのフィルタリング/ソート/ページネーション — 共有可能なフィルタリング済みビュー用の URL 検索パラメータ
- 価格: セント単位 (整数) で保存 —
Intl.NumberFormat と通貨コードを使用して表示
- カート操作: 追加、削除、数量変更、クリア — すべてのカート操作で在庫を検証
パターン
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface CartItem { productId: string; variantId: string; quantity: number; price: number; }
interface CartStore {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (variantId: string) => void;
updateQuantity: (variantId: string, quantity: number) => void;
clear: () => void;
total: () => number;
}
export const useCart = create<CartStore>()(
persist(
(set, get) => ({
items: [],
addItem: (item) => set((state) => {
const existing = state.items.find((i) => i.variantId === item.variantId);
if (existing) {
return { items: state.items.map((i) =>
i.variantId === item.variantId ? { ...i, quantity: i.quantity + item.quantity } : i
)};
}
return { items: [...state.items, item] };
}),
removeItem: (variantId) => set((s) => ({ items: s.items.filter((i) => i.variantId !== variantId) })),
updateQuantity: (variantId, quantity) => set((s) => ({
items: quantity <= 0
? s.items.filter((i) => i.variantId !== variantId)
: s.items.map((i) => (i.variantId === variantId ? { ...i, quantity } : i)),
})),
clear: () => set({ items: [] }),
total: () => get().items.reduce((sum, i) => sum + i.price * i.quantity, 0),
}),
{ name: "cart-storage" }
)
);
避けるべきこと
- チェックアウト作成のためにクライアントから価格を送信する — 常にデータベースからサーバーサイドで価格を解決してください
- カートを React の状態のみに保存する — 永続化された Zustand または localStorage を使用してカートがリフレッシュ後も保持されるようにしてください
- 「カートに追加」時に在庫を減らす — チェックアウト時にのみ在庫を予約し、セッション期限切れ時に解放してください
- クライアントから注文ステータスを更新する —
checkout.session.completed には Stripe Webhook を使用してください
- お金に浮動小数点を使用する — 整数 (セント) として保存し、表示用にのみフォーマットしてください