在 Next.js 中使用 Stripe Checkout 时,不应在创建会话后立即创建订单;而应通过监听 checkout.session.completed Webhook 事件,在用户真实完成支付后再创建并履行订单,从而杜绝未付款订单的产生。
在 next.js 中使用 stripe checkout 时,不应在创建会话后立即创建订单;而应通过监听 `checkout.session.completed` webhook 事件,在用户真实完成支付后再创建并履行订单,从而杜绝未付款订单的产生。
在典型的电商流程中,一个常见误区是:用户点击“支付”后,服务端同步调用 stripe.checkout.sessions.create() 并立刻保存订单到数据库,再将用户重定向至 Stripe 页面。这种做法存在严重逻辑缺陷——只要会话创建成功,无论用户最终是否付款、是否关闭页面或主动取消,订单都已生成,造成数据不一致与库存误扣等问题。
✅ 正确做法是采用事件驱动架构:
- 仅创建 Checkout Session,不创建订单;
- 将用户重定向至 session.url;
- 配置 Stripe Webhook 端点(如 /api/webhook),监听 checkout.session.completed 事件;
- 在 Webhook 处理器中验证签名、获取 Session 数据,并此时才创建订单 + 扣减库存 + 发送通知。
以下是关键代码示例:
✅ /api/stripe/route.ts(创建会话,不建订单)
import { NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
export async function POST(req: Request) {
const { productId, quantity } = await req.json();
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{
price_data: { /* ... */ },
quantity,
}],
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/cart`,
metadata: { productId }, // 可用于后续关联商品
});
return NextResponse.json({ url: session.url });
}
✅ /api/webhook/route.ts(仅在此处创建订单)
import { NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
import { createOrder } from '@/lib/orders'; // 自定义订单创建逻辑
export async function POST(req: Request) {
const rawBody = await req.text();
const signature = req.headers.get('stripe-signature');
let event;
try {
event = stripe.webhooks.constructEvent(
rawBody,
signature!,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (err) {
console.error(`Webhook signature verification failed: ${err}`);
return new NextResponse(`Webhook Error: ${err}`, { status: 400 });
}
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
const productId = session.metadata?.productId;
// ✅ 安全创建订单:验证 session.payment_status === 'paid'
if (session.payment_status === 'paid') {
await createOrder({
sessionId: session.id,
productId,
amount: session.amount_total,
currency: session.currency,
customerEmail: session.customer_details?.email,
});
}
}
return NextResponse.json({ received: true });
}
⚠️ 注意事项:
- 必须验证 Webhook 签名(constructEvent),防止伪造请求;
- checkout.session.completed 事件不等于“支付成功”,需额外校验 session.payment_status === 'paid'(尤其在 mode: 'payment' 下);
- Webhook 端点需部署为公网可访问(开发时可用 ngrok 或 Vercel 预览环境),并在 Stripe Dashboard 中正确配置;
- 建议对 Webhook 处理做幂等性设计(如用 session.id 做唯一索引),避免重复创建订单。
总结:订单生命周期应严格绑定于真实支付结果,而非会话创建动作。通过 Webhook 实现异步、可靠、可审计的订单履约,是 Stripe 集成的最佳实践,也是保障业务数据准确性的核心防线。











