Paypal suddenly doesn't call "onAuthorize" method during express checkout - javascript

My problem today is that I have a web application with an express checkout to execute.
I used the Client - Server architecture to avoid security issues.
So my client code is the following:
paypal.Button.render({
env : 'sandbox',
commit: true,
style: {
size: 'medium',
color: 'gold',
shape: 'pill',
label: 'checkout'
},
locale: 'en_US',
payment: function() {
nickname = $("#nickname").val();
amount = $("#amount").val();
description = $("#description").val();
data = {
"nickname" : nickname,
"amount" : amount,
"description" : description
};
return new paypal.Promise(function(resolve, reject) {
// Call your server side to get the Payment ID from step 3, then pass it to the resolve callback
jQuery.post("/newPayment", data).done(function(data){
console.log("HEI I GOT THE PAYMENT JSON = " + data);
parsed_data = JSON.parse(data);
resolve(parsed_data['id']);
});
});
},
onAuthorize: function(data, actions) {
console.log("JSON = " + JSON.stringify(actions.payment.getTransactions()[0]));
console.log("TRANSACTION = " + actions.payment.getTransactions()[0])
console.log("PAYMENT SUCCESSFUL");
return actions.payment.execute();
},
onError: function(data, actions){
console.log("ERRORRRRRR");
$("#warning").show();
}
}, '#paypal-button');
My server side code:
app.post("/newPayment",function(req,res){
console.log("RECEIVING POST WITH AMOUNT = " + req.body.amount);
//CONFIGURE PAYMENY - PAYPAL PHASE 1
var first_config = {
'mode': 'sandbox',
'client_id': '<MY CLIENT ID>',
'client_secret': '<MY SECRET>'
};
paypal.configure(first_config);
console.log("AMOUNT AUTHORIZED = " + req.body.amount);
//CREATING PAYMENT
PAYMENT = {
"intent": "sale",
"payer": {
"payment_method": "paypal"
},
"redirect_urls": {
"return_url": "http://return.url",
"cancel_url": "http://cancel.url"
},
"transactions": [{
"amount": {
"currency": "EUR",
"total": req.body.amount
},
"description": "This is the payment description."
}]
};
//CREATING PAYMENT AND SENDING IT TO THE CLIENT
paypal.payment.create(PAYMENT, function (error, payment) {
if (error) {
throw error;
} else {
console.log("Create Payment Response");
res.send(JSON.stringify(payment));
}
});
Now, the paypal window opens and I can insert all my data to execute a test payment.
But when I confirm payment and Paypal service should call my "onAuthorize" method, instead methon "onError" fires...
What could be the problem?
Redirect URLS?
The fact that I should execute the paypal.configure() with a .then() after it to ensure the flow of actions?
I'm really exploding trying to use Paypal services
EDIT:
Pushing my code on cloudno.de the payment is executed correctly, but running it locally it doesn't work... I don't know what to say ahah

actions.payment.getTransactions is not a function. You need to call actions.payment.get().then(function(result) { ... })

Related

Paypal SDK problem get an error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

I get status code 201 but when I try redirect to the approval_url I get
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I try to not redirect and all work just fine, when I click the approval_url manually.
This is my code:
var express = require("express");
var router = express.Router();
var middleAware = require("../middleware");
var paypal = require("paypal-rest-sdk");
var User = require("../models/user");
var Shoe = require("../models/shoe");
paypal.configure({
'mode': 'sandbox', //sandbox or live
'client_id': 'AUN6mxOAFtJS5hrlUGdyd-Fe1VOE6zu63W6dYztXhOXOpeT0ps9JbF9N3lII-f3EP1o7G2MHs9flc3Ho',
'client_secret': 'someSecretId'
});
//pay with paypal
router.post("/cart/:id/pay", function(req, res){
User.findById(req.params.id).populate("cart").exec(function(err, foundUser){
if(err){
console.log(err);
res.redirect("back")
}else{
//find all shoes to checkout
var newItems = [];
for(var i=0;i < foundUser.cart.length;i++){
itemToPush = {
"name": foundUser.cart[i].name,
"sku": foundUser.cart[i].length,
"price": foundUser.cart[i].price,
"currency": "USD",
"quantity": 1
}
newItems.push(itemToPush);
}
//totalprice
var totalPrice = 0;
foundUser.cart.forEach(function(item){
totalPrice += item.price;
return totalPrice;
});
res.redirect("back");
// create paypal payment JSON
var create_payment_json = {
"intent": "sale",
"payer": {
"payment_method": "paypal"
},
"redirect_urls": {
"return_url": "http://127.0.0.1:3000/shop",
"cancel_url": "http://127.0.0.1:3000/shop"
},
"transactions": [{
"item_list": {
"items": newItems
},
"amount": {
"currency": "USD",
"total": totalPrice
},
"description": "This is the payment description."
}]
};
// console.log(JSON.stringify(create_payment_json));
paypal.payment.create(create_payment_json, function(err, payment){
if(err){
throw err;
}else{
console.log(payment);
for(var i=0;i < payment.links.length;i++){
if(payment.links[i].rel === 'approval_url'){
console.log("FOUND " + payment.links[i].href);
res.redirect(payment.links[i].href);
}
}
}
});
}
});
});
module.exports = router;
Here the problem I get good status code but then this error:
(node:14648) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
(node:14648) DeprecationWarning: current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.
Server started!
{
id: 'PAYID-L3AZRLQ4LA763246G079113E',
intent: 'sale',
state: 'created',
payer: { payment_method: 'paypal' },
transactions: [
{
amount: [Object],
description: 'This is the payment description.',
item_list: [Object],
related_resources: []
}
],
create_time: '2020-05-17T20:03:57Z',
links: [
{
href: 'https://api.sandbox.paypal.com/v1/payments/payment/PAYID-L3AZRLQ4LA763246G079113E',
rel: 'self',
method: 'GET'
},
{
href: 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-2C992495UH4895047',
rel: 'approval_url',
method: 'REDIRECT'
},
{
href: 'https://api.sandbox.paypal.com/v1/payments/payment/PAYID-L3AZRLQ4LA763246G079113E/execute',
rel: 'execute',
method: 'POST'
}
],
httpStatusCode: 201
}
FOUND https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-2C992495UH4895047
_http_outgoing.js:526
throw new ERR_HTTP_HEADERS_SENT('set');
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:526:11)
at ServerResponse.header (C:\Users\Zohar Banai\Desktop\personal projects\ShopProject\node_modules\express\lib\response.js:771:10)
at ServerResponse.location (C:\Users\Zohar Banai\Desktop\personal projects\ShopProject\node_modules\express\lib\response.js:888:15)
at ServerResponse.redirect (C:\Users\Zohar Banai\Desktop\personal projects\ShopProject\node_modules\express\lib\response.js:926:18)
at C:\Users\Zohar Banai\Desktop\personal projects\ShopProject\routes\cart.js:116:33
at IncomingMessage. (C:\Users\Zohar Banai\Desktop\personal projects\ShopProject\node_modules\paypal-rest-sdk\lib\client.js:140:13)
at IncomingMessage.emit (events.js:323:22)
at endReadableNT (_stream_readable.js:1204:12)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {
code: 'ERR_HTTP_HEADERS_SENT'
}
Some pointers:
A redirect with the HTTP header 'Location: newurl' cannot be done if the HTTP headers have already been sent somewhere earlier in your application's response to the request. But, it isn't worth spending time fixing this issue -- see the below instead.
Redirects are the old way of doing things. Don't use any redirects. At all. At all at all. Instead, use a modern "in context" flow: keep your site loaded in the background and use this front-end: https://developer.paypal.com/demo/checkout/#/pattern/server (that's the server integration pattern; for a demo of what the UI will look like, click over to the client side pattern since that one is interactively clickable)
PAYIDs are from PayPal's old v1/payments REST API. Don't use v1/payments in your backend. Instead, change your server-side API calls to be v2/checkout/orders. If you want an SDK, here are the latest ones: https://developer.paypal.com/docs/api/rest-sdks/

PayPal REST SDK: Remove shipping address and payment authorization so it does go to pending state [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I want to sell a feature/features to my client and the payment should be instant, so that it unlocks a feature immediately whenever the transaction is completed.
I managed to execute the transaction and the sandbox personal account shows that transaction but the sandbox merchant account doesn't show anything to approve it. sandbox personal account shows that " This is a temporary authorization to make sure your payment method will cover the payment. Your payment method will be charged when jawad merchant's Test Store completes your order."
this is the code :
var express = require('express');
const router = express.Router();
var paypal = require('paypal-rest-sdk');
paypal.configure({
'mode': 'sandbox', //sandbox or live
'client_id': 'client id',
'client_secret': 'client secret'
});
// payment process
router.use('/buy', (req, res) => {
// payment object
var payment = {
"intent": "authorize",
"payer": {
"payment_method": "paypal"
},
"redirect_urls": {
"return_url": "http://localhost:4040/xyz/paypal/success",
"cancel_url": "http://localhost:4040/xyz/paypal/err"
},
"transactions": [{
"item_list": {
"items": [{
"name": 'item1',
"sku": "item",
"price": '39',
"currency": "USD",
"quantity": 1
}]
},
"amount": {
"total": 39.00,
"currency": "USD"
},
"description": "A feature "
}]
}
// calling the create Pay method
createPay(payment)
.then((transaction) => {
var id = transaction.id;
var links = transaction.links;
var counter = links.length;
while (counter--) {
if (links[counter].method == 'REDIRECT') {
// redirecting to paypal where user approves the transaction
return res.redirect(links[counter].href)
}
}
})
.catch((err) => {
console.log(err);
res.redirect('/err');
});
});
// success page
router.use('/success', (req, res) => {
var paymentId = req.query.paymentId;
var payerId = { 'payer_id': req.query.PayerID };
paypal.payment.execute(paymentId, payerId, function(error, payment){
if(error){
console.error(error);
} else {
if (payment.state === 'approved'){
res.send('payment completed successfully');
console.log(payment);
} else {
res.send('payment not successful');
}
}
});
})
// error page
router.use('/err', (req, res) => {
console.log(req.query);
res.redirect('https://soundcloud.com/');
})
// helper functions
var createPay = (payment) => {
return new Promise((resolve, reject) => {
paypal.payment.create(payment, function (err, payment) {
if (err) {
reject(err);
}
else {
resolve(payment);
console.log(payment)
}
});
});
}
module.exports = router;
Help me with the following:
My transactions shouldn't be linked to shipping addresses,
Transaction should be instant and paypal personal sandbox account
shouldn't say this:
User can buy one or many features,
Explain what the helper function at the end is doing
Thankyou in advance. I hope this code helps someone else too
In the Classic API there was a flag for NOSHIPPING you could include in your request that would disable shipping address requirements during checkout. I know it's in REST somewhere, but I'm struggling to find it in the reference right now.
Instead of "authorize" you should use "sale" for the intent parameter.
Not sure what you're asking here..?? Just build your shopping cart so your user can add as many items as they want to their cart, and then pass those cart details into your PayPal payment request.
It seems to be handling the actual pay call to PayPal (ie. paypal.payment.create)
This document shows the use of the no_shipping field in button code:
https://developer.paypal.com/docs/checkout/how-to/customize-flow/#pass-experience-profile-options
To call the API directly, you have to create a web experience profile object containing this field (input_fields.no_shipping):
https://developer.paypal.com/docs/api/payment-experience/v1/#web-profiles_create
Then reference it in your /payment call (the experience_profile_id field)
https://developer.paypal.com/docs/api/payments/v1/#payment

Paypal purchase not happening in HTML with ASP.NET

I have the little Paypal window working in the sandbox and production mode. The onAuthorize function gets called and my message window pops up thanking you for the purchase and giving the purchased credits to the customer.
paypal.Button.render({
<% if (useBankAccount) { %>
env: 'production', // Or 'sandbox', 'production'
<% } else { %>
env: 'sandbox', // Or 'sandbox', 'production'
<% }%>
client: {
sandbox: 'Axxx',
production: 'Axxx'
},
commit: true,
style: {
size: 'responsive',
color: 'gold',
shape: 'pill',
label: 'pay'
},
payment: function (data, actions) {
return actions.payment.create({
payment: {
transactions: [
{
amount: { total: currentPayment, currency: 'USD' }
}
]
}
});
},
onAuthorize: function (data, actions) {
if (window.ga) { // Google Analytics
window.ga('send', 'event', 'Credits', 'Purchase', 'Payment', currentPayment);
window.ga('send', 'event', 'Credits', 'Purchase', 'Credits', currentCredits);
}
document.getElementById("<%= PurchaseDone.ClientID %>").value = "Yes";
document.getElementById("<%= PurchaseAmount.ClientID %>").value = currentPayment;
TheForm.submit();
},
onCancel: function (data, actions) {
OpenMessageWindow("The payment was cancelled.");
},
onError: function (err) {
var result = err.message.toString().indexOf("Amount cannot be zero")
if (result > 0) {
OpenMessageWindow('Payment Error: The purchase amount must be more than zero');
return;
}
OpenMessageWindow('Payment Error:<br/>' + err.message)
}
}, '#paypal-button');
Looking the data passed into OnAuthorize()
{paymentToken: "EC-xxx",
orderID: "EC-xxx",
payerID: "FTxxx",
paymentID: "PAY-xxx",
intent: "sale", …}
intent:"sale" orderID:"EC-xxx"
payerID:"FTxxx"
paymentID:"PAY-xxx"
paymentToken:"EC-xxx"
returnUrl:
"https://www.paypal.com/?paymentId=PAY-0Kxxx&token=EC-xx&PayerID=FTxxx"
__proto__ :Object
actions is
{close: ƒ, redirect: ƒ, payment: {…}, order: {…}, restart: ƒ, …}
To see those values, I commented out the TheForm.Submit(), which could have been the problem but the payment still did not happen.
The problem is, in production, the customer purchase does not happen. The customer (me) logs into their Paypal account, presses Pay Now, the window disappears calling onAuthorize.
No payment registers on the customer account. No payment transaction happens and my software gives the credits for free.
Ideas? What can I look at? Paypal telephone support does not know anything about development.
Thank you for any help.
The solution is to call actions.payment.execute() in the onAuthorize callback. It causes a second busy indicator that does the payment. It's like the onAuthorize says the user accepts, now do you accept?
Now, this is my working code.
paypal.Button.render({
<% if (useBankAccount) { %>
env: 'production', // Or 'sandbox', 'production'
<% } else { %>
env: 'sandbox', // Or 'sandbox', 'production'
<% }%>
client: {
sandbox: 'xxx',
production: 'xxx'
},
commit: true,
payment: function (data, actions) {
return actions.payment.create({
payment: {
transactions: [
{
amount: { total: currentPayment, currency: 'USD' }
}
]
}
});
},
onAuthorize: function (data, actions) {
return actions.payment.execute().then(function (payment) {
// setup the data to post
TheForm.submit();
});
},
onCancel: function (data, actions) {
OpenMessageWindow("The payment was cancelled.");
},
onError: function (err) {
var result = err.message.toString().indexOf("Amount cannot be zero")
if (result > 0) {
OpenMessageWindow('Payment Error: The purchase amount must be more than zero');
return;
}
OpenMessageWindow('Payment Error:<br/>' + err.message)
}
}, '#paypal-button');
I added two things from the PayPal example to get it working.
The "commit: true," is needed to change the Continue button to Pay Now. Unbelievably, the default is to have a button called Continue that pays.
The second is calling actions.payment.execute() in onAuthorize. Not something you could figure out except by being told.
I am quite incredulous that the PayPal example does not include these two items providing an example that simply does not work. AND you don't know about it until you test a live purchase, sandbox seems fine.
Additionally, Stack Overflow is PayPal's development support, why was this question never answered? I actually found the solution to the problem on a web page explaining how bad PayPal is compared to Stripe. The page gave a nice example of the complicated PayPal code that had the missing function call.

Laravel 5.1 Cashier - Adds customer but without info

I am using Laravel 5.1 trying to set it up with Stripe using Cashier.
I am using a custom button to execute the javascript (using Angular):
$scope.subscribe = function(plan){
var handler = StripeCheckout.configure({
key: 'pk_test_*************',
image: '/img/documentation/checkout/marketplace.png',
locale: 'auto',
token: function(token) {
// Use the token to create the charge with a server-side script.
// You can access the token ID with `token.id`
var data = 'stripeToken=' + token.id;
$http.post("/createSubscription", data).success(function(data, status) {
console.log(data);
});
}
});
handler.open({
name: 'basic',
description: 'basic monthly $100',
currency: "usd",
amount: 10000
});
$(window).on('popstate', function() {
handler.close();
});
};
And in my Laravel code I have:
public function createSubscription(Request $request)
{
$user = JWTAuth::parseToken()->authenticate();
$token = Input::get('stripeToken');
$user->subscription("basic_plan")->create($token);
return 'test';
}
But I keep getting an error saying "this customer has no attached payment source".
The returned token at this line:
token: function(token) {
...
Does contain the users email, card info (+ card token), and stripe token. But when I check my Stripe dashboard, a customer is added without any data (no card, no email and not set up with the subscription).
I am trying to create customers with a subscription plan via this form.
(I do have the Billable trait set up and included in the controller)

Meteor.loginWithFacebook not storing email address

I'm using
accounts-password
accounts-facebook
service-configuration
On the server:
ServiceConfiguration.configurations.remove({
service: 'facebook'
});
ServiceConfiguration.configurations.upsert(
{ service: 'facebook' },
{ $set: {
appId: 'xxxxxxxxxxxxxxxx',
secret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
}
}
);
On the client:
Meteor.loginWithFacebook({requestPermissions: ['email']}, function(error){
if (error) {
throwError('Could not log in');
} else {
// success
}
});
This configuration prompts the user for Facebook verification with access to email and returns no errors. A new user is stores with the correct name and ID. But this e-mail is not stored in the user object.
This is what I get when i fetch a user from the shell.
{ _id: 'xxxxxxxxxxxxxxxxx',
createdAt: Mon Jul 13 2015 13:36:21 GMT+0200 (CEST),
services:
{ facebook:
{ accessToken: 'xxxxxxxxxxxxxxxxxxxxx...',
expiresAt: 1441971380621,
id: 'xxxxxxxxxxxxxxxxx',
name: 'xxxx xxxxxx' },
resume: { loginTokens: [Object] } },
profile: { name: 'xxxx xxxxxx' } }
Why is the email address from Facebook not being stored?
While I have reported the issue to Meteor I've found a quick fix for the time being.
On the server run this:
Accounts.onCreateUser(function(options, user) {
if (user.hasOwnProperty('services') && user.services.hasOwnProperty('facebook') ) {
var fb = user.services.facebook;
var result = Meteor.http.get('https://graph.facebook.com/v2.4/' + fb.id + '?access_token=' + fb.accessToken + '&fields=name,email');
if (!result.error) {
_.extend(user, {
"emails": [{"address": result.data.email, "verified": false}],
"profile": {"name": result.data.name}
});
}
}
return user;
});
[EDIT]
The previous code works, but since it causes problems with other login methods I went with another approach:
In the client I call a function on the server when the user authenticates with Facebook:
Meteor.loginWithFacebook({requestPermissions: ['email']}, function(error){
if (error) {
//error
} else {
Meteor.call('fbAddEmail');
}
});
And then on the server:
Meteor.startup(function () {
Meteor.methods({
fbAddEmail: function() {
var user = Meteor.user();
if (user.hasOwnProperty('services') && user.services.hasOwnProperty('facebook') ) {
var fb = user.services.facebook;
var result = Meteor.http.get('https://graph.facebook.com/v2.4/' + fb.id + '?access_token=' + fb.accessToken + '&fields=name,email');
if (!result.error) {
Meteor.users.update({_id: user._id}, {
$addToSet: { "emails": {
'address': result.data.email,
'verified': false
}}
});
}
}
}
});
});
Facebook API may not return the email address for some users even if you asked for the "email" permission. The official API docs state that:
[email] field will not be returned if no valid email address is available.
One of the reason may be an unconfirmed email address and another one a user who registered with a mobile phone number only.

Categories