My objective: I am developing a website (HTML, Boostrap, Javascript) where the user can add and fill an arbitrary number of forms. The user then submits these forms, and they are processed in the backend (Python, Flask). The forms are WTForms, and they are validated in the backend before being added to a database. Finally, the user is redirected to a stripe-hosted stripe checkout page.
My progress: I have successfully managed to allow the user to fill an arbitrary number of forms which are then posted as a list of json objects. In the backend, I convert them back to WTForms (I think this is right), validate them, and add them to a database.
My issue: I am struggling with the last part of my objective, ie redirecting the user to a stripe-hosted stripe checkout page. I am getting the following CORS-related issue:
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.
I have done quite a bit of reading and found this helpful stackoverflow post (No 'Access-Control-Allow-Origin' header is present on the requested resource—when trying to get data from a REST API), but all of this is quite new to me and I don't fully understand where the issue is coming from. Most importantly, I don't understand how to fix it, nor if there are easier ways to achieve my initial objective or if I'm making my life too difficult in some ways.
The following are the related segments of my code so far:
Frontend (related script inside test.html file):
<script>
document.addEventListener('DOMContentLoaded', () => {
var button = document.getElementById('submit');
// Add event listener for button click
button.addEventListener('click', event => {
var wholeJson = {};
if(productId.trim === '') {
console.log('product id is empty but it shouldn\'t');
return;
}
wholeJson['product_name'] = productId;
wholeJson['forms'] = [];
const forms = document.querySelectorAll('form');
// Gather data from all forms
forms.forEach(form => {
const formData = new FormData(form);
// Convert FormData to JSON manually
var jsonForm = {};
formData.forEach((value, key) => {
jsonForm[key] = value;
});
wholeJson['forms'].push(jsonForm);
});
// Send data from all forms to the server
fetch('/test', {
method: 'POST',
body: JSON.stringify(wholeJson), // Convert form data to JSON
headers: {
'Content-Type': 'application/json'
}
}).then(response => {
console.log('here');
// Handle response from server if needed
if (response.ok) {
console.log('response ok');
// Redirect or perform other actions
window.location.href = response.url;
} else {
console.log('response NOT ok');
// Handle error
console.error('Server responded with an error:', response.statusText);
}
}).catch(error => {
console.log('error');
// Handle fetch error
console.error('Error:', error);
});
});
});
</script>
Backend:
import requests
import stripe
from datetime import datetime, timedelta
from flask import Flask, session, request, url_for, redirect
from wtforms import StringField, DateField
from flask_wtf import FlaskForm
class TestForm(FlaskForm):
fieldA = StringField(label='Field A', validators=[DataRequired()])
fieldB = StringField(label='Field B', validators=[DataRequired()])
date = DateField(label='Date', validators=[DataRequired()], default=(datetime.now() + timedelta(days=1)).date())
@app.route('/test', methods=["GET", "POST"])
def test():
if request.method == "POST":
# Handle form submission
product_name : str = request.json['product_name']
product_id = None
if product_name in products_to_id:
product_id = products_to_id[product_name]
session['product_id'] = product_id
session['json_tests'] = []
forms = request.json['forms']
validForms: bool = True
for formJson in forms:
form_input = ImmutableMultiDict(formJson)
test_form: TestForm = TestForm(form_input)
if test_form.validate_on_submit():
date = datetime.strptime(formJson["date"], date_format).date()
# create Test object from json (unrelated)
test = Test(
fieldA=formJson["fieldA"],
fieldB=formJson["fieldB"],
date=date
)
session['json_tests'].append(test.to_json())
else:
print(test_form.errors)
validForms = False
break
if validForms:
return redirect(url_for('create_checkout_session'))
test_form = TestForm()
return render_template("test.html", form=test_form)
@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',
)
except Exception as e:
return str(e)
return redirect(checkout_session.url, code=303)
The issue is that your backend is attempting to do a HTTP redirect, which is not compatible with the fetch
/AJAX approach your frontend code is using(you can not redirect from a fetch
).
The most straightforward way to fix it is to change the backend to do something like this
return jsonify(url=checkout_session.url)
instead of
return redirect(checkout_session.url, code=303)
and then on the frontend you can access the URL and redirect the same way you're mostly doing already.
if (response.ok) {
console.log('response ok');
response.json().then(respJson => {
// Redirect or perform other actions
window.location.href = respJson.url;
})
}