Stripe /create-payment-intent接口POST 404报错求助
解决Stripe支付接口POST 404错误问题
问题描述
开发简易Stripe定期支付处理器时,调用/create-payment-intent接口持续出现POST 404错误,控制台报错如下。通过Insomnia测试该接口可正常响应,但前端集成时调用失败。
报错日志
POST http://localhost:5173/create-payment-intent 404 (Not Found) (anonymous) @ App.jsx:18 commitHookEffectListMount @ react-dom_client.js?v=1b04ad3d:16904 invokePassiveEffectMountInDEV @ react-dom_client.js?v=1b04ad3d:18320 invokeEffectsInDev @ react-dom_client.js?v=1b04ad3d:19697 commitDoubleInvokeEffectsInDEV @ react-dom_client.js?v=1b04ad3d:19682 flushPassiveEffectsImpl @ react-dom_client.js?v=1b04ad3d:19499 flushPassiveEffects @ react-dom_client.js?v=1b04ad3d:19443 (anonymous) @ react-dom_client.js?v=1b04ad3d:19324 workLoop @ react-dom_client.js?v=1b04ad3d:197 flushWork @ react-dom_client.js?v=1b04ad3d:176 performWorkUntilDeadline @ react-dom_client.js?v=1b04ad3d:384 Show 10 more frames Show less VM149:1 Uncaught (in promise) SyntaxError: Unexpected end of JSON input at App.jsx:23:26
相关代码
App.jsx代码
import React, { useState, useEffect } from "react"; import { loadStripe } from "@stripe/stripe-js"; import { Elements } from "@stripe/react-stripe-js"; import CheckoutForm from "./CheckoutForm"; import "./App.css"; // Make sure to call loadStripe outside of a component’s render to avoid // recreating the Stripe object on every render. // This is your test publishable API key. const stripePromise = loadStripe("pk_test_51P0HqXLUshuizymb87sieUj8dR6IrsTqqQGINvIrdWpLDPgTMOEM1OkvnDpPGyxa4lUv7tXDlShU5SJjdV3RduZg00Y6RRHSi8"); export default function App() { const [clientSecret, setClientSecret] = useState(""); useEffect(() => { // Create PaymentIntent as soon as the page loads fetch("/create-payment-intent", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ items: [{ id: "xl-tshirt" }] }), }) .then((res) => res.json()) .then((data) => setClientSecret(data.clientSecret)); }, []); const appearance = { theme: 'stripe', }; const options = { clientSecret, appearance, }; return ( <div className="App"> {clientSecret && ( <Elements options={options} stripe={stripePromise}> <CheckoutForm /> </Elements> )} </div> ); }
CheckoutForm.jsx代码
import React, { useEffect, useState } from "react"; import { PaymentElement, useStripe, useElements } from "@stripe/react-stripe-js"; export default function CheckoutForm() { const stripe = useStripe(); const elements = useElements(); const [message, setMessage] = useState(null); const [isLoading, setIsLoading] = useState(false); useEffect(() => { if (!stripe) { return; } const clientSecret = new URLSearchParams(window.location.search).get( "payment_intent_client_secret" ); if (!clientSecret) { return; } stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => { switch (paymentIntent.status) { case "succeeded": setMessage("Payment succeeded!"); break; case "processing": setMessage("Your payment is processing."); break; case "requires_payment_method": setMessage("Your payment was not successful, please try again."); break; default: setMessage("Something went wrong."); break; } }); }, [stripe]); const handleSubmit = async (e) => { e.preventDefault(); if (!stripe || !elements) { // Stripe.js hasn't yet loaded. // Make sure to disable form submission until Stripe.js has loaded. return; } setIsLoading(true); const { error } = await stripe.confirmPayment({ elements, confirmParams: { // Make sure to change this to your payment completion page return_url: "http://localhost:3000", }, }); // This point will only be reached if there is an immediate error when // confirming the payment. Otherwise, your customer will be redirected to // your `return_url`. For some payment methods like iDEAL, your customer will // be redirected to an intermediate site first to authorize the payment, then // redirected to the `return_url`. if (error.type === "card_error" || error.type === "validation_error") { setMessage(error.message); } else { setMessage("An unexpected error occurred."); } setIsLoading(false); }; const paymentElementOptions = { layout: "tabs" } return ( <form id="payment-form" onSubmit={handleSubmit}> <PaymentElement id="payment-element" options={paymentElementOptions} /> <button disabled={isLoading || !stripe || !elements} id="submit"> <span id="button-text"> {isLoading ? <div className="spinner" id="spinner"></div> : "Pay now"} </span> </button> {/* Show any error or success messages */} {message && <div id="payment-message">{message}</div>} </form> ); }
server.js代码
const express = require("express"); const app = express(); require("dotenv").config(); const stripe = require("stripe")(process.env.SECRET_KEY); app.use(express.static("public")); app.use(express.json()); const calculateOrderAmount = (items) => { // Your calculation logic here return 1400; // Example amount, replace with actual calculation }; app.post("/create-payment-intent", async (req, res) => { const { items } = req.body; try { // Create a PaymentIntent with the order amount and currency const paymentIntent = await stripe.paymentIntents.create({ amount: calculateOrderAmount(items), currency: "usd", // In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default. automatic_payment_methods: { enabled: true, }, }); res.json({ clientSecret: paymentIntent.client_secret, }); } catch (error) { console.error("Error creating payment intent:", error); res.status(500).json({ error: "Failed to create payment intent" }); } }); app.listen(5000, () => { console.log("Server is running on http://localhost:5000"); });
问题原因与解决方案
核心原因
前端开发服务器运行在localhost:5173,后端服务器运行在localhost:5000,前端代码中使用相对路径/create-payment-intent发起请求时,会直接请求前端服务器的5173端口,而非后端的5000端口,导致404错误。而Insomnia测试正常是因为直接请求了后端的5000端口。
解决方案
有两种可行方案,任选其一即可:
方案1:配置前端代理(推荐)
以Vite为例,在项目根目录的vite.config.js中添加代理配置,将前端的API请求转发到后端服务器:
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { proxy: { '/create-payment-intent': { target: 'http://localhost:5000', changeOrigin: true } } } })
配置完成后重启前端开发服务器,此时前端的相对路径请求会自动转发到后端5000端口。
方案2:修改请求为绝对路径
直接修改App.jsx中的fetch URL为后端服务器的完整地址:
fetch("http://localhost:5000/create-payment-intent", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ items: [{ id: "xl-tshirt" }] }), })
额外优化点
检查CheckoutForm.jsx中的return_url,当前设置为http://localhost:3000,与前端实际运行的5173端口不符,建议修改为http://localhost:5173,避免支付完成后跳转错误。
内容的提问来源于stack exchange,提问作者user23947361
相关产品推荐
相关产品推荐

