I'm creating a site for my senior project and have run into some trouble creating a payment portal for my site. The site was working correctly the other night, and without making any changes, the buttons now fail to render and I am being spammed with errors regarding indicating a cookie in a cross site address. Attached is the checkout.html file which the PayPal js is included within, along with the error codes I am receiving from the console. Any help would be much appreciated!
I have tried to edit the global config and played around with the SameSite attribute, but to no avail :(
{% extends 'main.html' %}
{% load static %}
<!DOCTYPE html>
<head>
<link rel="stylesheet" type="text/css" href="{% static 'css/checkout.css' %}">
</head>
<body>
{% block content %}
<div class="row">
<div class="col-sm-6 mt-4 mb-4">
<div class="box-element" id="form-wrapper">
<h2>Recipient Information</h2>
<form id="form">
<div id="recipient-info">
<div class="form-field">
<input required class="form-control" type="text" name="recipient_first_name" placeholder="Recipient First Name..">
</div>
<div class="form-field">
<input required class="form-control" type="text" name="recipient_last_name" placeholder="Recipient Last Name..">
</div>
<br>
<div class="form-field">
<input required class="form-control" type="email" name="recipient_email" placeholder="Recipient Email..">
</div>
<div class="row ml-auto">
<label class = "mt-1" for="pickup_location">Select a pickup location: </label>
<select class="mt-2 ml-2" name="pickup_location" size="4" multiple>
<option value="nabatieh">Nabatieh</option>
<option value="tyre">Tyre</option>
<option value="saida">Saida</option>
<option value="beirut">Beirut</option>
</select><br><br>
</div>
</div>
<hr>
<input id="form-button" class="btn btn-success btn-block" type="submit" value="Continue">
</form>
</div>
<br>
<div class="box-element hidden" id="payment-info">
<h2>PayPal Options</h2>
<div id="paypal-button-container"></div>
</div>
</div>
<div class="col-sm-6 mt-4 mb-4">
<div class="box-element">
<a class="btn btn-outline-dark" href="{% url 'cart' %}">← Back to Cart</a>
<hr>
<h3>Order Summary</h3>
<hr>
{% for item in items %}
<div class="cart-row">
<div style="flex:2"><img class="row-image" src="{{item.product.imageURL}}"></div>
<div style="flex:2"><p>{{item.product.name}}</p></div>
<div style="flex:1"><p>{{item.product.price|floatformat:2}}</p></div>
<div style="flex:1"><p>{{item.quantity}}</p></div>
</div>
{% endfor %}
<h5>Items: {{order.get_cart_items}}</h5>
<h5>Total: ${{order.get_cart_total|floatformat:2}}</h5>
</div>
</div>
</div>
<script src="https://www.paypal.com/sdk/js?client-id=ASbTo6tPEAlA-TQ3zYFDMuuDgSWzmybQ3E3LeE3xi2s3hN6UmiREE30_atIal3f9ui2r3Eh6oGpxmil-¤cy=USD"></script>
<script>
var total = '{{order.get_cart_total}}'
paypal.Buttons({
// Set up the transaction
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: parseFloat(total).toFixed(2)
}
}]
});
},
// Finalize the transaction
onApprove: function(data, actions) {
return actions.order.capture().then(function(orderData) {
submitFormData()
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
var transaction = orderData.purchase_units[0].payments.captures[0];
alert('Transaction '+ transaction.status + ': ' + transaction.id + '\n\nSee console for all available details');
// Replace the above to show a success message within this page, e.g.
// const element = document.getElementById('paypal-button-container');
// element.innerHTML = '';
element.innerHTML = '<h3>Thank you for your payment!</h3>';
actions.redirect('home.html');
});
}
}).render('#paypal-button-container');
</script>
<script type="text/javascript">
var form = document.getElementById('form')
var total = '{{order.get_cart_total|floatformat:2}}'
form.addEventListener('submit', function(e){
e.preventDefault()
console.log('Form submitted...')
document.getElementById('form-button').classList.add('hidden')
document.getElementById('payment-info').classList.remove('hidden')
})
function submitFormData(){
console.log('Payment button has been clicked...')
var orderData = {
'total':total,
}
var recipientInformation = {
'recipient_first_name':null,
'recipient_last_name':null,
'email':null,
'pickup_location':null,
}
recipientInformation.recipient_first_name = form.recipient_first_name.value
recipientInformation.recipient_last_name = form.recipient_last_name.value
recipientInformation.recipient_email = form.recipient_email.value
recipientInformation.pickup_location = form.pickup_location.value
var url = "/process_order/"
fetch(url, {
method:'POST',
headers:{
'Content-Type':'applicaiton/json',
'X-CSRFToken':csrftoken,
},
body:JSON.stringify({'form':orderData, 'recipient-information':recipientInformation}),
})
.then((response) => response.json())
.then((data) => {
console.log('Success:', data);
alert('Transaction completed');
window.location.href = "{% url 'store' %}"
})
}
</script>
{% endblock content %}
</body>
</html>
Error from console
Error information expanded
Here is how the portal was working the other night, note I made no changes on my end from then until now.
https://gyazo.com/7cfcc6a9d226af3604a7a597d39e7316
I don't know what your issue is, but it is wrong and bad design to use actions.order.create / capture on the client side and then perform server operations after the fact. This should never be done.
If server operations are to take place (such as saving form data or writing ot a database), the payment creation/capture should be done on the server, via API. With any luck, the problem in your question can be resolved as a byproduct of starting over and implementing things correctly:
Follow the PayPal Checkout integration guide and make 2 routes (url paths) on your django server, one for 'Create Order' and one for 'Capture Order'. You can use the Checkout-Python-SDK for the routes' API calls to PayPal, or your own HTTPS implementation of first getting an access token and then doing the call. Both of these routes should return only JSON data (no HTML or text). Inside the 2nd route, when the capture API is successful you should verify the amount was correct and store its resulting payment details in your database (particularly purchase_units[0].payments.captures[0].id, which is the PayPal transaction ID) and perform any necessary business logic (such as sending confirmation emails or reserving product) immediately before forwarding your return JSON to the frontend caller. In the event of an error forward the JSON details of it as well, since the frontend must handle such cases.
Pair those 2 routes with this frontend approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server . (If you need to send any additional data from the client to the server, such as an items array or selected options or form data in your case, add a body parameter to the fetch with a value that is a JSON string or object of your serialized data)
Related
I'm trying to make a stock finance like website where anyone can get fake money and buy stocks. So in the buy page, I am trying to implement a feature where as the user types the stock symbol and the number of shares, in real time, the pricing shows up in the h1 tags that have an id of "render". This can be achived if user input is sent to my app.py and after looking up the price using an api and some math, app.py send the price back to javascript to update the page.
I've been trying to use fetch() and AJAX but I don't understand any of the tutorials or stack overflow questions. Can someone give me a reliable solution and explain it to me?
HTML:
{% extends "layout.html" %}
{% block title %}Buy{% endblock %}
{% block main %}
<form action="/buy" method="post">
<div class="mb-3">
<input class="form-control mx-auto w-auto" autocomplete="off" name="symbol" placeholder="Symbol" value="{{ input_value }}" id="symbols">
</div>
<div class="mb-3">
<input class="form-control mx-auto w-auto" autocomplete="off" autofocus name="shares" placeholder="Shares" id="shares">
</div>
<div class="mb-3">
<button class="btn btn-primary" type="submit">Buy</button>
</div>
</form>
<h1 id="render">
</h1>
<script>
</script>
{% endblock %}
App.py:
#app.route("/buy", methods=["GET", "POST"])
#login_required
def buy():
"""Buy shares of stock"""
if request.method == "GET":
return render_template("buy.html", input_value = "")
else:
return render_template("buy.html", input_value = request.form.get("symbol"))
I'm trying to use the function above for rendering the template
Accepting response and sending back information:
#app.route("/show_price", methods=["GET", "POST"])
def show_price():
#logic stuff
return #price
TL;DR at bottom
I found a solution to the problem by using this as my app.py:
#app.route("/show_price", methods=["GET", "POST"])
#login_required
def show_price():
# https://www.makeuseof.com/tag/python-javascript-communicate-json/
data = request.get_json()
if data[1].isdigit() == True:
data = jsonify() # the data
return data
else:
return ""
and using fetch() in my javascript:
{% extends "layout.html" %}
{% block title %}Buy{% endblock %}
{% block main %}
<form action="/buy" method="post">
<div class="mb-3">
<input id="symbols">
</div>
<div class="mb-3">
<input id="shares">
</div>
<h2 id="render">
</h2>
<div class="mb-3">
<button class="btn btn-primary" type="submit">Buy</button>
</div>
</form>
<script>
let input1 = document.getElementById('symbols');
let input = document.getElementById('shares');
input.addEventListener('keyup', function(event) {
value = [
input1.value, input.value
]
fetch("http://127.0.0.1:5000/show_price",
{
method: 'POST',
headers: {
'Content-type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(value)}).then(res =>{
if(res.ok){
return res.json()
} else {
document.querySelector('h2').innerHTML = "Keep typing...";
}
}).then(jsonResponse=>{
word = "That would be " + jsonResponse
document.querySelector('h2').innerHTML = word;
})
.catch((err) => console.error(err));
});
</script>
{% endblock %}
so as the user is typing in the the shares field the event listener will get the symbols and shares fields, use fetch() to get the data over to def show_price() with a jsonified array of symbol and shares. If there is an error the div id="render" will display "Keep typing". After python gets the information it will look it up using a function, then it will return the price of the shares in json format. Then javascript will get the data and use some javascript to change the html.
TL;DR
Basically I used fetch() to get the data to python, did some algorithm stuff and python return it to javascript. https://www.makeuseof.com/tag/python-javascript-communicate-json/ is really useful in teaching you how to use fetch().
I am building a Zendesk app that will post a variety of information to a webhook. Currently, I am running into two issues. The client.invoke() function says it is not a function in the console when the send email button is pressed. Additionally, sometimes the after the button is pressed, the app will successfully post to the webhook, other times it won't post at all. I cannot narrow down what is causing the discrepancies on when it posts. I'm unsure if this is related to the app I've built or an issue interacting with Zendesk.
Here is the app:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="https://cdn.jsdelivr.net/bootstrap/2.3.2/css/bootstrap.min.css" rel="stylesheet">
<link href="main.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/jquery/3.2.1/jquery.min.js"></script>
<script type="text/javascript" src="https://assets.zendesk.com/apps/sdk/2.0/zaf_sdk.js"></script>
<script src="https://cdn.jsdelivr.net/handlebarsjs/4.0.8/handlebars.min.js"></script>
</head>
<body>
<script>
var client = ZAFClient.init();
client.invoke('resize', { width: '100%', height: '450px' });
client.get('ticket.brand.subdomain').then(
function(data) {
var subdomain = data['ticket.brand.subdomain'];
console.log('Zendesk Subdomain is ' + subdomain);
document.getElementById('subdomainform').value = subdomain;
}
);
client.get('ticket.organization.id').then(
function(data) {
var org_id = data['ticket.organization.id'];
console.log('Org id is ' + org_id);
document.getElementById('orgidform').value = org_id;
}
);
</script>
<form name="submissionForm">
<div class="formBox">
<label for="title">First Name</label>
<input type="text" id="firstName" placeholder="First Name"/>
</div>
<div class="formBox">
<label for="title">Last Name</label>
<input type="text" id="lastName" placeholder="Last Name"/>
</div>
<div class="formBox">
<label for="title">Email</label>
<input type="text" id="email" placeholder="Email"/>
</div>
<div class="formBox">
<select id="rescom">
<option value="residential">Residential</option>
<option value="commercial">Commercial</option>
</select>
</div>
<div class="formBox">
<button id="btn">Click to Send Email</button>
</div>
<div><p id="explain">The fields below are ready-only and required for submission. If you don't see them, please refresh the app.</p></div>
<div class="formBox">
<input type="text" id="subdomainform" readonly="readonly"/>
</div>
<div class="formBox">
<input type="text" id="orgidform" readonly="readonly"/>
</div>
</form>
<script>
let content = [];
const addDay1 = (ev)=>{
let information = {
id: Date.now(),
firstName: document.getElementById('firstName').value,
lastName: document.getElementById('lastName').value,
email: document.getElementById('email').value,
subdomain: document.getElementById('subdomainform').value,
orgid: document.getElementById('orgidform').value,
rescom: document.getElementById('rescom').value
}
content.push(content);
document.forms[0].reset();
const Url ='{PLACEHOLDER}';
$.ajax({
url: "{WEBHOOK URL}",
type: "POST",
dataType: 'json',
data: {information},
complete: function(){alert("Failure")}
});
}
document.addEventListener('DOMContentLoaded', ()=>{
document.getElementById('btn').addEventListener('click', addDay1);
});
</script>
</body>
</html>
What am I missing? Appreciate any and all help that can be provided.
This was solved my a community manager from Zendesk. See post below:
Make sure ZAFClient is fully registered before attempting to do
subsequent ZAFClient methods. Something like:
client.on('app.registered', e => {
client.get('ticket.brand.subdomain').then(
function(data) {
var subdomain = data['ticket.brand.subdomain'];
console.log('Zendesk Subdomain is ' + subdomain);
document.getElementById('subdomainform').value = subdomain;
}
);
client.get('ticket.organization').then(
function(data) {
var org_id = data['ticket.organization.id'];
console.log('Org id is ' + org_id);
document.getElementById('orgidform').value = org_id;
}
);
}) The "not a function in the console" error is caused from the app's HTML page being resubmitted again when you click the button. In
Zendesk Apps framework, the connection from the app (using
ZAFClient.init()) to the main agent window is done through parameters
that are passed to the app when the framework first loads it. You can
see this in your browser's Network tab if you look for something like
"iframe.html?origin=https%3A%2F%2Fyour_subdomain.zendesk.com&app_guid=ff7133010-abff-4f1c-a7bf-ff7133fff7133"
-- the origin and app_guid params are needed to make the connection. When you resubmit the page, those parameters no longer are passed on
the new page reload and the new call to ZAFClient.init() doesn't
successfully initialize. Thus leading the error when the now invalid
'client' object is attempting to be used to call 'invoke'. You have to
treat these app pages like single-page apps.
Phew! All that said -- you can still use HTML functionality,
just don't have it resubmit the entire page when the button is
pressed. You can do this by adding type="button" to the button tag.
Click to Send Email
See also: HTML button to NOT submit form
Hope this gets you on your way!
I am following the Stripe Quickstart guide for stripe Elements using Flask: https://stripe.com/docs/stripe-js/elements/quickstart and while the token is appearing in the web console POST params:
cardholder-name jane
stripeToken tok_1ECGDeCKSqZHGj511bnxBRad
the POST attempt is returning "400 bad request, CSRF token is missing or incorrect."
My javascript is consistent with the tutorial so I think it may be something to do with my views function:
#billing.route('/charge', methods=['GET', 'POST'])
def charge():
token = request.form['stripeToken']
if request.method == 'POST':
customer = stripe.Customer.create(
email='customer#example.com',
source=token
)
"""
charge = stripe.Charge.create(
customer=customer.id,
amount=250,
currency='usd',
description='Flask Charge',
source=token
)
"""
return render_template('billing/charge.html', token=token
)
I have been commenting out certain parts of the function to try to isolate the problem to no avail. Have I made a mistake passing the form to my server? Alternatively, is there a test I can write to debug the 400 error? Any and all feedback on all of the code is appreciated. Here is my html form code for reference
<script src="https://js.stripe.com/v3/"></script>
<body>
<form role="form" action="{{ url_for('billing.charge') }}" method="post" id="payment-form">
<div class="group">
<label>
<span>Name</span>
<input name="cardholder-name" class="field" placeholder="Jane Doe" />
</label>
<label>
<span>Phone</span>
<input class="field" placeholder="(123) 456-7890" type="tel" />
</label>
</div>
<div class="group">
<label>
<span>Card</span>
<div id="card-element" class="field"></div>
</label>
</div>
<button type="submit">Pay $25</button>
<div class="outcome">
<div class="error"></div>
<div class="success">
Success! Your Stripe token is <span class="token"></span>
</div>
</div>
</form>
</body>
I think the tutorial (and the set-up-subscriptions example code) don't bother with CSRF, presumably for simplicity.
The Flask-WTF docs have a bit about CSRF tokens that I found useful.
I ended up putting a csrf_token variable in my Flask template:
<script>
var csrf_token = {{ csrf_token()|tojson }};
</script>
And then in my Stripe Elements code, when making a call to /create-customer I added an extra header. Here's the JavaScript function from the set-up-subscriptions repo with my extra line:
// Assumes that a global csrf_token variable has been set.
async function createCustomer(paymentMethod, cardholderEmail) {
return fetch('/create-customer', {
method: 'post',
headers: {
'X-CSRFToken': csrf_token, // This is the extra line
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: cardholderEmail,
payment_method: paymentMethod
})
})
.then(response => {
return response.json();
})
.then(subscription => {
handleSubscription(subscription);
});
}
I assume a similar method will work with the other JS functions that call the Flask back end.
You need to add a CSRF token to your form as per Flask docs.
If you are using FlaskForm include {{ form.csrf_token }} in the form.
<form method="post">
{{ form.csrf_token }}
</form>
https://flask-wtf.readthedocs.io/en/stable/csrf.html
EDIT: Changed the HTML form statement to work like so:
<form action="." class="form-horizontal" id="groupinfoForm" onsubmit="SubmitToServer()" method="post">
Question: Do i still also need the method="post">
Also changed the javascript to work with the onsubmit:
function SubmitToServer() {
event.preventDefault();
//some ajaxy goodness here... still working on this.
$.ajax({
data: $("#groupinfoForm").serialize(),
success: function(resp){
alert ("resp: "+resp.name);
}
})
//I thinK i have to change all this to work inside the .ajax call?
formData = $('form').serializeArray()
$('#group_info option:first').prop('selected',true);
gid = $('#group_info option:selected').val()
//test alert
alert("Submitting data for provider: " + $("#provider_id").val() + " and " + gid + " and " + formData[0]['date_joined']);
$("#groupinfo-dialog").modal('hide');
}
I have a form, that is standard django. Fill it out send it off goes to a different page upon success..shows errors if you don't fill out portions.
Now I have a modal form that will pop up to fill out some extra data. I have decided to try to my hand at ajax for this. I have some of it working:
Inside the class that is an UpdateView:
def post(self, request, *args, **kwargs):
if self.request.POST.has_key('group_info_submit') and request.is_ajax():
print("YOU SURE DID SUBMIT")
return HttpResponse("hi ya!")
The problem is it always redirects to a different page, and fails validation anyway because the form the modal pops over is not complete and trying to be submitted.
I saw this post here:
Ajax Form Submit to Partial View
This seems overly complicated, I just have a small div in my form that is a modal div that I would like to submit sort of separately from the rest... The java script I have in the code:
$.ajax({
data: $("#groupinfoForm").serialize(),
success: function(resp){
alert ("resp: "+resp.name);
}
})
Then the little modal html snippet is:
<div class="container">
<div id="groupinfo-dialog" class="modal" title="Group Information" style="display:none">
<div class="modal-dialog">
<h1> Group Information </h1>
<div class="modal-content">
<div class="modal-body">
<form action="." class="form-horizontal" id="groupinfoForm" method="post">
{% csrf_token %}
{{ group_information_form.non_field_errors }}
<div class="col-md-12">
{{ group_information_form.date_joined_group.errors }}
{{ group_information_form.date_joined_group.label_tag }}
{{ group_information_form.date_joined_group }}
</div>
<div class="col-md-12">
{{ group_information_form.provider_contact.errors }}
{{ group_information_form.provider_contact.label_tag }}
{{ group_information_form.provider_contact }}
</div>
<div class="col-md-12">
{{ group_information_form.credentialing_contact.errors }}
{{ group_information_form.credentialing_contact.label_tag }}
{{ group_information_form.credentialing_contact }}
</div>
<div class="col-md-12">
<div class="col-md-3">
</div>
<div class="col-md-8 form-actions">
<input type='button' class='btn' onclick="CancelDialog()" value='Cancel'/>
<input type='submit' class='btn btn-success' onclick="SubmitToServer()" value='Save' name='group_info_submit'/>
</div>
<div class="col-md-1">
</div>
</div>
<input type="hidden" id="provider_id" name="provider_id" value="{{ provider_id }}" />
<input type="hidden" id="group_id" name="group_id" value="{{ group_id }}" />
</form>
</div>
</div>
</div>
</div>
</div> <!-- end modal Group Info Dialog -->
I do have a model form on the backend in the forms.py here. Also note there was a SubmitToServer() call, that is where I thought I could toss all the ajax stuff over the fence to the server, but I guess I need that $.ajax? I am still learning the deeper parts of jquery. I want to do a lot of preprocessing before i submit the data. My attempt at the submittoserver javascript was here:
Wasn't sure how to get all the form data (just the three fields I care about from the modal) to send it over...my attempt here:
function SubmitToServer() {
formData = $('form').serializeArray()
$('#group_info option:first').prop('selected',true);
gid = $('#group_info option:selected').val()
alert("Submitting data for provider: " + $("#provider_id").val() + " and " + gid + " and " + formData[0]['date_joined']);
$("#groupinfo-dialog").modal('hide');
}
So can I bypass the main form validation and just send the three fields over somehow? and not have it redirect but stay on the page?
You have to intercept the submit event. add onsubmit="someFunction()" to the form. In the someFunction ajax the data you want to validate before submitting, and if all is ok return true, else false.
https://jsfiddle.net/am5f14oc/
<html>
<body>
<form onsubmit="validateFunc()" action="." method="post">
<input id="name" type="text" name="name">
<input type="submit"/>
</form>
</body>
</html>
and the javascript
function validateFunc() {
formData = $('form').serializeArray();
// do ajax or whatever and return true if everything is ok
return false;
}
I'm using a super basic google form on my website. I'm using this website to extract the HTML to display it on my website - http://stefano.brilli.me/google-forms-html-exporter/
Once I hit submit, nothing happens. The page is just locked. I'm trying to resubmit it to another page. Here is my code
<div class="row">
<form action="https://docs.google.com/forms/d/e/1FAIpQLSfJQ9EkDN8aggSL9AEB2PK4BGiZgBzLDbS1IPppfSkU1zy-oA/formResponse"target="_self" id="bootstrapForm" method="POST">
<div class="col-sm-6 col-md-3">
<input id="679075295" type="text" name="entry.679075295" class="form-control" >
</div>
<div class="col-sm-6 col-md-3">
<input id="897968244" type="text" name="entry.897968244" class="form-control" >
</div>
<div class="col-sm-6 col-md-3">
<input id="685661947" type="text" name="entry.685661947" class="form-control" >
</div>
<input id="503500083" type="hidden" name="entry.503500083" value="<%= #investment.id %>" >
<div class="col-sm-6 col-md-3">
<button type="submit" value"submit" class="btn btn--primary type--uppercase" >Get Started</button>
</div>
</form>
Here is the ajax script
<script>
$('#bootstrapForm').submit(function (event) {
event.preventDefault()
var extraData = {}
$('#bootstrapForm').ajaxSubmit({
data: extraData,
dataType: 'jsonp', // This won't really work. It's just to use a GET instead of a POST to allow cookies from different domain.
error: function () {
// Submit of form should be successful but JSONP callback will fail because Google Forms
// does not support it, so this is handled as a failure.
alert('Form Submitted. Thanks.')
// You can also redirect the user to a custom thank-you page:
window.location = 'http://reif.com.au/thankyou'
}
})
})
</script>
</div>
Feeling a little silly on this one. Essentially i didn't copy over all of the scripts. I was rushing through.. ALWAYS number 1 error!
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.form/4.2.2/jquery.form.min.js" integrity="sha256-2Pjr1OlpZMY6qesJM68t2v39t+lMLvxwpa8QlRjJroA=" crossorigin="anonymous"></script>
This is what i needed to add, it now successfully submits and redirects!