Stripe Checkout窗口加载失败及API响应异常问题排查
问题分析与修复方案
问题背景
我实现了一个两步注册流程:用户提交网页表单后创建用户记录,随后跳转至Stripe Checkout页面存储支付信息。但目前Stripe Checkout窗口无法加载,且出现提示:API resolved without sending a response for /api/auth/register, this may result in stalled requests.
请问我在每一步处理req和res的方式是否存在问题?
原代码:/api/auth/register.ts
import prisma from '@/libs/client'; const bcrypt = require('bcrypt'); export default async function handler(req, res) { const saltRounds = 10; if (req.method === 'POST') { if ( req.body.email && req.body.password && req.body.confirmPassword && req.body.tos ) { if (req.body.password == req.body.confirmPassword) { const registeredUser = await prisma.appUsers.create({ data: { email: req.body.email.toLowerCase(), password: bcrypt.hashSync(req.body.password, saltRounds), termsOfService: req.body.tos, }, }); const userValues = { email: registeredUser.email, password: registeredUser.password, }; return fetch(`${req.headers.origin}/api/stripe/checkout`, { method: 'POST', mode: 'cors', cache: 'no-cache', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', }, redirect: 'follow', referrerPolicy: 'no-referrer', body: JSON.stringify(userValues, null, 2), }).catch((error) => { console.log(error); return res.status(200).json({ error: 'Password does not match', }); }); } else { return res.status(200).json({ error: 'Password does not match', }); } } else { return res.status(200).json({ error: 'Form not filled in completely', }); } } else { return res.status(200).json({ error: 'Server error. We are aware and looking into the issue', }); } }
原代码:/api/stripe/checkout.ts
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); export default async function handler(req, res) { if (req.method === 'POST') { console.log(req.body); // const plan = req.body.plan; const plan = 'starter'; var pricing; switch (plan) { case 'starter': pricing = process.env.STRIPE_PRICE_ID; break; default: pricing = process.env.STRIPE_PRICE_ID; } console.log(plan); // const { priceId } = req.body; const priceId = process.env.STRIPE_PRICE_ID; try { console.log('try - statement'); console.log(priceId); const session = await stripe.checkout.sessions.create({ mode: 'subscription', payment_method_types: ['card'], line_items: [ { price: pricing, quantity: 1, }, ], customer_email: req.body.email, success_url: `${req.headers.origin}/app/feed/?ref=checkout-success&session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${req.headers.origin}/app/feed/?ref=checkout-skip`, }); console.log(await session); console.log('303 redirect'); res.send({ sessionId: session.id, }); } catch (e) { res.status(400); return res.send({ error: { message: e.message, }, }); } } }
核心问题分析
/api/auth/register.ts 问题
- 未返回响应给客户端:创建用户后直接return了fetch调用,但fetch是异步操作,且未将任何结果返回给前端。Next.js API路由要求必须通过
res对象发送响应,否则会触发未响应警告,前端一直等待导致Stripe流程卡死。 - 错误信息混淆:fetch的catch块返回"Password does not match",但该分支是处理Stripe接口调用失败的错误,和密码不匹配无关,误导调试。
- 宽松相等判断:用
==比较密码,应改为===严格相等避免类型转换问题。 - 错误状态码错误:所有错误都返回200状态码,前端无法正确识别错误类型,应使用4xx(客户端错误)或5xx(服务器错误)状态码。
/api/stripe/checkout.ts 问题
- 未处理非POST请求:收到GET等其他方法请求时,不会返回任何响应,同样触发未响应警告。
- 代码冗余:plan固定为'starter',switch判断和priceId变量完全多余,直接使用
process.env.STRIPE_PRICE_ID即可。 - 无效await:
console.log(await session)中session已是await后的结果,无需再次await。 - 响应格式不规范:成功响应使用
res.send(),应改为res.status(200).json()保持格式统一。
修复后的代码
修复版:/api/auth/register.ts
import prisma from '@/libs/client'; const bcrypt = require('bcrypt'); export default async function handler(req, res) { const saltRounds = 10; // 仅允许POST请求 if (req.method !== 'POST') { return res.status(405).json({ error: '仅支持POST请求' }); } const { email, password, confirmPassword, tos } = req.body; // 校验表单完整性 if (!email || !password || !confirmPassword || !tos) { return res.status(400).json({ error: '表单未填写完整' }); } // 校验密码一致性 if (password !== confirmPassword) { return res.status(400).json({ error: '两次输入的密码不一致' }); } try { // 创建用户记录 const registeredUser = await prisma.appUsers.create({ data: { email: email.toLowerCase(), password: bcrypt.hashSync(password, saltRounds), termsOfService: tos, }, }); const userValues = { email: registeredUser.email, password: registeredUser.password, }; // 调用Stripe Checkout接口 const stripeResponse = await fetch(`${req.headers.origin}/api/stripe/checkout`, { method: 'POST', mode: 'cors', cache: 'no-cache', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(userValues), }); if (!stripeResponse.ok) { throw new Error('调用Stripe接口失败'); } // 将Stripe会话ID返回给前端,由前端触发Checkout跳转 const sessionData = await stripeResponse.json(); return res.status(200).json(sessionData); } catch (error) { console.error('注册流程错误:', error); return res.status(500).json({ error: '服务器内部错误' }); } }
修复版:/api/stripe/checkout.ts
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); export default async function handler(req, res) { // 仅允许POST请求 if (req.method !== 'POST') { return res.status(405).json({ error: '仅支持POST请求' }); } try { // 创建Stripe Checkout会话 const session = await stripe.checkout.sessions.create({ mode: 'subscription', payment_method_types: ['card'], line_items: [ { price: process.env.STRIPE_PRICE_ID, quantity: 1, }, ], customer_email: req.body.email, success_url: `${req.headers.origin}/app/feed/?ref=checkout-success&session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${req.headers.origin}/app/feed/?ref=checkout-skip`, }); // 返回会话ID给前端 return res.status(200).json({ sessionId: session.id }); } catch (e) { console.error('Stripe会话创建错误:', e); return res.status(400).json({ error: { message: e.message, }, }); } }
关键修复说明
- 确保所有分支返回响应:每个代码路径都通过
res对象发送响应,彻底解决未响应警告。 - 正确转发Stripe会话结果:register接口调用Stripe接口后,将会话ID返回给前端,前端需调用
stripe.redirectToCheckout({ sessionId })打开Checkout窗口(后端无法直接触发前端跳转)。 - 规范错误处理:使用正确的HTTP状态码,错误信息准确对应问题类型,便于调试。
- 简化冗余代码:移除不必要的变量和判断,提升代码可读性。
内容的提问来源于stack exchange,提问作者cphill
相关产品推荐
相关产品推荐

