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

基于MERN栈+Razorpay实现单商品退款及独立订单/支付ID方案

解决Razorpay多商品独立订单与退款问题

核心思路

不要将整个购物车作为单一订单提交,而是为每件商品单独创建Razorpay订单并发起独立支付。这样每件商品会对应唯一的orderId和paymentId,退款时仅针对目标商品的paymentId操作,不会影响同购物车中的其他商品。


具体实现步骤

1. 前端购物车提交逻辑

遍历购物车中的每件商品,逐个调用后端接口创建Razorpay订单,并发起支付请求:

// React前端示例:处理多商品 checkout
const handleCheckout = async () => {
  // 为每件商品创建订单
  const orderPromises = cartItems.map(async (item) => {
    const res = await fetch('/api/create-single-order', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        productId: item._id,
        quantity: item.quantity,
        amount: item.price * item.quantity * 100, // Razorpay要求金额以派萨为单位
        currency: 'INR'
      })
    });
    return res.json();
  });

  const orders = await Promise.all(orderPromises);

  // 对每个订单发起Razorpay支付
  orders.forEach(order => {
    const options = {
      key: 'YOUR_PUBLIC_KEY',
      amount: order.amount,
      currency: order.currency,
      order_id: order.id,
      handler: async (response) => {
        // 支付成功后,将关联信息提交后端存储
        await fetch('/api/save-payment-record', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            productId: order.productId,
            userId: currentUser._id,
            orderId: order.id,
            paymentId: response.razorpay_payment_id,
            amount: order.amount / 100, // 转换回元存储
            status: 'success'
          })
        });
      }
    };
    const rzp = new window.Razorpay(options);
    rzp.open();
  });
};

2. 后端创建单商品订单接口

使用Express调用Razorpay订单创建API,返回订单信息并关联商品ID:

// Node.js/Express后端示例
const Razorpay = require('razorpay');
const instance = new Razorpay({ key_id: 'YOUR_KEY_ID', key_secret: 'YOUR_SECRET' });

app.post('/api/create-single-order', async (req, res) => {
  const { productId, quantity, amount, currency } = req.body;
  const orderOptions = {
    amount,
    currency,
    receipt: `receipt_${productId}_${Date.now()}`,
    notes: { productId, quantity }
  };

  try {
    const order = await instance.orders.create(orderOptions);
    res.json({ ...order, productId });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

3. MongoDB数据模型设计

创建独立的支付记录模型,确保每件商品的支付信息单独存储:

// Mongoose支付记录模型
const mongoose = require('mongoose');

const paymentSchema = new mongoose.Schema({
  productId: { 
    type: mongoose.Schema.Types.ObjectId, 
    ref: 'Product', 
    required: true 
  },
  userId: { 
    type: mongoose.Schema.Types.ObjectId, 
    ref: 'User', 
    required: true 
  },
  orderId: { type: String, required: true },
  paymentId: { type: String, required: true, unique: true },
  amount: { type: Number, required: true },
  status: { 
    type: String, 
    enum: ['success', 'refunded'], 
    default: 'success' 
  },
  createdAt: { type: Date, default: Date.now }
});

module.exports = mongoose.model('Payment', paymentSchema);

4. 单件商品退款实现

根据目标商品对应的paymentId调用Razorpay退款接口,并更新MongoDB中的支付状态:

// 后端退款接口
app.post('/api/refund-single-item', async (req, res) => {
  const { paymentId, amount } = req.body;
  try {
    // 调用Razorpay退款API
    const refund = await instance.payments.refund(paymentId, {
      amount: amount * 100, // 转换为派萨
      speed: 'normal',
      receipt: `refund_${paymentId}`
    });
    // 更新支付记录状态
    await Payment.findOneAndUpdate({ paymentId }, { status: 'refunded' });
    res.json({ success: true, refund });
  } catch (error) {
    res.status(500).json({ success: false, error: error.message });
  }
});

注意事项

  • 用户体验优化:多商品支付会弹出多个Razorpay窗口,可在前端添加批量支付提示,或引导用户拆分订单;
  • 金额校验:前后端需双重校验商品金额,防止前端篡改;
  • 幂等性处理:支付成功后保存记录时,通过paymentId唯一性校验避免重复创建。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 09:01:30