I am trying to integrate the express-checkout API with node.js and express. It's my first time using PayPal's API and I am having trouble getting the lightbox to stop spinning and allowing the user to approve and authorize the payment. When the user clicks the checkout button, the payment method is invoked twice (2 payments are created with 2 different ids with a time difference of about 2 mins) and the lightbox pops up but keeps spinning and doesn't prompt the user to do anything. I appreciate any help on this.
My client-side code:
<div id="paypal-button"></div>
<script>
paypal.Button.render({
env: 'sandbox', // Or 'sandbox',
commit: true, // Show a 'Pay Now' button
style: {
color: 'gold',
size: 'small'
},
payment: function() {
/* Make a call to the server to set up the payment */
return paypal.request.post("http://localhost:3000/paypal/create-payment")
.then(function(res) {
return res.id;
})
},
onAuthorize: function(data, actions) {
/*
* Execute the payment here
*/
return paypal.request.post("http://localhost:3000/paypal/execute-payment",
{
paymentID: data.paymentID,
payerID: data.payerID
}).then(function(res) {
// The payment is complete!
// You can now show a confirmation message to the customer
window.alert("PAYMENT COMPLETE!");
});
},
onCancel: function(data, actions) {
/*
* Buyer cancelled the payment
*/
console.log("BUYER CANCELLED!");
},
onError: function(err) {
/*
* An error occurred during the transaction
*/
console.log(err);
}
}, '#paypal-button');
</script>
My server-side code:
// CREATE PAYMENT ROUTE
app.post("/paypal/create-payment", function(req, res){
// CREATE JSON REQUEST
var create_payment_json = {
"intent": "sale",
"redirect_urls":
{
"return_url": "https://localhost:3000/success",
"cancel_url": "https://localhost:3000/cancel"
},
"payer":
{
"payment_method": "paypal"
},
"transactions": [
{
"amount":
{
"total": "",
"currency": "USD",
"details":{
"subtotal": "",
"shipping": "0.00",
"tax": "0.00",
"shipping_discount": "0.00"
}
},
"item_list":
{
"items": []
},
"description": "Test shopping cart transaction."
}]
};
// Change the "total", "subtotal", and "items" array in the json request
create_payment_json["transactions"][0]["amount"]["total"] = cart.totalPrice.toString();
create_payment_json["transactions"][0]["amount"]["details"]["subtotal"] = cart.totalPrice.toString();
// Dummy variable for less typing
var itemsArray = create_payment_json["transactions"][0]["item_list"]["items"];
// 1) Access each puppy in the cart
// 2) Create an object with the following properties:
// quantity, name, price, currency, description, and tax
// 3) push that object into itemsArray
cart["puppies"].forEach(function(puppy){
var dummy = {
"quantity" : "1",
"name" : puppy.name,
"price" : puppy.price.toString(),
"currency" : "USD",
"description" : puppy.description,
"tax" : "0"
};
itemsArray.push(dummy);
});
// Send a Create Payment request to the Payments API
paypal.payment.create(create_payment_json, function (error, payment){
if (error) {
console.log(error);
} else {
console.log(payment);
console.log("==============");
}
});
});
// EXECUTE PAYMENT ROUTE
app.post("/paypal/execute-payment", function(req, res){
var payerId = { payer_id: req.query.PayerID };
var paymentId = req.query.paymentId;
console.log("Payer ID: " + payerId);
console.log("Paymend ID: " + paymentId);
paypal.payment.execute(paymentId, payerId, function(error, payment){
if (error){
console.log(error);
} else {
if(payment.state === "approved"){
console.log(payment);
res.send("SUCCESS");
} else {
console.log("payment not successful");
}
}
});
});
The console.log(payment) in the paypal.payment.create method is run twice. That's how I know that the payment method is invoked twice. I don't know how to solve this. Any help or suggestion is appreciated!
Update: changed return res.paymentId into return res.id, but no luck.
Related
I am writing a chrome extension that makes requests to an API and I have noticed that after I create a notification from background script using chrome's notification API, the listeners on the buttons from the notification are executed multiple times. on the first run only once and then increasing. I figured that the listeners just add up on the page but I couldn't find a way to sort of refresh the background page.
This is the function that creates the notification and it's listeners.
var myNotificationID
const displayNotification=(userEmail, password, website,username) =>{
chrome.notifications.create("", {
type: "basic",
iconUrl: "./icon128.png",
title: "PERMISSION",
requireInteraction: true,
message: "question",
buttons: [{
title: "YES",
}, {
title: "NO",
}]
}, function(id) {
myNotificationID = id;
})
chrome.notifications.onButtonClicked.addListener(function(notifId, btnIdx) {
if (notifId === myNotificationID) {
if (btnIdx === 0) {
console.log('inserting')
try{
fetch (`http://localhost:8080/users/${userEmail}/accounts`,{
})
}catch(err){
}
} else if (btnIdx === 1) {
console.log('clearing')
chrome.notifications.clear(myNotificationID)
}
}
});
}
And this is where the function is called
chrome.runtime.onMessage.addListener((message, sender, response)=>{
if(message.message === 'showNotification'){
console.log('received insert')
displayNotification(message.userEmail,message.password, message.currentSite,message.username)
response({status:"received"})
}
})
the fetch within the listener is executed multiple times but the log from the onMessage listener is only displayed once, so the listener is the problem here.
I tried chrome.notifications.onButtonClicked.removeListener(), but as i mentioned there was no success.
Are there any other ways in which i could clean the listeners from the background script once they are used?
Using a notification store:
const notificationsByID = {};
chrome.notifications.onButtonClicked.addListener((notifId, btnIdx) => {
// Avoid access to the notification if not registered by displayNotification
if (!notificationsByID[ notifId ]) { return null; }
if (btnIdx === 0) {
console.log('inserting')
try{
fetch (`http://localhost:8080/users/${ notificationsByID[ notifId ].userEmail }/accounts`,{ /**/ });
}catch(err){
console.log(err);
}
delete notificationsByID[ notifId ]; // Cleanup
} else if (btnIdx === 1) {
console.log('clearing')
chrome.notifications.clear(myNotificationID);
delete notificationsByID[ notifId ]; // Cleanup
}
});
chrome.notifications.onClosed.addListener((notifId) => {
if (notificationsByID[ notifId ]) { delete notificationsByID[ notifId ]; }
});
const displayNotification=(userEmail, password, website,username) =>{
chrome.notifications.create("", {
type: "basic",
iconUrl: "./icon128.png",
title: "PERMISSION",
requireInteraction: true,
message: "question",
buttons: [{ title: "YES", }, { title: "NO", }]
}, function(id) {
// Insertion
notificationsByID[ id ] = { userEmail, password, website,username };
})
}
I finished my app and I am planning to host it online. Can my clients use sandbox accounts to make real transactions? This is my first time developing an app and making it have real online transactions using Paypal. Does it have something to do with coding or I have to change a setting in Paypal itself?
Payments.vue
<template>
<div>
<div ref="paypal"></div>
</div>
</template>
mounted()
{
const script = document.createElement("script");
script.src =
"https://www.paypal.com/sdk/js?client-MY-CLIENT-ID";
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: this.product.description,
amount: {
currency_code: "USD",
value: this.product.price,
},
},
],
});
},
onApprove: async (data, actions) => {
const order = await actions.order.capture();
this.$q.notify({
message: "Transaction Successful!",
color: "green",
actions: [
{
label: "Dismiss",
color: "white",
handler: () => {
/* ... */
},
},
],
});
let dateObj = new Date();
let month = dateObj.getMonth();
let day = dateObj.getDate();
let year = dateObj.getFullYear();
let output = month + "" + day + "" + year;
this.$store
.dispatch("SAVE_ENTRY", {
username: this.username,
password: this.password,
confirmPass: this.confirmPass,
access_id: output + this.newAccData,
chosenSchoolId: this.chosenSchoolId,
})
.then((res) => {
if (res.data === "Success1") {
this.$q.notify({
message:
"Transaction Successful! Please complete your registration process inside the website.",
color: "green",
actions: [
{
label: "Dismiss",
color: "white",
handler: () => {
/* ... */
},
},
],
});
}
});
console.log(order);
},
onError: (err) => {
console.log(err);
},
})
.render(this.$refs.paypal);
},
}
I believe there's a sandbox environment, on https://sandbox.paypal.com/ . You have to change the API endpoint to something like that. The sandbox uses separate accounts that can be made in the developer environment on PayPal API pages.
You should be able to set up sandbox accounts here:
https://developer.paypal.com/developer/accounts/
However I dont think this is supposed to be a Javascript/Vue question, you'd want your PayPal API calls to take place on the server side as much as possible.
Currently I try to integrate amazon payment.
I want to display the button, created with the function:
OffAmazonPayments.Button("AmazonPayButton", myAmznMerchantID
and after the User has logged in, I want to display the AdressBook Widget and the Payment Widget on the same Page.
The Button and the Widgets are displayed correctly, but when I try to confirm the Payment, I'm getting the Error-Constrains Message:
PaymentPlanNotSet The buyer has not been able to select a Payment
method for the given Order Reference.
But the Payment was selected.
I hope you can help me, to find my failure in this Javascript code:
window.onAmazonLoginReady = function () {
amazon.Login.setClientId(myAmznClientID);
};
window.onAmazonPaymentsReady = function() {
var __accessToken = 0;
var __orderReferenceId = 0;
show_amazon_Button();
function show_amazon_Button() {
OffAmazonPayments.Button("AmazonPayButton", ibuiAmazonPaymentsInfos.ibAmznMerchantID, {
type: "PwA",
color: "Gold",
size: "small", // "medium",
language: "de-DE",
authorization: function () {
loginOptions = { scope: "profile:user_id", popup: true };
authRequest = amazon.Login.authorize(loginOptions, function(response) {
if (response.error) {
//show Error
return;
} else {
__accessToken = response.access_token;
show_Adress_Widget();
}
});
},
onError: function(error) {
//handleError
}
});
}
function show_Adress_Widget() {
new OffAmazonPayments.Widgets.AddressBook({
sellerId: ibuiAmazonPaymentsInfos.ibAmznMerchantID,
onOrderReferenceCreate: function (orderReference) {
__orderReferenceId = orderReference.getAmazonOrderReferenceId();
//do Stuff
},
onAddressSelect: function (orderReference) {
show_Amazon_Wallet();
},
design: {
designMode: 'responsive'
},
onError: function (error) {
//handle Error
}
}).bind("readOnlyAddressBookWidgetDiv");
}
function show_Amazon_Wallet() {
new OffAmazonPayments.Widgets.Wallet({
sellerId: myAmznMerchantID,
onOrderReferenceCreate: function(orderReference) {
//do Stuff
},
design: {
designMode: 'responsive'
},
onPaymentSelect: function(orderReference) {
//activate buy button
},
onError: function(error) {
//handle error
}
}).bind('AmazonWalletWidgetDiv');
}
} //onAmazonPaymentsReady
In PHP I create the OrderReference over:
$params = array(
'order_reference_id' => $orderReferenceId,
'amount' => $amount,
'seller_order_id' => $buchungsKopf->getBuchung_nr(),
);
$responseObject = $client->setOrderReferenceDetails($params);
And even if I had selected a payment method before, I'm getting the "PaymentPlanNotSet" in the responseObject.
Same error, when I try to confirm the orderReference
$responseObject2 = $client->confirmOrderReference($params);
Before I had integrate the AdressWidget, I was able to do the payment.
Can you see what I'm doing wrong?
It seems I have found my failure.
When I want to display both Widgets (Adress and Wallet) on the same page, then I can not use the onOrderReferenceCreate-Method in the OffAmazonPayments.Widget.Wallet.
It seems like then it create a new order reference wich is not the same as the orderreference created by the AdressBook Widget.
Now I have delete this part of the Wallet Widget and everything seems to work fine.
I am testing a web app with a PayPal express checkout button
I click on Pay now, I log-in with one of my sandbox accounts and pay with Paypal fundings, but payment doesn't end well and I receive a 400 error: {"ack":"contingency","contingency":"INSTRUMENT_DECLINED","meta":...
my code is
paypal.button.render({
env: 'sandbox',
client: {
sandbox: 'AAAAAAAAAAA',
production: '<????>'
},
locale: 'en_US',
commit: true, // Show a 'Pay Now' button
style: {
size:'medium',
color: 'silver',
label: 'pay',
},
payment: function(data, actions) {
return actions.payment.create({
payment: {
transactions: [
{
amount: { total: ctrl.amount, currency: 'EUR' }
}
]
},
experience: {
input_fields: {
no_shipping: 1
}
},
});
},
onAuthorize: function(data, actions) {
return actions.payment.execute().then(function(payment) {
alert("Payment completed!");
paymentCompleted();
});
},
onCancel: function(data, actions) {
alert("Payment doesn't end well :-(");
location.href=location.href;
}
}, '#paypal-button');
I noticed the same behaviour in interactive demo, I can login, pay, but the payment is not accepted due to INSTRUMENT_DECLINED.
Any idea about where can be the problem?
I tried to create different sandbox accounts, but nothing changes.
I have a profile page for each seller that is privately and publicly available. I have the seller (user) information published and sending data to the client but I'm struggling with sending that seller's product collection to the client.
If I could send the user information for the profile page to the Products collection I could publish the products for the specific seller, but I'm currently stuck. I used this post as a reference: Meteor: User Profile Page with Iron Router
This is what I have thus far, it's not DRY:
template on client
<template name="profile">
<div style="margin-top 5em;">
<h1>Profile.Name: {{profile.name}}</h1>
<h1>Username: {{username}}</h1>
<h1>Profile.Description: {{profile.description}}</h1>
{{#each products}}
<h1>Products! {{username}}</h1>
<h1>price {{price}}</h1>
{{/each}}
</div>
</template>
helpers on client
Meteor.subscribe("userProfile", this.params.username);
Template.profile.helpers({
products : function(){
return Products.find();
}
});
router.js on lib
Router.route("/artist/:username", {
name:"profile",
waitOn:function(){
var username=this.params.username;
return Meteor.subscribe("userProfile", username);
return Meteor.subscribe("products-by-id", username);
},
data:function(){
var username=this.params.username;
return Meteor.users.findOne({username:username});
return Meteor.subscribe("products-by-id", username);
},
fastRender: true
});
publications.js on server
Meteor.publish("userProfile",function(username){
// simulate network latency by sleeping 2s
Meteor._sleepForMs(2000);
var user=Meteor.users.findOne({
username:username
});
if(!user){
this.ready();
return;
}
if(this.userId==user._id){
}
else{
return Meteor.users.find(user._id,{
fields:{
"profile.name": 1,
"profile.description" : 1,
"_id" : 1,
"username" : 1,
"profile.username" : 1
}
});
return Products.find({username: user.username});
}
});
Meteor.publish("allProducts", function(){
return Products.find();
});
Thank you for any input!
You can add reywood:publish-composite package. This package allow "link" collecttion like joins.
Meteor.publishComposite('AutorTexts', function(avatar) {
check(avatar, Match.Any);
return {
find: function(autorId) {
check(autorId, Match.Any)
return Roman.find({
autor_id: avatar
});
},
children: [{
find: function(avtor) {
return Avtor.find({
_id: avatar
});
}
}]
};
});
this code returns data from two collections: Roman & Avtor (code is weird, i know).
Also you need configure iron-router subscribe on route:
Router.route('/a/:_id', function() {
//console.log(this.params._id);
this.render('AvtorAll');
SEO.set({
title: 'blahblah title'
});
}, {
path: '/a/:_id',
// data: {id: this.params._id},
name: 'AvtorAll',
waitOn: function() {
return Meteor.subscribe('AutorTexts', this.params._id);
},
onAfterAction: function() {
if (!Meteor.isClient) { // executed only on client side!!
return;
}
SEO.set({
title: 'blahblah : ' + Avtor.findOne().autor_name,
og: {
"title": "blahblah : " + Avtor.findOne().autor_name,
"description": "blahblah . Powered by MeteorJS",
"url": Router.current().route.path(this),
"site_name": "blahblah ",
"locale": "you_locale_here" // didnt work
}
});
}
});