Flask+Stripe表单提交后跳转支付页遇CORS跨域问题求助
背景
- 目标:开发基于HTML、Bootstrap、JavaScript的网站,支持用户添加任意数量WTForms表单,提交后由Flask后端验证并存入数据库,最终跳转至Stripe托管的支付页面。
- 已完成:实现用户填写任意数量表单并以JSON列表提交至后端,后端转换为WTForms验证并存入数据库的功能。
- 遇到的错误:
Access to fetch at 'https://checkout.stripe.com/c/pay/cs_test_a1SrbQBbElmNFdfuSsF...' (redirected from 'http://localhost:4242/test') from origin 'http://localhost:4242' 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.
问题根源
你用fetch发起AJAX请求到后端/test接口,后端返回302重定向到Stripe的支付URL。此时fetch会自动跟随重定向,向Stripe的地址发起请求,但Stripe的服务器不会为localhost:4242设置Access-Control-Allow-Origin头,浏览器出于安全策略拦截了这个跨域请求,导致CORS错误。
解决方法
方案1:后端返回Stripe支付URL,前端主动跳转
修改后端,不再返回重定向,而是直接返回Stripe的支付URL;前端拿到URL后用window.location.href跳转,绕开AJAX的CORS限制。
后端修改(create_checkout_session和test接口):
# 修改create_checkout_session,返回JSON格式的URL @app.route('/create_checkout_session', methods=["GET", "POST"]) def create_checkout_session(): try: product_id = session.get('product_id') domain_url = os.getenv('DOMAIN') checkout_session = stripe.checkout.Session.create( line_items=[ { 'price': product_id, 'quantity': 1, }, ], mode='subscription', success_url=domain_url + url_for('success'), cancel_url=domain_url + '/cancel', ) # 返回JSON格式的支付URL return {'url': checkout_session.url} except Exception as e: return {'error': str(e)}, 400 # 修改test接口,返回create_checkout_session的结果 @app.route('/test', methods=["GET", "POST"]) def test(): if request.method == "POST": # ... 原有验证逻辑保持不变 ... if validForms: # 不再重定向,而是调用create_checkout_session获取URL并返回 checkout_response = create_checkout_session() return checkout_response test_form = TestForm() return render_template("test.html", form=test_form)
前端修改:
fetch('/test', { method: 'POST', body: JSON.stringify(wholeJson), headers: { 'Content-Type': 'application/json' } }).then(response => { if (response.ok) { return response.json(); // 解析后端返回的JSON } else { throw new Error('Server error'); } }).then(data => { if (data.url) { window.location.href = data.url; // 直接跳转至Stripe支付页 } else { console.error('Error:', data.error); } }).catch(error => { console.error('Error:', error); });
方案2:改用普通表单提交(更简便)
放弃AJAX请求,直接用HTML表单提交数据。因为浏览器处理表单提交的重定向不受CORS限制,后端返回的302重定向会直接让页面跳转,不会触发AJAX的跨域检查。
前端修改:
把收集到的表单数据隐藏在一个表单里,点击按钮时提交这个表单:
<!-- 在页面中添加一个隐藏表单 --> <form id="hidden-form" method="POST" action="/test"> <input type="hidden" id="form-data" name="form_data"> </form> <script> document.addEventListener('DOMContentLoaded', () => { var button = document.getElementById('submit'); button.addEventListener('click', event => { event.preventDefault(); // 阻止原有按钮的默认行为 var wholeJson = {}; // ... 原有收集表单数据的逻辑保持不变 ... // 将JSON数据转为字符串,放入隐藏字段 document.getElementById('form-data').value = JSON.stringify(wholeJson); // 提交隐藏表单 document.getElementById('hidden-form').submit(); }); }); </script>
后端修改:
修改test接口,从表单字段中获取JSON数据:
import json @app.route('/test', methods=["GET", "POST"]) def test(): if request.method == "POST": # 从表单字段获取JSON数据 whole_json = json.loads(request.form.get('form_data')) product_name : str = whole_json['product_name'] # ... 后续验证逻辑保持不变 ... if validForms: return redirect(url_for('create_checkout_session')) test_form = TestForm() return render_template("test.html", form=test_form)
总结
方案2更简便,不需要修改Stripe相关的后端逻辑,直接利用浏览器原生的表单提交机制避开CORS问题;方案1适合需要保留AJAX交互的场景。
内容的提问来源于stack exchange,提问作者monre

