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

如何在用户完成Stripe付款后再创建NEXT.JS订单?

解决Stripe结账取消后产生无效订单的问题(Next.js环境)

当前代码的核心问题是在跳转到Stripe结账前就创建了订单,不管后续支付是否成功,都会留下无效订单。正确的做法是仅在支付确认完成后再创建订单,这需要借助Stripe的Webhook机制来实现。

具体解决方案步骤

1. 修改现有/api/stripe路由,仅创建Stripe结账会话

删除当前代码里创建Order的逻辑,只负责生成Stripe结账会话,并将订单所需的关键数据存在会话的metadata中(方便后续Webhook获取)。

修改后的代码示例:

import stripe from '@stripe/stripe-js';
import { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  const stripeInstance = stripe(process.env.STRIPE_SECRET_KEY!);
  const { items, userId, userEmail } = req.body;

  // 创建Stripe结账会话
  const session = await stripeInstance.checkout.sessions.create({
    payment_method_types: ['card'],
    line_items: items.map(item => ({
      price_data: {
        currency: 'usd',
        product_data: { name: item.name },
        unit_amount: item.price * 100, // Stripe以分为单位计算金额
      },
      quantity: item.quantity,
    })),
    mode: 'payment',
    success_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_URL}/cancel`,
    // 存储订单关键数据,供Webhook使用
    metadata: {
      userId,
      userEmail,
      // 可额外存储商品ID列表等信息,比如:itemIds: items.map(i => i.id).join(',')
    },
  });

  // 仅返回Stripe结账地址,不创建订单
  return res.status(200).json({ checkoutUrl: session.url });
}

2. 新建Stripe Webhook路由,监听支付成功事件

创建/api/stripe-webhook路由,专门处理Stripe发送的支付状态通知,仅在收到checkout.session.completed事件时才创建订单。

代码示例:

import stripe from '@stripe/stripe-js';
import { NextApiRequest, NextApiResponse } from 'next';
import { buffer } from 'micro';
import Order from '../../models/Order'; // 替换为你的Order模型路径

// 关闭Next.js默认的body解析,需要原始请求体验证Stripe签名
export const config = {
  api: { bodyParser: false }
};

const stripeInstance = stripe(process.env.STRIPE_SECRET_KEY!);

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  // 获取原始请求体与Stripe签名
  const requestBuffer = await buffer(req);
  const stripeSignature = req.headers['stripe-signature'] as string;
  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;

  let stripeEvent;
  try {
    // 验证签名,确保请求来自Stripe
    stripeEvent = stripeInstance.webhooks.constructEvent(
      requestBuffer,
      stripeSignature,
      webhookSecret
    );
  } catch (err) {
    return res.status(400).json({ error: `Webhook验证失败: ${(err as Error).message}` });
  }

  // 处理支付成功事件
  if (stripeEvent.type === 'checkout.session.completed') {
    const checkoutSession = stripeEvent.data.object as stripe.Checkout.Session;
    const { userId, userEmail } = checkoutSession.metadata!;

    // 从会话中提取订单数据并创建订单
    const newOrder = new Order({
      userId,
      userEmail,
      stripeSessionId: checkoutSession.id,
      totalAmount: checkoutSession.amount_total! / 100, // 转换回元单位
      status: 'paid',
      // 补充商品列表等字段,可从metadata的itemIds查询数据库获取
    });

    await newOrder.save();
    console.log(`订单已创建: ${newOrder._id}`);
  }

  return res.status(200).json({ received: true });
}

3. 配置Stripe后台Webhook

  • 登录Stripe Dashboard,进入Webhooks页面;
  • 添加端点,地址填写你的线上域名/api/stripe-webhook;
  • 选择监听checkout.session.completed事件;
  • 复制生成的Webhook Secret,存入你的环境变量STRIPE_WEBHOOK_SECRET。

关键注意事项

  • 本地调试时,可使用Stripe CLI转发Webhook请求到本地服务;
  • 必须验证Stripe签名,防止伪造请求触发订单创建;
  • 用户取消支付时,因为订单从未创建,自然不会产生无效订单,无需额外处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 12:55:19