电子商务
规则
- 购物车状态:使用 Zustand 存储和
localStorage 持久化——支持页面刷新和标签页关闭后状态保持
- 产品变体:模型化为独立的 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 以在刷新后保持购物车
- 在“加入购物车”时扣减库存——仅在结账时预留库存,会话过期后释放
- 从客户端更新订单状态——使用 Stripe webhooks 处理
checkout.session.completed
- 用浮点数处理货币——以整数(分)存储,仅用于显示时格式化