Having Trouble Accessing Variables & Setting Up Stripe Connect (Flask) - javascript

I'm building a Flask marketplace app (using Stripe Collect payments then pay out) where users can choose how much they want to pay (think fundraiser).
I am having trouble moving necessary data around appropriately throughout the checkout process and could really use some help.
Once a user enters how much they'd like to donate, the donation amount and the owner of the campaign they'd like to donate to are sent to the below /pay route where they see a form to enter in their card details with a "Submit Payment" button.
#app.route('/pay', methods=['GET', 'POST'])
def pay():
campaign = Campaign.query.get_or_404(request.args["campaign_id"])
owner = User.query.get_or_404(campaign.user_id) #owner of the campaign
donation_amount = request.args['amount_entered'] # EX: "1000"
return render_template('payment_form.html', campaign=campaign, owner=owner, amount=donation_amount)
The file, payment_form.html, has a simple Stripe form like this:
<form method="post" id="payment-form" class="sr-payment-form">
<div class="sr-form-row">
<label for="card-element">Credit or debit card</label>
<div style="width: 30em" id="card-element"></div>
</div>
<div>
<span style="width: 30em; height: 2em; letter-spacing: 0em" id="card-errors" role="alert"></span>
</div>
<button id="card-button" style="width: 33em;">Submit Payment</button>
</form>
And whenever someone enters their card info and submits the payment, I have a JavaScript file that listens for it and processes the payment (this does not work yet).
var form = document.getElementById('payment-form');
form.addEventListener('submit', function(ev) {
ev.preventDefault();
fetch("/pay_now", {
method: "GET",
headers: {
"Content-Type": "application/json"
},
})
.then(response => response.json())
.then(data => {
// not sure what to do here
});
stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: card,
billing_details: {
name: 'Jenny Rosen' //placeholder (would like to replace)
}
}
}).then(function(result) {
if (result.error) {
console.log(result.error.message);
} else {
if (result.paymentIntent.status === 'succeeded') {
}
});
});
This script fetches the below Flask API /pay_now and should return the clientSecret variable as well as other necessary data to complete the transaction.
#app.route('/pay_now', methods=['GET','POST'])
def create_payment():
intent = stripe.PaymentIntent.create(
payment_method_types=['card'],
amount="1000", #need to pass dollar amount here calculated in /pay route
currency="usd",
transfer_data={'destination': owner.stripe_id}, #need to pass owner from /pay route
application_fee_amount="100")
)
client_secret = intent.client_secret
return jsonify({"client_secret": client_secret})
So basically, my dilemma is that I have the amount of the donation and the campaign owner as variables in the /pay route. But I need to access them when I create the stripe.PaymentIntent object when I call the /pay_now API from my JavaScript and then I need to pass the clientSecret variable back to my JavaScript file for confirmCardPayment() to actually complete the payment.
I'm also open to different approaches if mine doesn't make sense.
I am new to Stripe & new to APIs in Flask. Any help or explanation here would be extremely helpful. Thanks in advance!

You'd want to only POST to your /pay_now route, in the body of that POST you should include the amount your user intends to donate. Then it's a simple case of including that amount when creating the PaymentIntent and returning the client secret to be confirmed on the client.
You might want to first do some checks on both the client and server whether the amount makes sense (e.g. if someone enters -1 it correctly gets rejected).
The stripe.confirmCardPayment code should go in the then resolver of your fetch request, after the response has been parsed to JSON:
fetch("/pay_now", {
method: "POST",
body: JSON.stringify({
amount: amount, // get this from the form
}),
headers: {
"Content-Type": "application/json"
},
})
.then(response => response.json())
.then(data => {
stripe.confirmCardPayment(data.clientSecret, {
payment_method: {
card: card,
billing_details: {
name: name, // get this from the form, like you did with the amount
}
}
})
.then(function(result) {
if (result.error) {
console.log(result.error.message);
} else {
if (result.paymentIntent.status === 'succeeded') {
// display success message
}
}
});

Related

How to correctly use Fetch in JavaScript and Django?

I am trying to make a METAR decoder as shown:
I am using fetch in Vanilla js and I plan to send the entered code to a Django view. From the Django view, the decoded data will be taken and displayed in the template.
views.py
def ToolsPageView(request):
if request.method == "POST":
jsonData = json.loads(request.body)
metarCode = jsonData.get('Metar')
return JsonResponse("Success", safe=False)
return render(request, 'app/tools.html')
urls.py
...
path("tools", views.ToolsPageView, name="tools")
tools.html
<div class="metar-code-decode">
<form method="POST" action="{% url 'tools' %}" id="metar-form">
{% csrf_token %}
<input type="text" placeholder="Enter METAR: " id="metar-value"> <br>
<input type="submit" id="metar-button">
</form>
</div>
tool.js
function getDecodedMetar() {
let formButton = document.querySelector("#metar-button");
formButton.onclick = function (e) {
let metarCode = document.querySelector("#metar-value").value;
sendMetar(metarCode);
//e.preventDefault();
//getMetar(metarCode);
};
}
function sendMetar(metarCode) {
fetch('/tools', {
method: "POST",
headers: {
"X-CSRFToken": getCookie("csrftoken"),
},
body: JSON.stringify({
Metar: metarCode,
}),
});
}
I have used the same code for POST using fetch where I had to let user update his/her profile. And that worked. But, this does not work and the error keeps on changing from time to time after restarting the server. At the first try, there was no error produced and the server also showed a POST request being made. And the latest error which I am getting is "In order to allow non-dict objects to be serialized set the safe parameter to False." I get the same thing again and again even after setting safe=False within the JsonResponse(). Worth to note, request when converted to request.json() gives an error.
Am I using fetch wrongly? If yes, what is the correct way?
I'm not sure you have the flow right. The idea is that the button, when clicked, will call a function (fetch) that will send data to the view, which will decode it and send it back to the JavaScript, so that it could be displayed without reloading the entire page.
I think this might help:
let formButton = document.querySelector("#metar-button");
// When the button is clicked,
formButton.onclick = function(e) {
// do NOT send the form the usual way
e.preventDefault();
let metarCode = document.querySelector("#metar-value").value;
// Run the function that will send the code to the ToolsPageView
sendMetar(metarCode);
}
async function sendMetar(metarCode) {
const response = await fetch('/tools', {
method: "POST",
headers: {
"X-CSRFToken": getCookie("csrftoken"),
},
body: JSON.stringify({
'Metar': metarCode,
}),
})
.then(response => response.json())
.then(data => {
console.log(data);
// extract the decoded value from the data sent back from the view
// display it by targeting the element in your html that you want
// to display it
});
}
And in your view,
def ToolsPageView(request):
if request.method == "POST":
jsonData = json.loads(request.body)
metarCode = jsonData.get('Metar')
# Remove the original JsonResponse
# return JsonResponse("Success", safe=False)
# and INSTEAD,
# Send the code back to the JavaScript
# I don't THINK you need safe=False here?
return JsonResponse({'MetarCode': metarCode})
return render(request, 'app/tools.html')

cant redirect to success page in django paypal

i have a Django Store Website which use Paypal for payment
but in my views.py,Django do everything except go to the directed page that i choose
this is my views.py
def capture(request,id):
do some stuff
return redirect(reverse("shop:success"))
and this is JavaScript
<script type="text/javascript">
function completeOrder(){
var url = "{% url 'shop:paypal' id=orders.id %}"
fetch(url, {
method:'POST',
headers:{
'Content-type':'application/json',
'X-CSRFToken':csrftoken,
},
body:JSON.stringify("{{orders.id}}")
})
}
// Render the PayPal button into #paypal-button-container
paypal.Buttons({
style: {
layout: 'horizontal',
color:"blue",
label:"checkout",
tagline:"false",
shape:"pill",
size:"small",
},
// Set up the transaction
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: '{{orders.converter}}'
}
}]
});
},
// Finalize the transaction
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
// Show a success message to the buyer
completeOrder()
alert('Transaction completed by ' + details.payer.name.given_name + '!');
});
}
}).render('#paypal-button-container');
</script>
Do not use actions.order.create() and actions.order.capture() to create+capture on the client side and then call a server with fetch after a client-side capture. This is thoroughly bad design when using a server.
Instead, switch to a proper server-side integration: make two routes on the server, one for 'Create Order' and one for 'Capture Order', documented here; there is a Checkout-Python-SDK you can use. These two routes should return only JSON data (no HTML or text). The latter one should (on success) store the payment details in your database before it does the return (particularly purchase_units[0].payments.captures[0].id, the PayPal transaction ID)
Pair your two routes with the following approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server
In the success code of that flow, the redirect can be done with actions.redirect(), or simply setting window.location.href (general JavaScript solution)

Not able to figure out the way to call a function at the correct place in django website

I am working on a django website and have come across an error which I am not able to solve and Its been a couple of days but I wasnt able to solve the error. I am trying to integrate a payment gateway to my site. I have a payment button which on click is supposed to post the data to the database as well as go to the payment site. But it is storing the data but not going to payment site.
Here is the javascript for my button:
document.getElementById('payment-info').addEventListener('click', function (e) {
submitFormData()
})
function submitFormData() {
console.log('Payment Button Clicked')
var userFormData = {
'name': null,
}
var shippingInfo = {
'address': null,
}
shippingInfo.address = form.address.value
userFormData.name=form.name.value
var url = "/process_order/"
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken,
},
body:JSON.stringify({'form': userFormData, 'shipping': shippingInfo }),
})
.then((response) => response.json())
.then((data) => {
console.log('Success:', data);
alert('Transaction Completed')
window.location.href = "{% url 'index' %}"
})
}
</script>
This is my views.py:
def processOrder(request):
transaction_id = datetime.datetime.now().timestamp()
data = json.loads(request.body)
if request.user.is_authenticated:
customer=request.user.customer
order, created=Order.objects.get_or_create(customer=customer, complete=False)
total=float(data['form']['total'])
order.transaction_id=transaction_id
if total == order.get_cart_total:
order.complete = True
order.save()
ShippingAddress.objects.create(
customer=customer,
order=order,
address=data['shipping']['address'],
name=data['form']['name'],
)
//Code for integration below
param_dict = {
'MID': 'DIY12386817555501617',
'ORDER_ID': str(order.id),
'TXN_AMOUNT': '4',
'CUST_ID': 'j',
'INDUSTRY_TYPE_ID': 'Retail',
'WEBSITE': 'WEBSTAGING',
'CHANNEL_ID': 'WEB',
'CALLBACK_URL':'http://127.0.0.1:8000/handlerequest/',
}
param_dict['CHECKSUMHASH'] = Checksum.generate_checksum(param_dict, MERCHANT_KEY)
return render(request, 'paytm.html', {'param_dict': param_dict})
return JsonResponse('Done',safe=False)
#csrf_exempt
def handlerequest(request):
# paytm will send you post request here
form = request.POST
response_dict = {}
for i in form.keys():
response_dict[i] = form[i]
if i == 'CHECKSUMHASH':
checksum = form[i]
verify = Checksum.verify_checksum(response_dict, MERCHANT_KEY, checksum)
if verify:
if response_dict['RESPCODE'] == '01':
print('order successful')
else:
print('order was not successful because' + response_dict['RESPMSG'])
return HttpResponse('doneee')
And this is my urls.py:
path("process_order/",views.processOrder,name="process_order"),
path("handlerequest/",views.handlerequest,name="handlerequest"),
This code is not working . After clicking the payment button I am getting this error:
Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0
This error is referring to the submitformdata() in my javascript code.
I want to workout a way so that once a person clicks on the payment button and his transaction is completed then his data is submitted to the database otherwise not for which I have written the code in handlerequest.
Please help me out.

stripe - can not use same payment-method for new paymentIntents

I have the issue that saved 3d-secure cards can not be re-used again.
it says, card number invalid. below is the paymentmethod created. (sorry, it is in german).
in this picture, you see, it says "For future usage" and shows the paymentintent id.
if I want to use the saved card for new payment this way:
it says, card is invalid.
I have some more codes, so dont know which part is relevant. i will paste js part already here:
stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: card,
billing_details: {},
},
return_url: 'https://localhost:8000/'
}).then(function(data) {
console.log(data);
if (data.error) {
showError(data.error.message);
} else if (data.paymentIntent.status === "succeeded") {
orderComplete(clientSecret);
}
});

how to properly use Stripe api for saved card on client side

I am currently working with Stripe API. I was able to use one time payment properly, and save credit card correctly as well as process;
However, I was stuck on the 3D secure(2nd auth) for saved card on client side. I had search out the website with no success. And the official doc https://stripe.com/docs/payments/save-and-reuse#web-create-payment-intent-off-session seems with little information about using saved card on client side. Or probably I just don't understand it. Hopefully someone can guide me out with correct practice.
Below it's the critical part of codes, it currently works with one time payment and 2nd auth stripe pop out.
NOTE: I was able to create a charge based on the information from each individual card from $saved_cards in my server, however it would not trig the 3d secure hence it will always failed with cards that requires 2nd authentication
backend.php
\Stripe\Stripe::setApiKey(env('STRIPE_SECRET'));
$intent = \Stripe\PaymentIntent::create([
'amount' => $payment * 100,
'currency' => 'usd',
]);
if (!empty(Auth::user()->stripe_id)) { //all saved card info
$saved_cards = \Stripe\PaymentMethod::all([
'customer' => Auth::user()->stripe_id,
'type' => 'card',
]);
}
return view('cart.preview', $items)->with('saved_cards', $saved_cards)->with('client_secret', $intent->client_secret);
Client.js
// this is for one time payment
var payment_method = {
card: card,
billing_details: {
name: "name"
}
};
stripe.confirmCardPayment( "{{ $client_secret }}", {
payment_method: payment_method, // <= I believe this is the place I can use the saved card?
}).then(function (result) {
if (result.error) {
// Show error to your customer (e.g., insufficient funds)
console.log(result.error.message);
} else {
// The payment has been processed!
if (result.paymentIntent.status === 'succeeded') {
// Show a success message to your customer
// There's a risk of the customer closing the window before callback
// execution. Set up a webhook or plugin to listen for the
// payment_intent.succeeded event that handles any business critical
// post-payment actions.
}
}
});
/*****************************************************************************/
Update for solution based on answer suggestion:
Disclaimer: this might be a bad practice since the secret switch happens on the frontend side. But you get the idea.
In order to use your payment_method id, you also have to attach your customer id, which happens in the backend side. So for my case I created another savedCard_intent in my backend and pass it to frontend for handle the saved card specifically.
$savedCard_intent = \Stripe\PaymentIntent::create([
'customer' => Auth::user()->stripe_id,
'amount' => $payment * 100,
'currency' => 'usd',
]);
pass it to frontend with('saved_secret' ,$savedCard_intent->client_secret);
Combine all of them together I have code like the following:
newBackend.php
\Stripe\Stripe::setApiKey(env('STRIPE_SECRET'));
$new_intent = \Stripe\PaymentIntent::create([
'amount' => $payment * 100,
'currency' => 'usd',
]);
$savedCard_intent = \Stripe\PaymentIntent::create([
'customer' => Auth::user()->stripe_id,
'amount' => $payment * 100,
'currency' => 'usd',
]);
if (!empty(Auth::user()->stripe_id)) { //all saved card info
$saved_cards = \Stripe\PaymentMethod::all([
'customer' => Auth::user()->stripe_id,
'type' => 'card',
]);
}
return view('cart.preview', $items)->with('saved_cards', $saved_cards)->with('new_secret', $new_intent->client_secret)->with('saved_secret', $savedCard_intent->client_secret);
newClient.js
// this is for one time payment
var payment_method = {
card: card,
billing_details: {
name: "name"
}
};
var secret = (Some conditions here) ? "{{$new_secret}}" : "{{$saved_secret}}";
stripe.confirmCardPayment( secret, {
payment_method: (Some conditions here) ? payment_method : saved_payment_method.id,
}).then(function (result) {
if (result.error) {
// Show error to your customer (e.g., insufficient funds)
console.log(result.error.message);
} else {
// The payment has been processed!
if (result.paymentIntent.status === 'succeeded') {
// Show a success message to your customer
}
}
});
You are correct. You would use the PaymentMethod's id (pm_******) like so:
stripe.confirmCardPayment( "{{ $client_secret }}", {
payment_method: payment_method.id,
}). then( ... )
Documented here: https://stripe.com/docs/js/payment_intents/confirm_card_payment#stripe_confirm_card_payment-data-payment_method
You can also pass a PaymentMethod object if you're generating one on the client side, but that's not likely the approach you're looking for.

Categories

Resources