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
相关产品推荐
相关产品推荐

