I'd like to use the Paypal's smart buttons, because payment by card is also inserted as an option and the visitor/customer doesn't need to leave my page in order to purchase. Using the Pp code works fine, but I prefer to load the external javascript and to render the buttons on click or mouse over, mostly for speed and SEO purposes.
From my little knowledge, the below should work:
<div id="paypal-button-container"></div>
Pay by Paypal
<script>
// uncommented by me window.addEventListener('load', function() {
function initPayPalButton() {
paypal.Buttons({ // and here is the Uncaught ReferenceError: paypal is not defined
style: {
shape: 'rect',
color: 'gold',
layout: 'vertical',
label: 'checkout',
},
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
"description": "My awesome product",
"amount": {
"currency_code": "USD",
"value": 111
}
}]
});
},
onApprove: function(data, actions) {
return actions.order.capture().then(function(orderData) {
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
const element = document.getElementById('paypal-button-container');
element.innerHTML = '';
element.innerHTML = '<h3>Thank you for your payment!</h3>';
});
},
onError: function(err) {
console.log(err);
}
}).render('#paypal-button-container');
//my approach starts
var e = document.createElement("script");
e.type = "text/javascript", e.async = 0, e.src = "https://www.paypal.com/sdk/js?client-id=xxx-yyy&enable-funding=venmo¤cy=USD", (document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(e);
//my approach ends
}
// uncommented by me initPayPalButton();
// uncommented by me });
</script>
<!-- uncommented by me <script async src="https://www.paypal.com/sdk/js?client-id=xxx-yyy&enable-funding=venmo¤cy=USD" data-sdk-integration-source="button-factory"></script>-->
Fails miserably, with the error message 'Uncaught ReferenceError: paypal is not defined'. You can see above where the error occurs.
Please help. I'm using jQuery as well, so any jQuery solution that works will be appreciated.
I've tried to append the sdk js above the render('#paypal-button-container'), it didn't work. I've also tried to have 2 functions triggered by onclick, one to load the sdk and the second to render the buttons... still no use.
OK, a little later....the best I could come up with was to have 2 functions: 1 that would load the external sdk and one that would render the buttons, then add 2 onclick events to the Pay by Paypal link:
onclick="loadpaypalsdk();setTimeout(initPayPalButton, 3000);"
This seems to work, although the 3s delay is a little annoying. Shorter than this is could trigger another error, due to the sdk not being completely loaded.
Surely there are other better ways? Any ideas?
You'll need an onload callback function to invoke paypal.Buttons after the PayPal JS SDK script finishes loading. Here's a loadAsync helper function for this...
//helper function
function loadAsync(url, callback) {
var s = document.createElement('script');
s.setAttribute('src', url); s.onload = callback;
document.head.insertBefore(s, document.head.firstElementChild);
}
// Example usage -- a callback function is inlined here
// (but could be a named function like your own initPayPalButton)
loadAsync('https://www.paypal.com/sdk/js?client-id=test¤cy=USD', function() {
paypal.Buttons({
// Set up the transaction
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: '0.01'
}
}]
});
},
// Finalize the transaction
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
//...
});
}
}).render('#paypal-button-container');
});
I don't necessarily endorse your idea of only loading the JS SDK onclick/onmouseover; the buttons would appear faster if you render them on page load to a container div that is style="display:none;" , and then have your onclick/onmouseover reveal them with $('#paypal-button-container').show(); or similar
Related
I've been trying to integrate PayPal buttons on my Django website, but I keep having this problem where the PayPal popup window appears as about:blank#blocked. I can see this error in console:
popup_open_error_iframe_fallback
{err: 'n: Can not open popup window - blocked\n at Ie (…owser=false&allowBillingPayments=true:1342:297830', timestamp: '1644780862712', referer: 'www.sandbox.paypal.com', sdkCorrelationID: 'f12370135a997', sessionID: 'uid_d36969c1b2_mtk6mja6mzy', …}
What I don't understand is that the problem isn't there if I just open the HTML file itself in a browser... The script looks like this:
<!-- Set up a container element for the button -->
<div id="paypal-button-container" class='text-center mt-2'></div>
<!-- Include the PayPal JavaScript SDK -->
<script src="https://www.paypal.com/sdk/js?client-id=blahblahmyid¤cy=EUR"></script>
<script>
// Render the PayPal button into #paypal-button-container
paypal.Buttons({
locale: 'it_IT',
style: {
color: 'gold',
shape: 'rect',
layout: 'vertical',
label: 'pay'
},
// Set up the transaction
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: '88.44'
}
}]
});
},
// Finalize the transaction
onApprove: function(data, actions) {
return actions.order.capture().then(function(orderData) {
// Successful capture! For 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');
// 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>';
// Or go to another URL: actions.redirect('thank_you.html');
});
}
}).render('#paypal-button-container');
</script>
What's the problem ? I don't understand.
There's a new feature in django 4.0 that follows the addition of the COOP header in newer browsers which, as I understand it, stops remote sites from opening a window in the same browsing context.
The default setting of SECURE_CROSS_ORIGIN_OPENER_POLICY in django isolates the browsing context to just the current document origin. Setting it to same-origin-allow-popups allows the paypal popup to open in the current context.
See https://docs.djangoproject.com/en/4.0/ref/middleware/#cross-origin-opener-policy
TLDR; set SECURE_CROSS_ORIGIN_OPENER_POLICY='same-origin-allow-popups'
For django 4.0 settings.py add
SECURE_CROSS_ORIGIN_OPENER_POLICY='same-origin-allow-popups'
I have a shopping cart which is updated via Ajax (quantity, removing of products). How do I update the value in the Smart Button iframe? It obviously works when I refresh the page, but how to do it in the background with Ajax? I have tried reloading the PayPal iframe using a hack which bypasses the same origin policy but it didn't work, the Smart Button disappeared.
This is the hack I'm talking about:
const iframe = document.querySelector("iframe");
iframe.src = iframe.src
This is my Smart Button code:
<script>
paypal.Buttons({
style: {
shape: "rect",
color: "gold",
layout: "horizontal",
label: "checkout",
size: "responsive",
tagline: "false"
},
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
currency_code: "GBP",
value: <?php echo number_format($total, 2); ?>
},
}]
});
},
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
alert("Dear " + details.payer.name.given_name + ", Thank you for your payment!");
});
},
onShippingChange: function(data, actions) {
if (data.shipping_address.country_code !== "GB") {
return actions.reject();
}
return actions.resolve();
}
}).render("#paypal-button-container");
}
</script>
Change this line:
value: <?php echo number_format($total, 2); ?>
To call a JavaScript function, which you will need to write and serve as part of the page
value: getCartTotal()
Somewhere, perhaps at the top of the <script>, you will have something like:
window.myCartTotal = '<?php echo number_format($total, 2); ?>';
function getCartTotal() {
return window.myCartTotal;
}
Then have your AJAX also update window.myCartTotal
There are many more elegant ways to code this, but that should get you going.
The proper server-side solution
For best results you shouldn't be setting the amount on the client side's createOrder, and perhaps even more importantly not capturing on the client side. Doing these things on the client side opens you to a whole series of issues and problems. Instead, have the client side call two routes on your server, one for 'Create Transaction' and another for 'Set Up Transaction', documented here: https://developer.paypal.com/docs/checkout/reference/server-integration/
Then, use those two new routes following button approval code's ajax/fetch calls: https://developer.paypal.com/demo/checkout/#/pattern/server
I am trying to implement a paypal smart button within VueJs, however my payment processing application have to handle multiple currencies. The currency the paypal accepts is defined within index.html script file which you cannot dynamically change within VueJS. However is it possible to substitute this approach of something built in within VueJs when the smart button/s gets renders. Meaning that is it possible to dynamically change the ¤cy=XXX or the marchant code parameter depending on condition?
I am unwilling to use vue-paypal-checkout solution as its deprecated regarding endpoints and what response info can you retrieve.
Furthermore I have tried similar solutions to this below but encounter 'paypal' not defined error due to rendering.
let ckeditor = document.createElement('script'); ckeditor.setAttribute('src',"//cdn.ckeditor.com/4.6.2/full/ckeditor.js");
document.head.appendChild(ckeditor);
From https://medium.com/#lassiuosukainen/how-to-include-a-script-tag-on-a-vue-component-fe10940af9e8
Below you can see my smart button render function.
The render button function lives within a function as it gets executed when a page gets mounted. This page in my scenario is a child.
createPayPalOrderFor10() {
var selectedIdOfService = this.serviceIdSelected
var enteredNumberOfCustomer = this.telephoneNumberEntered
paypal.Buttons({
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: '10.00',
}
}]
});
},
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
AXIOS({
method: 'POST',
url: 'api/order/',
data: {
serviceId: selectedIdOfService,
telephoneNumber: enteredNumberOfCustomer,
amount: details.purchase_units[0].amount.value,
customerPayPalId: data.payerID,
paymentCurrency: details.purchase_units[0].amount.currency_code,
payPalCaptureTime: details.create_time,
transactionId: details.purchase_units[0].payments.captures[0].id,
orderNumber: data.orderID
},
})
.then(response => {
console.log(response.data)
})
}
);
}
}).render('#paypal-button-container-10');
},
Any help suggestions or further references will be appreciated.
Thanks
My website has products in several currencies up for sale on the same page, so a person can click the product that sold in EUR and pay in euros, or they can click the product that is sold in USD and pay in usd and so on...
The problem is that once you initialise the new PayPal SDK, you cannot change the currency that it accepts without:
destroying the element
changing the link to the SDK, so that it would accept a different currency
manually injecting it into the page
reinitialising it
As you can probably understand it is not very fast, stable or safe at the same time. Am I missing something? I know that you could send the currency as a parameter in the old Express Checkout version.
The PayPal documentation is infuriating, it is missing a lot of information and doesn't have a big community around it, so I could not find the answer to my question anywhere.
I have tried sending the currency in the payment parameters, but if it is different from the initialised currency, it throws a currency mismatch error once you try to confirm the payment.
Right now I am manually reinjecting and reinitialising the paypal SDK with the correct currency if the user clicks on the option of paying with PayPal, but it is slow and requires hardcoding sleep (although it is probably due to my lack of knowledge, there are probably better ways).
Here's the pseudocode of my current setup that is not acceptable:
initialisePaypalSDK(currency) {
destroy old initialisation
change link to paypal with new currency
inject new link to page
initialise the new sdk
sleep until the paypal variable is defined
showPayPalButton()
}
I expect that there is an easier and a safer way of changing the currency than this. Thanks.
You can use a backend to solve this issue.
First, define createOrder function like this:
const createOrder = async (data, actions) => {
return await axios.get('/yourbackend/api/get_order_id')
}
const onApprove = (data, actions) => {
// ... code to handle the payment status
}
paypal.Buttons({createOrder, onApprove}).render('#paypal-button-container')
Then just implement REST Api for order creation (/v2/checkout/orders) on your backend. Then return id of the created payment.
For those using React, this is now possible with the new library #paypal/react-paypal-js. From the README:
The usePayPalScriptReducer hook can be used to reload the JS SDK script when parameters like currency change. It provides the action resetOptions for reloading with new parameters. For example, here's how you can use it to change currency.
// get the state for the sdk script and the dispatch method
const [{ options }, dispatch] = usePayPalScriptReducer();
const [currency, setCurrency] = useState(options.currency);
function onCurrencyChange({ target: { value } }) {
setCurrency(value);
dispatch({
type: "resetOptions",
value: {
...scriptProviderOptions,
currency: value,
},
});
}
return (
<>
<select value={currency} onChange={onCurrencyChange}>
<option value="USD">United States dollar</option>
<option value="EUR">Euro</option>
</select>
<PayPalButtons />
</>
);
You can add the script once the currency has been selected.
let myScript = document.createElement("script");
myScript.setAttribute(
"src",
"https://www.paypal.com/sdk/js?client-id="your cliend id"&components=buttons¤cy=" + currencyVariable
);
let head = document.head;
head.insertBefore(myScript, head.firstElementChild);
myScript.addEventListener("load", scriptLoaded, false);
function scriptLoaded() {
console.log("Script is ready to rock and roll!");
paypal
.Buttons({
style: {
layout: "vertical",
color: "blue",
shape: "rect",
label: "paypal",
},
createOrder: function (data, actions) {
// Set up the transaction
return actions.order.create({
purchase_units: [
{
amount: {
value: traspasoCantidad,
},
payee: {
},
description: "",
},
],
application_context: {
},
});
},
onApprove: function (data, actions) {
return actions.order.capture().then(function (details) {
/* window.location.href = "approve.html"; */
});
},
onError: function (err) {
myApp.alert("Ha ocurrido un error al hacer el traspaso", "ERROR");
},
})
.render("#paypal-button-container");
}
I am using paypal Buttons SDK. The Code activating the button is:-
paypal.Buttons({
createOrder: ( data, actions ) => {
return actions.order.create({
purchase_units: [{
amount: {
value: this.amount.toFixed(2),
currency_code: "GBP",
}
}]
})
},
onApprove: ( data, actions ) => {
return actions.order.capture().then(details => {
console.log('details',details);
})
},
onError: ( error ) => {
console.log('error',error);
}
}).render('#paypal-button-container')
The User Interface operates as expected, there is then a long pause before the error is returned. The client_id used in the script tag is for a sandbox account. I can find not documentation describing possible cause for the error...
error Error: Order could not be captured
Any advice greatly appreciated.
Paypal.. https://developer.paypal.com/docs/checkout/integrate/#1-get-paypal-rest-api-credentials
As suggested in the comment try to do a curl with this URL :
https://www.sandbox.paypal.com/smart/api/order/ODER_ID/capture
And it replies with code 401 and did some research and end up finding that I was using a wrong account to make payments.
I refresh the PayPal login and login with the correct sandbox buyer account and make the payment and It works.
Probably paypal should give correct errors messages.
Can you check using CURL which returns the Paypal server?
This is a comment but I do not have 50pkt S / O. Sorry.
If you get a Xss message in the console, just try in private navigation, disconnect from your paypal buyer account.
I have had the same "Order could not be captured" error at "actions.order.capture()" in the onApprove callback.
In my case, it worked on the first run but not the subsequent calls. I found my order always had the same invoice_id. I removed invoice_id and Paypal stopped complaining.
It should be good if the invoice_id was always unique.
Same problem with Nuxt , checkout works but catch error response : Error 500 order-could-not-be-captured
<template>
<no-ssr>
<v-layout row wrap>
<div ref="paypal"></div>
</v-layout>
</no-ssr>
</template>
Script
mounted() {
const script = document.createElement("script");
script.src =
"https://www.paypal.com/sdk/js?client-id=MyKeyID";
script.addEventListener("load", this.setLoaded);
document.body.appendChild(script);
},
methods: {
setLoaded: function() {
this.loaded = true;
window.paypal
.Buttons({
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [
{
description: "Test description",
amount: {
currency_code: "USD",
value: 1
}
}
]
});
},
onApprove: async (data, actions) => {
const order = await actions.order.capture();
this.paidFor = true;
console.log(order);
},
onError: err => {
console.log(err);
}
})
.render(this.$refs.paypal);
}