如何在 Stripe 中实现商品变体(如尺码)与库存控制的正确架构设计

8次阅读

如何在 Stripe 中实现商品变体(如尺码)与库存控制的正确架构设计

stripe 的 custom_fields 不适用于商品规格选择和库存管理;应通过独立 product/price 建模变体,并由业务后端自行实现库存校验与扣减逻辑。

stripe 的 custom_fields 不适用于商品规格选择和库存管理;应通过独立 product/price 建模变体,并由业务后端自行实现库存校验与扣减逻辑。

在 Next.js 电商项目中集成 Stripe 时,开发者常误将 custom_fields 视为商品属性(如 T 恤尺码)的选择入口。但需明确:Stripe Checkout 的 custom_fields 是用于收集支付附带的元数据(如 Discord 用户名、企业发票号),而非定义商品变体或驱动库存逻辑。将其用于尺寸选择,不仅破坏用户体验(用户在结算页而非商品页做关键决策),更因 Stripe 缺乏原生库存能力,导致库存超卖风险无法规避。

✅ 正确架构:用独立 Price 建模变体

每个可售组合(如 “T 恤 – Small”、“T 恤 – Medium”)应对应唯一的 Stripe Product + Price 对。例如:

// 创建三个独立 Price(均关联同一 Product 或不同 Product) const smallPrice = await stripe.prices.create({   product: 'prod_tshirt_basic', // 或新建 prod_tshirt_small   unit_amount: 2999,   currency: 'usd',   nickname: 'T-Shirt (Small)', });  const mediumPrice = await stripe.prices.create({   product: 'prod_tshirt_basic',   unit_amount: 2999,   currency: 'usd',   nickname: 'T-Shirt (Medium)', });

前端商品页即可直接渲染这些 Price ID,并在添加购物车时绑定具体变体。Checkout session 仅需传入已选 Price ID:

const session = await stripe.checkout.sessions.create({   mode: 'payment',   line_items: [{     price: selectedPriceId, // 如 'price_1Qx...'(对应 Medium)     quantity: 1,   }],   success_url: `${origin}/success`,   cancel_url: `${origin}/cart`, });

⚠️ 库存控制必须由你完全接管

Stripe 不提供任何库存(inventory)功能。所有库存状态(总库存、预占库存、已售出量)必须在你的数据库中维护,并在关键路径严格校验:

  • 加购前:检查 size=medium 的实时可用库存 ≥ 1
  • 创建 Checkout Session 前:再次校验(防止并发冲突)
  • 支付成功后(Webhook payment_intent.succeeded):原子性扣减库存
  • 支付失败或取消(checkout.session.expired / payment_intent.payment_failed):释放预占库存

示例库存校验伪代码(Next.js API Route):

// POST /api/create-checkout-session export async function POST(req: Request) {   const { priceId, quantity } = await req.json();    // 1. 查询该 priceId 对应的库存余量(需映射 price → size → stock)   const inventory = await db.inventory.findUnique({     where: { priceId },   });    if (!inventory || inventory.available < quantity) {     return Response.json(       { error: 'Insufficient stock' },       { status: 400 }     );   }    // 2. 创建 Session(此时库存未扣减,仅校验)   const session = await stripe.checkout.sessions.create({ /* ... */ });   return Response.json({ url: session.url }); }

? 关键注意事项

  • 不要依赖 custom_fields 传递业务逻辑:它无法触发库存变更,也无法在 Webhook 中可靠映射到商品维度。
  • Price ID 是唯一可信标识:所有库存、日志、报表均应基于 Price ID(而非自定义字段值)进行关联。
  • 预占机制不可省略:用户加入购物车即应预占库存(如 redis 键 cart:${userId}:stock:${priceId}),避免“看到有货却下单失败”。
  • Webhook 必须幂等处理:payment_intent.succeeded 可能重复投递,库存扣减需使用数据库 UPDATE … WHERE available >= $quantity 并检查影响行数。

归根结底,Stripe 是支付管道,不是商品目录或库存系统。将变体建模为独立 Price,并由你的应用层承担库存状态管理,是当前最健壮、可扩展且符合 Stripe 设计哲学的实践方案。

text=ZqhQzanResources