React集成Stripe结账传参时触发CORS跨域问题求助
问题场景
我在React应用里集成Stripe结账功能,购物车页面的结账按钮调用以下表单提交函数:
const handleSubmit = async(e) => { e.preventDefault(); try { const response = await fetch('/create-checkout-session', { method: 'POST', // mode: "no-cors", // no-cors, *cors, same-origin headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({productId}), }); if (response.ok) { console.log('Request sent successfully'); } else { console.log('Request failed'); } } catch (error) { console.log('Error occurred during the request', error); } }
该请求对应后端API:
app.post('/create-checkout-session', async (req, res) => { // Get the selected product ID from the frontend const { productId } = req.body; console.log(productId) // Define a mapping of product IDs to prices const priceMap = { bricky_id: 'price_1NA7O1JVu65LdnVcBljr473q', sub_id: 'price_1NA64tJVu65LdnVcfgUwbhUA', // Add more product IDs and their corresponding prices here }; // Get the price based on the selected product ID const price = priceMap[productId]; console.log(price); if (!price) { // Handle invalid product ID return res.status(400).json({ error: 'Invalid product selected' }); } const session = await stripe.checkout.sessions.create({ line_items: [ { price: price, quantity: 1, }, { price: 'price_1NA64tJVu65LdnVcfgUwbhUA', quantity: 1 } ], mode: 'subscription', success_url: `http://localhost:4000/order-success`, cancel_url: `http://localhost:4000/order-preview`, }); res.redirect(303, session.url); // res.redirect({url: session.url}); // res.redirect(session.url); });
React已配置代理:"proxy": "http://localhost:4000",服务端也添加了CORS配置:
app.use(cors()) app.use(function(req, res, next) { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); res.setHeader('Access-Control-Allow-Credentials', true); next(); });
无请求体的简单调用能正常跳转Stripe结账页,但传递参数后出现CORS错误:
Access to fetch at 'https://checkout.stripe.com/c/pay/...' (redirected from 'http://localhost:3000/create-checkout-session') from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
OrderPreview.js:11 GET https://checkout.stripe.com/c/pay/... net::ERR_FAILED
问题根源
核心不是后端CORS配置问题,而是前端用fetch发起请求后,后端直接重定向到Stripe域名——fetch会自动跟随重定向去请求Stripe的URL,但Stripe服务器不会给你的前端域名添加CORS响应头,因此浏览器拦截了这个跨域请求。
另外,React代理只作用于/create-checkout-session的请求阶段,重定向后的请求直接从浏览器发往Stripe,不再走代理。
解决方法
方案1:后端返回会话URL,前端主动跳转(Stripe推荐)
这是最直接的解决方案,避开fetch处理跨域重定向:
- 修改后端API,替换重定向逻辑,返回会话URL:
// 删除res.redirect(303, session.url); res.json({ url: session.url });
- 修改前端
handleSubmit函数,拿到URL后主动跳转:
const handleSubmit = async(e) => { e.preventDefault(); try { const response = await fetch('/create-checkout-session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({productId}), }); if (response.ok) { const { url } = await response.json(); // 直接让浏览器跳转Stripe结账页 window.location.href = url; } else { console.log('Request failed'); } } catch (error) { console.log('Error occurred during the request', error); } }
方案2:清理冗余CORS配置,避免冲突
当前服务端同时使用了cors()包和自定义CORS中间件,两者可能产生冲突,建议只保留一种:
- 保留
cors包的配置,更简洁且不易出错:
const cors = require('cors'); // 生产环境建议替换为实际前端域名,不要用* app.use(cors({ origin: 'http://localhost:3000', credentials: true })); // 删除自定义的CORS中间件代码
方案3:确认React代理生效
检查代理配置并重启服务:
- 确认
package.json中的proxy字段为"http://localhost:4000" - 重启React开发服务器,避免缓存导致代理未生效
关键注意事项
- 禁止用
fetch/axios处理Stripe的重定向,必须让浏览器直接发起跳转请求,绕过跨域限制 - 生产环境不要用
*作为Access-Control-Allow-Origin,指定具体前端域名更安全 - Stripe结账会话URL是一次性的,不要缓存,每次结账都需重新创建会话
内容的提问来源于stack exchange,提问作者jamesj9005

