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

Stripe支付处理报错:Unhandled Rejection (TypeError): Failed to fetch求助

Stripe支付流程Fetch请求异常排查与修复

问题描述

对接Stripe支付网关时遇到Unhandled Rejection (TypeError): Failed to fetch错误,无法正常完成支付流程。

前端结账弹窗组件代码

import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Modal from '@mui/material/Modal';
import products from './product'
import { useContext } from 'react';
import { Elements } from "@stripe/react-stripe-js";
import { useState, useEffect} from 'react';
import { loadStripe } from "@stripe/stripe-js";

import cart from '../context/cart'
import { CartContext } from '../context/cart';
const style = {
  position: 'absolute',
  top: '50%',
  left: '50%',
  transform: 'translate(-50%, -50%)',
  width: "100%",
  bgcolor: 'background.paper',
  border: '2px solid #000',
  boxShadow: 24,
  p: 4,
};
export default function BasicModal() {
  const [open, setOpen] = React.useState(false);
  const handleOpen = () => setOpen(true);
  const handleClose = () => setOpen(false);
  const stripePromise = loadStripe("pk_test_35p114pH8oNuHX72SmrvsFqh00Azv3ZaIA");


  const { cartItems, addToCart, removeFromCart, clearCart, getCartTotal } = useContext(CartContext)
  useEffect(() => {
    // storing input name
    localStorage.getItem("cartItems", JSON.stringify(cartItems));
  }, [cartItems])



// payment is processed here


  const makePayment = async () => { 
    const stripe = await loadStripe("pk_test_35p"); 
    const body = { cartItems }; 
    const headers = { 
      "Content-Type": "application/json", 
    }; 
  
    const response = await fetch( 
      "localhost:8000/api/create-checkout-session", 
      { 
        method: "POST", 
        headers: headers, 
        body: JSON.stringify(body), 
      } 
    ); 
  
    const session = await response.json(); 
  
    const result = stripe.redirectToCheckout({ 
      sessionId: session.id, 
    }); 
    if (result.error) { 
      console.log(result.error); 
    } 
  }; 
  return (
    <div>
      <Button onClick={handleOpen}   >

        ({cartItems.length})
        Check Out</Button>
      <Modal
      disableScrollLock={true}
      fullScreen={true}
        open={open}
        onClose={handleClose}
        aria-labelledby="modal-modal-title"
        aria-describedby="modal-modal-description"
      >
        <Box sx={style}>
          <>
            <div className="flex-col flex items-center bg-white gap-8 p-10 text-black text-sm">
              <h1 className="text-2xl font-bold">Cart</h1>
              <div className="flex flex-col gap-4">
  
  
                {cartItems.map((item) => (
                  <div className="flex justify-between items-center" key={item.id}>
                    <div className="flex gap-4">
                      <img src={item.imageId} alt={item.title} className="rounded-md h-24" />
                      <div className="flex flex-col">
                        <h1 className="text-lg font-bold">{item.title}</h1>
                        <p className="text-gray-600">{item.price}</p>
                        <p className="text-gray-600">{item.description}</p>

                      </div>
                    </div>
                    <div className="flex gap-4">
                      <button
                        className="px-4 py-2 bg-gray-800 text-white text-xs font-bold uppercase rounded hover:bg-gray-700 focus:outline-none focus:bg-gray-700"
                        onClick={() => {
                          addToCart(item)
                        }}
                      >
                        +
                      </button>
                      <p>{item.quantity}</p>
                      <button
                        className="px-4 py-2 bg-gray-800 text-white text-xs font-bold uppercase rounded hover:bg-gray-700 focus:outline-none focus:bg-gray-700"
                        onClick={() => {
                          removeFromCart(item)
                        }}
                      >
                        -
                      </button>

                    </div>
                  </div>
                ))}
              </div>
              {
                cartItems.length > 0 ? (
                  <div className="flex flex-col justify-between items-center">
                    <h1 className="text-lg font-bold">Total: ${getCartTotal()}</h1>
                    <button
                      className="px-4 py-2 bg-gray-800 text-white text-xs font-bold uppercase rounded hover:bg-gray-700 focus:outline-none focus:bg-gray-700"
                      onClick={() => {
                        clearCart()
                      }}
                    >
                      Clear cart
                    </button>
                    <Button variant="primary" onClick={makePayment} >Check out
                      </Button> 
                  </div>
                ) : (
                  <>
                    <h1 className="text-lg font-bold">Your cart is empty</h1>
                  </>
                )
              }
            </div>
            <div class="col d-flex justify-content-center">      
 </div>
          </>
        </Box>
      </Modal>
    </div>
  );
} 

后端Node服务代码

const cors = require("cors"); 
const express = require("express"); 
 
const stripe = require("stripe")("sMK"); 
 
const app = express(); 
 
// Middlewares here 
app.use(express.json()); 
 
// Routes here 
app.get("/", (req, res) => { 
  res.send("Hello World"); 
}); 
 
app.use(cors({origin: "*",}))
// Listen 
app.listen(8000, () => { 
  console.log("Server started at port 8000"); 
}); 

app.post("/api/create-checkout-session", async (req, res) => { 
    const { product } = req.body; 
    const session = await stripe.checkout.sessions.create({ 
      payment_method_types: ["card"], 
      line_items: [ 
        { 
          price_data: { 
            currency: "inr", 
            product_data: { 
              name: product.name, 
            }, 
            unit_amount: product.price * 100, 
          }, 
          quantity: product.quantity, 
        }, 
      ], 
      mode: "payment", 
      success_url: "http://localhost:3000/success", 
      cancel_url: "http://localhost:3000/failure", 
    }); 
    res.json({ id: session.id }); 
  }); 

具体问题与修复方案

1. Fetch请求URL缺少协议头

  • 问题:前端makePayment中请求URL为"localhost:8000/api/create-checkout-session",缺少http://,浏览器会将其解析为相对路径,导致请求失败。
  • 修复:修改为"http://localhost:8000/api/create-checkout-session"

2. Stripe公钥不完整

  • 问题:makePayment里初始化Stripe用了截断的公钥"pk_test_35p",无法正常初始化Stripe实例。
  • 修复:复用组件顶部定义的完整公钥stripePromise,避免重复初始化:
    const stripe = await stripePromise;
    

3. 前后端数据字段不匹配

  • 问题:前端发送的是cartItems数组,但后端解构的是单个product对象,导致product.name等取值报错,返回500错误触发fetch失败。
  • 修复:后端遍历购物车项生成line_items,同时统一货币类型(前端显示美元,后端之前用印度卢比):
    app.post("/api/create-checkout-session", async (req, res) => {
      const { cartItems } = req.body;
      if (!cartItems || cartItems.length === 0) {
        return res.status(400).json({ error: "购物车为空" });
      }
      try {
        const session = await stripe.checkout.sessions.create({
          payment_method_types: ["card"],
          line_items: cartItems.map(item => ({
            price_data: {
              currency: "usd",
              product_data: {
                name: item.title,
                description: item.description
              },
              unit_amount: item.price * 100, // Stripe使用最小货币单位(如美元为分)
            },
            quantity: item.quantity,
          })),
          mode: "payment",
          success_url: "http://localhost:3000/success",
          cancel_url: "http://localhost:3000/failure",
        });
        res.json({ id: session.id });
      } catch (error) {
        console.error("Stripe会话创建错误:", error);
        res.status(500).json({ error: error.message });
      }
    });
    

4. CORS中间件顺序错误

  • 问题:后端app.use(cors)放在了路由之后,导致CORS配置不生效,跨域请求被浏览器拦截。
  • 修复:调整中间件顺序,将CORS放在最前面:
    app.use(cors({origin: "*",}));
    app.use(express.json());
    
    // 之后定义路由...
    app.get("/", (req, res) => { 
      res.send("Hello World"); 
    }); 
    
    app.post("/api/create-checkout-session", async (req, res) => {
      // ...接口逻辑
    });
    
    app.listen(8000, () => { 
      console.log("Server started at port 8000"); 
    });
    

5. Stripe私钥无效

  • 问题:后端初始化Stripe用了截断的私钥"sMK",无法调用Stripe API。
  • 修复:替换为你的完整Stripe测试私钥(以sk_test_开头)。

6. localStorage使用错误

  • 问题:useEffect里用localStorage.getItem存数据,应该用setItem。
  • 修复:
    useEffect(() => {
      localStorage.setItem("cartItems", JSON.stringify(cartItems));
    }, [cartItems])
    

7. 缺失错误处理

  • 问题:前端未处理HTTP错误状态,后端返回错误时会触发未捕获的Promise拒绝。
  • 修复:给makePayment添加完整的错误捕获:
    const makePayment = async () => { 
      try {
        const stripe = await stripePromise;
        const body = { cartItems }; 
        const headers = { 
          "Content-Type": "application/json", 
        }; 
      
        const response = await fetch( 
          "http://localhost:8000/api/create-checkout-session", 
          { 
            method: "POST", 
            headers: headers, 
            body: JSON.stringify(body), 
          } 
        ); 
    
        if (!response.ok) {
          const errorData = await response.json();
          throw new Error(errorData.error || "支付会话创建失败");
        }
      
        const session = await response.json(); 
      
        const result = stripe.redirectToCheckout({ 
          sessionId: session.id, 
        }); 
        if (result.error) { 
          console.error("Stripe跳转错误:", result.error);
          alert("支付失败: " + result.error.message);
        } 
      } catch (error) {
        console.error("支付错误:", error);
        alert("支付失败: " + error.message);
      }
    }; 
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 21:15:53