How to POST stripe custom checkout token to flask backend - javascript

I'm trying to integrate Stripe custom checkout https://stripe.com/docs/checkout#integration-custom with Flask and WTForms. My problem at the moment is the payment form doesn't seem to be posting so the credit card charge cannot be created.
It seems the form is recognised because the token is being posted to stripe's api with a 200 response:
XHRPOST
https://api.stripe.com/v1/tokens
[HTTP/2.0 200 OK 1444ms]
Form data
card[cvc] 123
card[exp_month] 10
card[exp_year] 20
card[name] dev#local.host
card[number] 4242424242424242
email dev#local.host
guid 4a6cfd25-8c4b-4d98-9dd2-9e9c1770e290
key pk_test_DVVO0zxtWjXSZx4yHsZGJxtv
muid c6b9d635-20de-4fc6-8995-5d5b2d165881
payment_user_agent Stripe+Checkout+v3+checkout-manhattan+ (stripe.js/9dc17ab)
referrer http://localhost:8000/subscription/index
sid 494d70dd-e854-497b-945b-de0e96a0d646
time_on_page 26657
validation_type card
However the token (and the form) is not being posted to my server to create the charge that stripe requires.
Here is the javascript code to load stripe custom checkout, which is in /index.html:
<script src="https://checkout.stripe.com/checkout.js"></script>
<form role="form" id = "payment_form" action="{{ url_for('billing.charge') }}" method="post">
{{ form.hidden_tag }}
<input type="hidden" id="stripeToken" name="stripeToken" />
<input type="hidden" id="stripeEmail" name="stripeEmail" />
<div class="form-group">
<div class="col-md-12 button-field" style = "text-align: center;">
<button type="confirm" id = 'confirm' onclick = "runStripe('https://checkout.stripe.com/checkout.js')" class="btn btn-default btn-responsive btn-lg">Confirm Order</button>
</div>
</div>
<script>
var handler = StripeCheckout.configure({
key: "{{ stripe_key }}",
locale: 'auto',
token: function(token) {
// token ID as a hidden field
var form = document.createElement("form");
form.setAttribute('method', "POST");
form.setAttribute('action', "{{ url_for('billing.charge') }}");
form.setAttribute('name', "payment-form");
var inputToken = document.createElement("input");
inputToken.setAttribute('type', "hidden");
inputToken.setAttribute('name', "stripeToken");
inputToken.setAttribute('value', token.id);
form.appendChild(inputToken);
// email as a hidden field
var inputEmail = document.createElement("input");
inputEmail.setAttribute('type', "hidden");
inputEmail.setAttribute('name', "stripeEmail");
inputEmail.setAttribute('value', token.email);
form.appendChild(inputEmail);
document.body.appendChild(form);
}
});
document.getElementById('confirm').addEventListener('click', function(e) {
// Open Checkout with further options:
handler.open({
name: 'Stripe.com',
description: '2 widgets',
amount: '{{ amount }}'
});
e.preventDefault();
});
// Close Checkout on page navigation:
window.addEventListener('popstate', function() {
handler.close();
});
</script>
<script>
document.getElementsByClassName("stripe-button-el")[0].style.display = 'none';
</script>
I have attempted a post method within the html tag with no success. I have also tried adding a form variable within the javascript token to post to my charge route, adapted from this question: Stripe Checkout Link onClick does not process payment
Here is my index and charge routes for reference:
#billing.route('/index', methods=['GET', 'POST'])
def index():
stripe_key = current_app.config.get('STRIPE_PUBLISHABLE_KEY')
amount = 1010
form = CreditCardForm(stripe_key=stripe_key)
return render_template('billing/index.html', stripe_key=stripe_key, form=form)
#billing.route('/charge', methods=['GET', 'POST'])
def charge():
if request.method == 'POST':
customer = stripe.Customer.create(
email = current_user,
source = request.form['stripeToken']
)
charge = stripe.Charge.create(
customer = customer.id,
amount = 2000,
currency = 'usd',
description = 'payment'
)
return render_template('charge.html', customer=customer, charge=charge)

I decided to change the token to jquery, which now seems to work perfectly and is far simpler:
<script>
var handler = StripeCheckout.configure({
key: "{{ stripe_key }}",
locale: 'auto',
token: function(token) {
$(document).ready(function(){
$("#stripeToken").val(token.id);
$("#stripeEmail").val(token.email);
$("#payment_form").submit();
})
</script>
In order for the jquery to be recognised, I also added the script for the jquery package at the top of the html file:
script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
Finally, for anone else who needs help in flask, here is my adjusted route:
#billing.route('/index', methods=['GET', 'POST'])
#handle_stripe_exceptions
#login_required
def index():
stripe_key = current_app.config.get('STRIPE_PUBLISHABLE_KEY')
amount = 1010
form = CreditCardForm(stripe_key=stripe_key, name=current_user.name, amount=amount )
if request.method == 'POST':
customer = stripe.Customer.create(
email='customer#example.com',
source=request.form['stripeToken']
)
charge = stripe.Charge.create(
customer=customer.id,
amount=amount,
currency='usd',
description='Flask Charge'
)
return render_template('billing/index.html', stripe_key=stripe_key, form=form)

Related

JAVASCRIPT: After sending Item to Database (POST) the window.location.reload() does not work. How to refresh the Data in HTML-Template?

I recode a Tutorial on Youtube.
Django, Python, HTML an Javascript.
Everthing works fine exept the window.location.reload() function.
I try some workarounds with
windows.reload(true),
window.href = window.href
location = self.location
and some more.
I have a hunch that the reload is executed before or during the code before the reload. But I do not know.
The goal is to send the data from the input to the database and only then refresh the page.
This ist the Code from the tutorial:
index.html (shortened)
<body>
<header>
<h1>Shoppinglist</h1>
<div id="input-field">
<label for="item-input">Was möchtest du einkaufen?</label>
<input type="text" name="item" id="item-input">
</div>
<button id="btn" onclick="addItem()">+</button>
</header>
<div id="item-table">
{% for row in all_items %}
<div class="list-item">
<input type="checkbox"> {{row.name}}
</div>
{% endfor %}
</div>
<script>
function addItem(){
let itemName = document.getElementById("item-input").value;
let formData = new FormData();
let token = '{{csrf_token}}';
formData.append('itemName', itemName);
formData.append('csrfmiddlewaretoken', token);
fetch('/mylist/', {
method: 'POST',
body: formData
});
window.location.reload();
};
</script>
</body>
</html>
views.py
from django.shortcuts import render
from .models import ShoppingItem
# Create your views here.
def mylist(request):
if request.method == 'POST':
print('Received date: ', request.POST['itemName'])
ShoppingItem.objects.create(name = request.POST['itemName'])
all_items = ShoppingItem.objects.filter(done = 0)
return render(request, 'index.html', {'all_items':all_items})
models.py
from django.db import models
from datetime import date
#Create your models here.
class ShoppingItem(models.Model):
creatDate = models.DateField (default=date.today)
name = models.CharField (max_length =200)
done = models.BooleanField(default=False)
def __str__(self):
return '(' + str(self.id) +') ' +self.name
Try this:
async function addItem() {
let itemName = document.getElementById("item-input").value;
let formData = new FormData();
let token = "{{csrf_token}}";
formData.append("itemName", itemName);
formData.append("csrfmiddlewaretoken", token);
await fetch("/mylist/", {
method: "POST",
body: formData,
});
window.location.reload();
}

Posting Date Data to Django Model

I was wondering if any of you guys here knew how to fix this error, I have been dealing with it for quite a few hours, it has to do with posting json date (a date from a html date picker) to a backend model using the django web framework. Please let me know if my question is unclear.
ViewOrders.html
<form id="form">
<label for="start">Drop Off Date Selector:</label>
<br>
<input type="date" id="dropOffDate" name="drop_Off_Date"
min="2022-01-01" max="3000-12-31">
<button type="submit" value="Continue" class="btn btn-outline-danger" id="submit-drop-off-date" >Submit Drop Off Date</button>
</form>
<script type="text/javascript">
var form = document.getElementById('form')
form.addEventListener('submit', function(e){
e.preventDefault()
submitDropOffData()
console.log("Drop Off Date submitted...")
})
function submitDropOffData() {
var dropOffDateInformation = {
'dropOffDate':null,
}
dropOffDateInformation.dropOffDate = form.drop_Off_Date.value
var url = "/process_drop_off_date/"
fetch(url, {
method:'POST',
headers:{
'Content-Type':'application/json',
'X-CSRFToken':csrftoken,
},
body:JSON.stringify({'drop-off-date':dropOffDateInformation}),
})
.then((response) => response.json())
.then((data) => {
console.log('Drop off date has been submitted...')
alert('Drop off date submitted');
window.location.href = "{% url 'home' %}"
})
}
</script>
Views.py
def processDropOffDate(request):
data = json.loads(request.body)
DropOffDate.objects.create(
DropOffDate=data['drop-off-date']['dropOffDate'],
)
return JsonResponse('Drop off date submitted...', safe=False)
Models.py
class DropOffDate(models.Model):
dropOffDate = models.CharField(max_length=150, null=True)
def __str__(self):
return str(self.dropOffDate)
Errors
The fix to this was that I had created an object with the wrong field name so the Json repsonse was invalid.
class DropOffDate(models.Model):
dropOffDate = models.CharField(max_length=150, null=True)
def __str__(self):
return str(self.dropOffDate)
Changed to:
class DropOffDate(models.Model):
DropOffDate = models.CharField(max_length=150, null=True)
def __str__(self):
return str(self.DropOffDate)

Why Django modelform is submitted with enter key

I'm using Django modelform and it's submitted by pressing the enter key. This is what I wanted, but I don't know why it works. I didn't add any JS codes related to keydown but other codes to practice Ajax. Also, I found out that when there's only one input inside the form, it's submitted with the enter key, but my form has two inputs.
What I'm doing is to add a comment on a post like instagram. I used Ajax to create a comment instance.
models.py
class Comment(models.Model):
parent_post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
author = models.CharField(max_length=10)
content = models.CharField(max_length=100)
forms.py
class CommentForm(ModelForm):
class Meta:
model = Comment
exclude = ["parent_post"]
HTML
{% for post in posts %}
<form method="POST" data-id="{{post.id}}" class="d-flex align-items-center w-100 mt-2">
{% load widget_tweaks %} <!-- this is a django package for easy CSS -->
{% csrf_token %}
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.author.errors }}
{{ form.author|add_class:"form__author"|attr:"placeholder:name" }}
</div>
<div class="fieldWrapper w-100">
{{ form.content.errors }}
{{ form.content|add_class:"form__content"|attr:"placeholder:content" }}
</div>
<button class="btn btn-sm btn-warning w-auto">Write</button>
</form>
{% endfor %}
JS (Here I didn't put the codes after getting JSON response from views.py)
const forms = document.querySelectorAll("form");
forms.forEach((value) => {
const post_id = Number(value.getAttribute("data-id"));
value.addEventListener("submit", (e) => {
e.preventDefault(); // prevents reload, still submits form data but views.py does nothing for this data
writeComment(post_id);
});
const requestWrite = new XMLHttpRequest();
const writeComment = (post_id) => {
const form = document.querySelector(`form[data-id="${post_id}"]`);
const author = form.querySelector(".form__author");
const content = form.querySelector(".form__content");
const url = "/write/";
if (author.value && content.value) {
requestWrite.open("POST", url, true);
requestWrite.setRequestHeader(
"Content-Type",
"application/x-www-form-urlencoded"
);
requestWrite.send(
JSON.stringify({
post_id: post_id,
author: author.value,
content: content.value,
})
);
author.value = null;
content.value = null;
}
};
views.py
#csrf_exempt
def write(request):
req = json.loads(request.body)
post_id = req["post_id"]
author = req["author"]
content = req["content"]
comment = Comment.objects.create(parent_post=get_object_or_404(Post, id=post_id), author=author, content=content)
return JsonResponse({"post_id":post_id, "comment_id":getattr(comment, "id"), "author":author, "content":content})
Thank you!

How to show Django form in template?

I want to display countries and states in Django form, for that I am trying to get data from json, create form, pass json data to form and get state of the country on ajax request. I managed to write the process as far as I learned, but at last form is not rendered on Django template. How can I render Django form with following code structure?
My Model:
from django.db import models
class Address(models.Model):
country = models.CharField(null=True, blank=True, max_length=100)
state = models.CharField(null=True, blank=True, max_length=100)
def __str__(self):
return '{} {}'.format(self.country, self.state)
My Forms.py:
import json
def readJson(filename):
with open(filename, mode="r", encoding="utf-8") as fp:
return json.load(fp)
def get_country():
""" GET COUNTRY SELECTION """
filepath = './static/data/countries_states_cities.json'
all_data = readJson(filepath)
all_countries = [('-----', '---Select a Country---')]
for x in all_data:
y = (x['name'], x['name'])
all_countries.append(y)
return all_countries
def return_state_by_country(country):
""" GET STATE SELECTION BY COUNTRY INPUT """
filepath = './static/data/countries_states_cities.json'
all_data = readJson(filepath)
all_states = []
for x in all_data:
if x['name'] == country:
if 'states' in x:
for state in x['states']:
y = (state['name'], state['name'])
all_states.append(state['name'])
else:
all_states.append(country)
return all_states
class AddressForm(forms.ModelForm):
country = forms.ChoiceField(
choices = get_country(),
required = False,
label='Country / Region*',
widget=forms.Select(attrs={'class': 'form-control', 'id': 'id_country'}),
)
class Meta:
model = Address
fields = ['country']
My Form.html
<form class="" action="" method="post">
{% csrf_token %}
{% for error in errors %}
<div class="alert alert-danger mb-4" role="alert">
<strong>{{ error }}</strong>
</div>
{% endfor %}
<div class="row">
<div class="col-lg-6">
<div class="mb-4">
{{ form.country}}
</div>
</div>
<div class="col-lg-6">
<div class="mb-4">
<div class="form-group">
<label >Select a Province/State</label>
<select id="id_province" class="form-control" name="state">
<option value="-----">Select Province/State</option>
</select>
</div>
</div>
</div>
</div>
</form>
My Views:
def readJson(filename):
with open(filename, mode="r", encoding="utf-8") as fp:
return json.load(fp)
def return_state_by_country(country):
""" GET STATE SELECTION BY COUNTRY INPUT """
filepath = './static/data/countries_states_cities.json'
all_data = readJson(filepath)
all_states = []
for x in all_data:
if x['name'] == country:
if 'states' in x:
for state in x['states']:
y = (state['name'], state['name'])
all_states.append(state['name'])
else:
all_states.append(country)
return all_states
def getProvince(request):
country = request.POST.get('country')
provinces = return_state_by_country(country)
return JsonResponse({'provinces': provinces})
def processForm(request):
context = {}
if request.method == 'GET':
form = AddressForm()
context['form'] = form
return render(request, './ecommerce/checkout.html', context)
if request.method == 'POST':
form = AddressForm(request.POST)
if form.is_valid():
selected_province = request.POST['state']
obj = form.save(commit=False)
obj.state = selected_province
obj.save()
return render(request, './ecommerce/checkout.html', context)
My Ajax:
<script>
$("#id_country").change(function () {
var countryId = $(this).val();
$.ajax({
type: "POST",
url: "{% url 'ecommerce:get-province' %}",
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'country': country
},
success: function (data) {
console.log(data.provinces);
let html_data = '<option value="-----">Select Province/State</option>';
data.provinces.forEach(function (data) {
html_data += `<option value="${data}">${data}</option>`
});
$("#id_province").html(html_data);
}
});
});
</script>
I am trying to print form.country on template but its not working. What could be the problem?
With ModelForms I find that this type of configuration, in which everything falls under class Meta: works.
However im dubious about that get_country() method. If you share it I can take a deeper look and maybe even test it to make sure that it's not doing anything funky.
If your list of countries is somewhat static and not too long you might wanna consider using a TextChoices enum type in your model attribute to limit the choice selection. Django forms will automatically render a dropdown widget listing the items from your enum.
You can checkout this answer if you want to look into that, which further links to the django docs.
class AddressForm(forms.ModelForm):
class Meta:
model = Address
fields = ['country']
widgets = {
"country": forms.ChoiceField(
choices = get_country(),
attrs={
"class": "form-control",
"id": "id_country"
}
),
}
labels = {
"country" : "Company Country Location"
}

Passing Django model properties to JavaScript with the Fetch API

I'm working on an assignment to use the fetch API to do some of the normal things we would have Python do in our views with JavaScript such as adding records or querying the database. One issue I'm running across is passing the normal properties we would see in Django, say a user or username, where it just shows up as a literal user id when I pull it from the sql database with the fetch API. With the views, html and JavaScript I have written now, how would I go about pulling the username with fetch in JavaScript that I can normally grab with a variable or view with a print statement in the Django console, instead of just viewing the user id from the database. I feel like I'm missing a step and I'm just not seeing it.
urls
app_name = "network"
urlpatterns = [
path("", views.index, name="index"),
path("login", views.login_view, name="login"),
path("logout", views.logout_view, name="logout"),
path("register", views.register, name="register"),
# API Routes
path("addpost", views.post, name="post"),
path("<str:navbar_item>", views.viewposts, name="viewposts"),
]
models.py
class User(AbstractUser):
pass
class Profile(models.Model):
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
bio = models.TextField(null=True, blank=True)
# pics
website = models.CharField(max_length=225, null=True, blank=True)
follower = models.ManyToManyField(
User, blank=True, related_name="followed_user") # user following this profile
# profile user that follows this profile
following = models.ManyToManyField(
User, blank=True, related_name="following_user")
def __str__(self):
return f"{self.user}'s' profile id is {self.id}"
def following_users(self):
for username in self.following:
return username
def get_absolute_url(self):
return reverse("network:profile-detail", args=[str(self.id)])
class Post(models.Model):
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name="post_user")
body = models.TextField(max_length=1000)
timestamp = models.DateTimeField(auto_now_add=True)
likes = models.ManyToManyField(User, blank=True, related_name="post_likes")
def __str__(self):
return f"{self.created_by} posted {self.body}"
def get_absolute_url(self):
return reverse("network:post-detail", args=[str(self.id)])
def total_likes(self):
return self.likes.count()
class Meta:
ordering = ["-timestamp"]
views.py
def index(request):
if request.user.is_authenticated:
return render(request, "network/index.html", {})
else:
return HttpResponseRedirect(reverse("network:login"))
#login_required
def post(request):
# Composing a new post must be done via POST
if request.method != "POST":
return JsonResponse({"error": "You must POST your request."}, status=404)
try:
data = json.loads(request.body)
body = data.get("body", "")
user = request.user
print(user)
post = Post(created_by=user, body=body)
# post = Post(created_by=Profile.objects.get(user=user), body=body)
post.save()
except AttributeError:
return JsonResponse({"error": "AttributeError thrown."}, status=500)
return JsonResponse({"message": "Post created."}, status=201)
#login_required
def viewposts(request, navbar_item):
if navbar_item == "viewposts":
posts = Post.objects.all()
posts = posts.order_by("-timestamp")
json_post = serialize("json", posts)
print(posts)
return HttpResponse(json_post, content_type="application/json")
else:
return JsonResponse({"error": "Invalid page."}, status=400)
index.html
{% extends "network/layout.html" %}
{% load static %}
{% block body %}
<div class="container p-5">
{% if error %}
{{ error }}
{% endif %}
<h1 class="display-4">All Posts</h1>
<div class="form-group border rounded p-4">
<h2 class="diplay-3">New Post</h2>
<form id="addpost" class="form-group pt-5">
{% csrf_token %}
<div class="form-group">
<textarea class="form-control" id="body" placeholder="Add post here..."></textarea>
</div>
<input type="submit" class="btn btn-primary"/>
</form>
</div>
<div id="all-posts" class="all-posts">
</div>
</div>
{% endblock %}
{% block script %}
<script src="{% static 'network/main.js' %}"></script>
{% endblock %}
JavaScript
// Post on index page # API Routes /addpost
const addPost = () => {
const addPostUrl = '/addpost';
const csrftoken = getCookie('csrftoken');
const body = document.querySelector('#body').value;
// body needs to be passed into an object before using the stringify method
const bodyObject = { body };
fetch(addPostUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken,
},
body: JSON.stringify(bodyObject)
})
.then(response => response.json())
.then(result => {
console.log(result);
})
.catch(error => {
console.log(error);
});
return false;
};
// Load posts in index page # API Routes /navbar_item
function loadPosts(navItem, event) {
preventPageLoad(event);
const postUrl = `/${navItem}`;
// Send a GET request to the URL to retrieve all posts
fetch(postUrl)
.then(response => response.json())
.then(data => {
data.forEach(post => {
const { fields } = post;
const allPostsContainer = document.querySelector("#all-posts");
const element = document.createElement('div');
const postId = `#post-${fields.id}`;
element.style.textDecoration = 'none';
element.classList.add('HoverClass1');
element.setAttribute('id', `post-${fields.id}`);
element.classList.add('d-flex', 'flex-column' ,'justify-content-between', 'p-4', 'm-3', 'lead', 'border', 'rounded');
element.style.color = '#000000';
element.innerHTML =
// This is returning an id
`<div class="bd-highlight font-weight-bolder mr-5">${fields.created_by}</div>
<div class="bd-highlight">${fields.timestamp}</div>
<div class="flex-fill bd-highlight">${fields.body}</div>`;
console.log(fields);
allPostsContainer.append(element);
const linePost = document.querySelector(postId);
linePost.addEventListener('click', (event) => {
console.log(event);
});
});
})
.catch(error => {
console.log(error);
});
return false;
}
Images showing my admin console in Django versus the browser console and what fetch is pulling in JavaScript. You'll see in the admin console we can view the username, but in the browser console all I'm getting is the user id with fetch.
I figured out how to do this. I added a serialize method to the Post model to convert these properties to JSON.
def serialize(self):
return {
'id': self.id,
'created_by': self.created_by.username,
'body': self.body,
'timestamp': self.timestamp.strftime('%b %-d %Y, %-I:%M %p'),
'likes': self.total_likes()
}
Then in views.py, in my viewposts function, instead of my the HttpResponse, I used JsonResponse and passed the model's serialize method as an argument.
#login_required
def viewposts(request, navbar_item):
if navbar_item == "viewposts":
posts = Post.objects.all()
posts = posts.order_by("-timestamp")
return JsonResponse([post.serialize() for post in posts], safe=False)
else:
return JsonResponse({"error": "Invalid page."}, status=400)
This allowed me to not have to deconstruct anything in my JavaScript file. So I could pull any attributes from my query using dot notation directly off of the data model in fetch.

Categories

Resources