How to re-render PayPal Smart Buttons with different values? - javascript

I'm using PayPal Smart button on my web site, and after each payment is made the user is able to make another new payment with PayPal smart button. The problem is that once I've created my PayPal button I'm unable to set the new createOrder in it with new orderID.
So in my website I have the following function which init the PayPal button if there is yet a pending order or it's called when a new order is made and I get a new orderID.
function initPayPal(orderID, publicKey) {
var paymentID = $("#paypal-button-container").data("payment-id")
var PAYPAL_SCRIPT =
"https://www.paypal.com/sdk/js?client-id=" + publicKey + "&currency=EUR&intent=capture&commit=false&vault=true";
var script = document.createElement("script");
script.setAttribute("src", PAYPAL_SCRIPT);
script.onload = function () {
paypal
.Buttons({
style: {
shape: "rect",
color: "gold",
layout: "horizontal",
label: "paypal",
tagline: false,
height: 52,
},
createOrder: async function () {
$(".body-payments").hide();
$(".loader-payments").show();
const res = await fetch(
"/api/payment/paypal/" + paymentID + "/" + orderID,
{
method: "post",
headers: {
"content-type": "application/json",
},
credentials: "include",
}
);
const data = await res.json();
return data.id;
},
onApprove: async function (data) {
const res = await fetch(
"/api/payment/paypal/capture/" + paymentID + "/" + data.orderID,
{
method: "post",
headers: {
"content-type": "application/json",
},
credentials: "include",
}
);
if (localStorage.STBOrder) {
localStorage.removeItem("STBOrder");
}
$("#modalPayments").modal("hide");
$("#modalSuccess").modal("show");
$(".body-payments").show();
$(".loader-payments").hide();
},
onCancel: function (data) {
$(".body-payments").show();
$(".loader-payments").hide();
},
})
.render("#paypal-button-container");
};
document.head.appendChild(script);
}
Client id is set dynamically as each user on its own page will get the payment on its own account. The issue here is that the button is initiated the first time all works fine, but if I dismiss the order and I call this function again it will create another button instead of setting new values to existing (already rendered) one.

There should be no need to init the button more than once.
Initialize the button one time (on page load).
Then, inside createOrder, ensure paymentID and orderID reference non-local variables or are replaced with function calls that will return the correct value at the time you want them to, e.g. some kind of getOrderID() of yours, and $("#paypal-button-container").data("payment-id") , in place of your existing local variables
In the unlikely event that you really did need to render the buttons more than once, you could save a reference to myButtons = paypal.Buttons({..}) before calling .render() --- and then, later myButtons.close() before saving a new reference to and rendering a new buttons object. But again, there should be no reason to do that here.

Related

Adding multiple products to WooCommerce Cart - AJAX

I am trying to find a way to select multiple item variations from a Vue frontend inside a WooCommerce website.
I installed the WC Ajax Cart plugin in order to have some sort of endpoint that I can call using AXIOS.
Each option I select ads and I to an array of called selectedProducts
When the user presses on checkout what I do is:
this.textCheckout = "Checking you out..."; //No pun intended actually xD
this.products.forEach((item, i) => {
if(this.selectedProducts.includes(item.variation_id)) {
let adder = new FormData();
adder.append('attribute_alege-zona', Object.values(item.attributes)[0]);
adder.append('attribute_tratament', Object.values(item.attributes)[1]);
adder.append('quantity', 1);
adder.append('product_id', item.variation_id);
adder.append('variation_id', item.variation_id);
console.log(adder);
let me = this;
let apel = setTimeout(function(){
axios({
method: "post",
url: "https://HOST/?wc-ajax=add_to_cart",
data: adder,
headers: { "Content-Type": "multipart/form-data" },
})
.then(function (response) {
})
.catch(function (response) {
console.log(response);
});
},1000)
}
});
let goCart = setTimeout(function() {
window.location.href = "https://HOST/cart";
}, 1500 * this.alese.length);
You might ask why I added those setTimeout functions. In my head it made sense that in case the call takes longer I am sure I will add all the products to the cart. The max I can add is two.
If let's say I select 5 products, when I get redirected to my cart I can only see 2 of them, sometime 3 (it is really really random)
Do you have any idea how can I add all the selected products to the same cart session ?
Thanks in advance!
Ok so after inspecting the network tab this is the solution that works:
this.toBeAdded.forEach((item, i) => {
let adder = new FormData();
adder.append('attribute_alege-zona', Object.values(item.attributes)[0]);
adder.append('attribute_tratament', Object.values(item.attributes)[1]);
adder.append('quantity', 1);
adder.append('product_id', item.variation_id);
adder.append('variation_id', item.variation_id);
console.log(adder);
let me = this;
let send = setTimeout(function() {
axios({
method: "post",
url: "https://HOST/?wc-ajax=add_to_cart",
data: adder,
headers: { "Content-Type": "multipart/form-data" },
withCredentials: true,
})
.then(function (response) {
if(i == 0) {
document.cookie = document.cookie + " woocommerce_items_in_cart=1; path=/";
}else if (i==me.toBeAdded.length-1){
window.location.href = "https://HOST/cart";
}
})
.catch(function (response) {
console.log(response);
});
}, i*500);
});
Basically if it is the fist product I am adding the WC items in cart cookie.
If it is the last product I redirect to cart.
Each call is delayed by i*500, where i is the index of the array of products

Paypal Smart Button – Delay firing "onApproval" causes issues

Using the Smart Payment buttons, there is a 3-4 second delay after the payment pop-up window closes. It takes 3-4 seconds after the close of the transaction window to fire the onApproval event which gets the transaction ID needed to process an order.
This causes trouble as the buyer could close the window in the meantime (as nothing seems to happen) and the event is never received, thus the order not processed (although paid for).
Here is the code:
paypal.Buttons({
createOrder: function(data,actions) {
// do some stuff
return fetch('/createOrder', {
method: 'post',
headers: {
'content-type': 'application/json'
}
}).then(function(res) {
return res.json();
}).then(function(data) {
return data.orderID;
});
},
// onApprove will be fired 3-4 second AFTER the popup of transaction closes
onApprove: function(data, actions) {
return fetch('captureOrder', {
method: 'post',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
orderID:data.orderID
})
}).then(function(res) {
return res.json();
}).then(function(details) {
});
}
}).render(selector);
Is there any way to have the popup close AFTER the even is fired? Otherwise the only work-around would be to make an overlay with a spinner (or something similar) that will disappear once the onApproval is received. But that is cumbersome. The pop-up really shouldn't close before the event is fired.
I haven't observed that long of a delay myself, and it shouldn't cause issues as the buyer should wait for their confirmation in any case, but well it is as it is.
You can use the onClick method to trigger a please wait / spinner or whatever if you feel it's necessary, and nuke it within your onApprove's fetch (and also an onError and onCancel function). But you're overcomplicating things.
use this
// Finalize the transaction after payer approval
onApprove: function(data, actions) {
return actions.order.capture().then(function(orderData) {
// Successful capture! For dev/demo purposes:
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');
// When ready to go live, remove the alert and show a success message within this page. For example:
// var element = document.getElementById('paypal-button-container');
// element.innerHTML = '';
// element.innerHTML = '<h3>Thank you for your payment!</h3>';
// Or go to another URL: actions.redirect('thank_you.html');
});
}

Need to modify the URL inside a onClick function while invoking fetch in reactJS

I am new to reactJS.
The below snippet is to invoke a URL on a click event.
But the URL fired is with the baseURI.
How can i truncate that or reset it ?
handleClick = (event) => {
console.log("Got the row " + event );
alert('The link was clicked.'+ event);
fetch("www.google.com")
.then(res => {
console.log("Got the result "+ res.json());
}
);
};
The URL fired is
Response:
{type: "basic", url: "http://localhost:3000/www.google.com", redirected: false,
status: 200, ok: true, …}
(...)
statusText: "OK"
type: "basic"
url: "http://localhost:3000/www.google.com"
I need to fire multiple calls within this handleClick event and then open a new tab if successful with the final URL along with the session key. The above code is just the beginning to check the calls work fine or not.
How should i truncate "http://localhost:3000" ??
Fetch API doesn't know that it's an external link. Add the protocol to your url, i.e. http:// or https://.
Fetching API from external link, you need to add complete URL like
http://google.com
or
https://google.com
just pass the fetch options:
fetch(URL, {
mode: 'cors',
headers: {
'Access-Control-Allow-Origin':'*'
}
})

PayPal Smart Button Orders API Ignoring notify_url in purchase_units object

We are sending an order using the PayPal Smart Button with their latest JS SDK (February 2019). We have set the notify_url in the purchase_units object in the createOrder function as specified in the Orders API Documentation.
The transaction is still taking the IPN url from the account rather than the one we have provided upon creating the transaction.
We have tried different keys as suggested on stackoverflow such as 'NOTIFYURL', 'NOTIFY_URL', 'notifyurl' and 'notify_url'. None of which worked.
We have tried to remove the IPN from the account settings but this was not possible (notify_url should always override this anyway according to documentation).
paypal.Buttons({
createOrder: function (data, actions) {
return actions.order.create({
intent: "CAPTURE",
purchase_units: [{
amount: {
value: '#Model.Total.ToString("F")',
},
NOTIFYURL: "#notifyUrl"
}]
});
},
onApprove: function (data, actions) {
return actions.order.capture().then(function (details) {
return fetch('/umbraco/surface/PayPalPayment/process', {
method: 'post',
redirect: 'follow',
body: JSON.stringify({
OrderID: data.orderID,
PayerID: data.payerID,
}),
headers: {
'content-type': 'application/json'
}
}).then(function (response) {
response.json().then(function (data) {
window.location.href = data.redirect;
})
});
}).catch(function (error) {
window.location.href = '/umbraco/surface/PaymentFailed/PaymentFailed/?error=' + error;
});
}
}).render('#paypal-button-container');
Note: variables are added via Razor Syntax, I have confirmed that these values are being set correctly in Fiddler. Post is below but the IPN Url has been redacted.
{"intent":"CAPTURE","purchase_units":[{"amount":{"value":"0.01","currency_code":"GBP"},"NOTIFYURL":"https://<REDACTED>/umbraco/surface/paypalipn/receive"}],"application_context":{}}
We should be seeing the notify URL being set but when checking the message ID in the IPN history it is trying to use the IPN url found on the account rather than the notify_url that was provided.
Notify URL is not used with the REST APIs. They don't use IPN. They use Webhooks. You will need to register webhooks in your PayPal app and setup a listener for those hooks accordingly.

Two requests in one time immediatly. ASP MVC + JQuery Ajax

MVC application (ASP.NET MVC, client: jquery).
Problem: The second ajax-request wait, when the first ajax request will done.
I need, when the first and the second ajax-requests executes immediatly in one time.
The page sends to server to determine the count of records (the first ajax-request), very long (~5-7 seconds).
The operator click the buttom to open the card to edit it (the second ajax-request, fast, get the Dto-model).
The user doesn't need to wait the first request, he wants to work immediatly.
As a result, in Chrome in network page, two requests in status 'pending'. The second waits the first.
Question, how can I send requests, to execute asynchronously ?
The first ajax-request:
`window.jQuery`.ajax({
type: 'POST',
url: Url.Action("GetCountBooks", "Book");
contentType: "application/json; charset=utf-8",
dataType: 'json',
data: JSON.stringify({ typeBook: "...", filter: "..." };),
success: function (data) {
// show in UI page the count of books by filter and params
},
error: function (data) {
//show error
}});
public class BookController : Controller
{
[HttpPost]
public NJsonResult GetCountBooks(string typeBook, Filter filter)
{
var data = DbProvider.GetCountBooks(typeBook, filter)
if (data.Result == ResultType.Success)
{
var count = data.Data;
return new NJsonResult
{
Data = new { Data = count }
};
}
return new NJsonResult
{
Data = new { Error = "Error while counting the books." }
};
}
}
The second ajax-request:
`window.jQuery`.ajax({
type: 'POST',
dataType: 'json',
contentType: "application/json",
url: Url.Action("GetBookById", "Book"),
data: JSON.stringify({ id: bookId }),
success: function (data) {
// show jquery dialog form to edit dto-model.
},
error: function (data) {
//show error
}});
public class BookController : Controller
{
[HttpPost]
public NJsonResult GetBookById(int id)
{
var data = DbProvider.GetBookById(id)
if (data.Result == ResultType.Success)
{
var book = data.Data;
return new NJsonResult
{
Data = new { Data = book }
};
return new NJsonResult
{
Data = new { Error = "The book is not found." }
};
}
return new NJsonResult
{
Data = new { Error = "Error while getting the book." }
};
}
}
I Cannot union ajax requests into one! The user can send various second request.
You need a fork-join splitter to fork 2 tasks and join based on some condition.
For example here is my implementation:
function fork(promises) {
return {
join: (callback) => {
let numOfTasks = promises.length;
let forkId = Math.ceil(Math.random() * 1000);
fork_join_map[forkId] = {
expected: numOfTasks,
current: 0
};
promises.forEach((p) => {
p.then((data) => {
fork_join_map[forkId].current++;
if (fork_join_map[forkId].expected === fork_join_map[forkId].current) {
if (callback) callback(data)
}
})
});
}
}}
Pass any number of async tasks (promises) into fork method and join when all are done. The done criteria here is managed by simple global object fork_join_map which tracks the results of your fork-join process (global is not good but its just an example). The particular fork-join is identified by forkId which is 0..1000 in this example which is not quite good again, but I hope you got the idea.
With jQuery you can create promise with $.when( $.ajax(..your ajax call) )
In the end you can join your promises like this
fork([
$.when( $.ajax(..your ajax call 1) ),
$.when( $.ajax(..your ajax call 2) )
]).join(() => {
// do your logic here when both calls are done
});
It's my own implementation, there may be already-written library functions for this in jQuery - I dont know. Hope this will give you a right direction at least.
The solution is to add attribute to Asp Controller: [SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
http://johnculviner.com/asp-net-concurrent-ajax-requests-and-session-state-blocking/

Categories

Resources