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

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 问题

  1. 未返回响应给客户端:创建用户后直接return了fetch调用,但fetch是异步操作,且未将任何结果返回给前端。Next.js API路由要求必须通过res对象发送响应,否则会触发未响应警告,前端一直等待导致Stripe流程卡死。
  2. 错误信息混淆:fetch的catch块返回"Password does not match",但该分支是处理Stripe接口调用失败的错误,和密码不匹配无关,误导调试。
  3. 宽松相等判断:用==比较密码,应改为===严格相等避免类型转换问题。
  4. 错误状态码错误:所有错误都返回200状态码,前端无法正确识别错误类型,应使用4xx(客户端错误)或5xx(服务器错误)状态码。

/api/stripe/checkout.ts 问题

  1. 未处理非POST请求:收到GET等其他方法请求时,不会返回任何响应,同样触发未响应警告。
  2. 代码冗余:plan固定为'starter',switch判断和priceId变量完全多余,直接使用process.env.STRIPE_PRICE_ID即可。
  3. 无效await:console.log(await session)中session已是await后的结果,无需再次await。
  4. 响应格式不规范:成功响应使用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,
      },
    });
  }
}

关键修复说明

  1. 确保所有分支返回响应:每个代码路径都通过res对象发送响应,彻底解决未响应警告。
  2. 正确转发Stripe会话结果:register接口调用Stripe接口后,将会话ID返回给前端,前端需调用stripe.redirectToCheckout({ sessionId })打开Checkout窗口(后端无法直接触发前端跳转)。
  3. 规范错误处理:使用正确的HTTP状态码,错误信息准确对应问题类型,便于调试。
  4. 简化冗余代码:移除不必要的变量和判断,提升代码可读性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 00:47:40