You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node+React项目中Stripe支付后如何设置正确的库存更新条件?

问题:Stripe支付完成后才更新库存的触发条件设置

我正在开发一个基于Node和React的小型项目,用Stripe搭建支付系统。目前其他功能正常,只有一个问题:想在支付流程完成后更新数据库里的商品库存,但没法设置正确的触发条件。现在只要进入Stripe支付页面,就算没完成付款就返回,库存也会被更新。

我的后端代码:

exports.getCheckoutSessionBook = async (req, res, next) => {
    const book = req.body.bookID[0]
    console.log(book._id)
    const quantity = req.body.quantityBooks
    if (book.quantity < quantity) return next(new AppError(`there is only ${book.quantity} in stuck `))

    if (book.quantity <= 0) return next(new AppError(`this book is over please try another book `))


    const session = await stripe.checkout.sessions.create({
        payment_method_types: ['card'],
        success_url: `http://127.0.0.1:3001`,
        cancel_url: `http://127.0.0.1:3001/account`,
        customer_email: req.user.email,
        client_reference_id: req.user.id,
        line_items: [{
            price_data: {
                currency: 'usd',
                product_data: {
                    name: book.title,
                    description: book.description,
                    images: [book.image],

                },
                unit_amount: book.price * 100,

            },
            quantity: quantity,


        }],
        mode: 'payment',



    });

    res.status(200).json({
        status: 'suceess',
        session
    })
//here is my problem
    if (res.status == 200) {
        console.log(321)
        const doc = await Book.findByIdAndUpdate(book._id, { $inc: { quantity: - quantity } }, {
            new: true,
            runValidators: true
        })
        console.log(333)
    }

}

前端引入的脚本:

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

解决方法:通过Stripe Webhook监听支付成功事件

你当前的逻辑是创建支付会话后立即更新库存,完全忽略了用户是否完成支付。正确的做法是依赖Stripe的Webhook机制,只有当用户成功完成支付时,Stripe会向你的后端发送checkout.session.completed事件,此时再执行库存更新操作。

1. 配置Stripe Webhook端点

  • 登录Stripe后台,进入「Webhooks」页面,添加一个接收事件的端点(例如https://你的域名/api/stripe-webhook)。
  • 勾选checkout.session.completed事件,保存后复制Stripe提供的签名密钥(用于验证请求合法性)。

2. 编写Webhook处理路由

在你的Node项目中新增处理Webhook的路由:

const stripe = require('stripe')('你的Stripe Secret Key');
const Book = require('../models/Book'); // 替换为你的Book模型路径

exports.stripeWebhook = async (req, res) => {
  const sig = req.headers['stripe-signature'];
  const webhookSecret = '你的Webhook签名密钥';

  let event;
  try {
    // 验证Stripe请求的合法性
    event = stripe.webhooks.constructEvent(
      req.rawBody, // 注意:需确保Express能获取原始请求体,需配置express.raw()
      sig,
      webhookSecret
    );
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // 处理支付完成事件
  if (event.type === 'checkout.session.completed') {
    const session = event.data.object;
    // 从会话的metadata中取出之前存储的图书ID和购买数量
    const bookId = session.metadata.bookId;
    const quantity = parseInt(session.metadata.quantity);

    // 原子更新库存($inc是MongoDB的原子操作,避免并发问题)
    await Book.findByIdAndUpdate(bookId, { $inc: { quantity: -quantity } }, {
      new: true,
      runValidators: true
    });

    console.log('库存已成功更新');
  }

  res.json({ received: true });
};

3. 修改支付会话创建代码

在创建Stripe支付会话时,把图书ID和购买数量存入metadata字段,方便Webhook中获取信息:

exports.getCheckoutSessionBook = async (req, res, next) => {
    const book = req.body.bookID[0]
    const quantity = req.body.quantityBooks
    if (book.quantity < quantity) return next(new AppError(`库存仅剩余${book.quantity}本`))
    if (book.quantity <= 0) return next(new AppError(`该图书已售罄,请选择其他图书`))

    const session = await stripe.checkout.sessions.create({
        payment_method_types: ['card'],
        success_url: `http://127.0.0.1:3001`,
        cancel_url: `http://127.0.0.1:3001/account`,
        customer_email: req.user.email,
        client_reference_id: req.user.id,
        // 添加metadata存储订单关键信息
        metadata: {
            bookId: book._id.toString(),
            quantity: quantity.toString()
        },
        line_items: [{
            price_data: {
                currency: 'usd',
                product_data: {
                    name: book.title,
                    description: book.description,
                    images: [book.image],
                },
                unit_amount: book.price * 100,
            },
            quantity: quantity,
        }],
        mode: 'payment',
    });

    res.status(200).json({
        status: 'success',
        session
    })
    // 删除原来这里的库存更新代码,交由Webhook处理
}

关键注意事项

  • Express请求体配置:在Webhook路由前必须添加app.use('/api/stripe-webhook', express.raw({type: 'application/json'}));,因为Stripe的Webhook请求需要原始JSON数据,不能被express.json()解析。
  • 测试Webhook:可以使用Stripe CLI模拟发送checkout.session.completed事件,验证库存更新逻辑是否正常。
  • 并发安全:使用$inc原子操作更新库存,能避免多个用户同时购买导致的库存超卖问题。

内容的提问来源于stack exchange,提问作者elyasaf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 21:55:13