PayPal Smart Button transaction not charging after 'approval' - paypal

After spending a full day on this dangerous thing, I can't understand how a transaction show APPROVED, I get an order ID, but there is no charge (not for buyer or receiver).
I am on live environment after tested on sandbox successfully.
So the code is long but basically on the server side we first create the transaction:
const request = new paypal.orders.OrdersCreateRequest();
request.prefer("return=representation");
request.requestBody({
intent: 'CAPTURE', //CAPTURE
purchase_units: [{
amount: {
currency_code: currency,
value: finalPrice
},
payee: {
email_address: payee
},
shipping: shipObj
}]
});
and later we approve the order with PayPal :
let request = new paypal.orders.OrdersGetRequest(orderID);
let order;
try {
order = await client.execute(request);
}
catch (err) {
console.error("error",err);
return res.sendStatus(500);
}
At this point i get order.result.status = APPROVED .
This is a live environment (the client and secret keys).
How can you send APPROVED to developer, and give an orderID but not charge ?
This is such a dangerous thing to do and can literally ruin businesses.
So then i found out there is a link to your order ID :
https://api.paypal.com/v2/checkout/orders/4PE63643WC652674S
If you look in this page you get this :
"message":"Authentication failed due to invalid authentication credentials or a missing Authorization header."
Now, only god knows what this means, and this is a failur message on an order ID page, which mean the orderID means nothing ??
So i also check the paypal link with my client-ID (should be identical to my client id in sdk right?) :
https://www.paypal.com/sdk/js?client-id=xxxxxxxxx&merchant-id=xxx#yyyy.com&disable-funding=credit,card&currency=USD
Which seems ok and contains the right email.

Seems like you may have solved the issue and that you were missing the capture step, which is a required step to commit the transaction and must always occur after approval.
Just to review the best ways to integrate, if you are doing so with API calls from a server, you'll want to set make two routes on your server, one for 'Set up Transaction' and one for 'Capture Transaction', documented here: https://developer.paypal.com/docs/checkout/reference/server-integration/
Then, the best JS customer approval flow to pair with those server-side routes is this: https://developer.paypal.com/demo/checkout/#/pattern/server

Related

php8 and Paypal IPN setup: Where does db INSERT upon successful handshake go?

Of the three files here- https://github.com/paypal/ipn-code-samples/tree/master/php
I have my Webhook URL set to the stock github version of- PaypalIPN.php (this validates successfully 100% of the time, if I use example_usage.php... Doesn't work. If I use both as Webhooks... Doesn't work).
From the Paypal button side of things I'm able to post my website's active user (call him $MrUser) with this:
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
"custom_id":"<?php echo $MrUser; ?>",
"description":"One hundred Webdollars",
"amount":
{
"currency_code":"USD",
"value":1.99
}
}]
});
},
Here's the SQL I need to run upon successful validation (I change $MrUser to $kitty for clarity's sake):
require 'sqlconfig.php';
$dsn = "mysql:host=$host;dbname=$db;charset=UTF8";
try {
$pdo = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
echo $e->getMessage();
}
$hashedIP = $_SERVER['REMOTE_ADDR'];
$kitty = $_POST['custom']; // Not sure this is working yet, but this should give me $mrUser;
$sql = "INSERT INTO `Insert_upon_Paypal_success` (`date`,`hashedIP`,`username`,`webdollarAMT`) VALUES (now(),:hashedIP,:kitty,'100')";
$statement = $pdo->prepare($sql);
$statement->bindValue(':hashedIP', $hashedIP);
$statement->bindValue(':kitty', $kitty);
$inserted = $statement->execute();
I'm popping this into the PaypalIPN.php file upon validation, but, it doesn't work. Here's how I have it in there:
// Check if PayPal verifies the IPN data, and if so, return true.
if ($res == self::VALID) {
return true;
// i.e. putting all of the SQL above right here.
} else {
return false;
}
I'm guessing I need to put the SQL in a specific place that I'm missing, as per the layout of the PaypalIPN.php file... Please help!!
There is no reason to use IPN with current PayPal Checkout integrations. It is very old technology (20+ years) and should be deprecated soon.
Webhooks are a successor to IPN. However, even they are unnecessary for normal payment processing -- better used only if you need automated notifications of post-checkout exceptions such as refunds or disputes.
For normal PayPal payments, do not use either.
Instead, use the v2/checkout/orders API and make two routes (url paths) on your server, one for 'Create Order' and one for 'Capture Order'. You could use the (recently deprecated) Checkout-PHP-SDK for the routes' API calls to PayPal, or your own HTTPS implementation of first getting an access token and then doing the call with PHP's curl or similar. Both of these routes should return/output only JSON data (no HTML or text). Inside the 2nd route, when the capture API is successful you should verify the amount was correct and store its resulting payment details in your database (particularly purchase_units[0].payments.captures[0].id, which is the PayPal transaction ID) and perform any necessary business logic (such as reserving product or sending an email) immediately before forwarding return JSON to the frontend caller. In the event of an error forward the JSON details of it as well, since the frontend must handle such cases.
Pair those 2 routes with this frontend approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server . (If you need to send any additional data from the client to the server, such as an items array or selected options, add a body parameter to the fetch with a value that is a JSON string or object)

Paypal smart buttons credit card error management

I'm using paypal smart buttons sdk to fullfill paypal payment by credit card or paypal balance. the problem is for sandbox negative testing i was unable to test bad credit card cases because of lack of documentation (or may be it's me who didn't search enough).
so i decided to test in a production like environment with my production paypal business account and intentionally put a bad credit card secret .
The problem is when calling order.capture() function paypal raises a lot of errors but i'm unable to catch them and manage them correctly
here is my calling code :
onApprove: async (data: any, actions: any) => {
const order = await actions.order;
this.logger.log(order);
try {
order.capture().then((details: any) => {
this.logger.log('[PAYPAL onApprove : ]' + details);
const payPalCreateOrderResponse: PayPalCreateOrderResponse = new PayPalCreateOrderResponse();
payPalCreateOrderResponse.details = details;
this.checkOutEventsStore.paymentDetails = payPalCreateOrderResponse;
});
} catch (e) {
this.managePaypalError(params, e);
this.logger.error('====> Paypal Order Capture ERROR ' + e);
}
}
the errors i see in console :
POST https://www.paypal.com/smart/api/order/order_id_replaced/capture 500
(anonyme) # buttons?
(anonyme) # js?client-id=xxxxxxxxxxxxxxxxxxx&currency=EUR&locale=fr_FR&debug=true:4841
ZalgoPromise.try # js?client-id=xxxxxxxxxxxxxx&currency=EUR&locale=fr_FR&debug=true:770
(anonyme) # js?client-id=xxxxxxxxxxxxxx&currency=EUR&locale=fr_FR&debug=true:4834
(anonyme) # js?client-id=xxxxxxxxxxxxx&currency=EUR&locale=fr_FR&debug=true:4851
######## a lot of stack ommited
Error: Api: /smart/api/order/order_id_replaced/capture returned status code: 500 (Corr ID: f2967533987cc)
{"ack":"error","message":"Unhandled api error","meta":{"calc":"f2967533987cc","rlog":"rZJvnqaaQhLn%2FnmWT8cSUueWscmrtUHe5Y1Bd%2FeqyvyOTq66rSXAcgw%2BjwUfLWoirTjSF3Dcz2NbXl4NQOgVH84XX3DSygFN_17c9d7d88e6"},"server":"HR8xYFSZUP13jAt-X87VBJ7lq_LqwktwVsmzzP_zQqInVub3-ylXm8UuExvdz-SWJ0NH49XoaSL2hE_9LzQo_5B-X0COwFFVi2Z4c-cTCQBGoBSZtkefMbHWojX3rQ4-qLZIYQefq6OE7funNI8ZnGZUi9YpufYlG9X1qx89zj0l4LERQ9wesnqMpT59y3GbjqsfOGbGf7uasTCGOz6f58ZNMbdNVYrz1h5gc3sZbk-LhH5ks1k1DqJV7UPsxus1QBII26hjpRQbnFr6VLiyCW"}
and the most important , in the network api calls i can see clearly for :
https://www.paypal.com/smart/api/order/order_id_replaced/capture api call
a good json result for the error :
{"name":"UNPROCESSABLE_ENTITY","details":[{"issue":"INSTRUMENT_DECLINED","description":"The instrument presented was either declined by the processor or bank, or it can't be used for this payment."}],"message":"The requested action could not be performed, semantically incorrect, or failed business validation.","debug_id":"f569254a2e9c8","links":[{"href":"https://developer.paypal.com/docs/api/orders/v2/#error-INSTRUMENT_DECLINED","rel":"information_link","method":"GET"},{"href":"https://www.paypal.com/checkoutnow?token=token_replaced","rel":"redirect","method":"GET"}]}
So the question is how to manage this error correctly by using the js api.
Thank you for your Help .
Ryan
when calling order.capture() function paypal raises a lot of errors but I'm unable to catch them and manage them correctly
When capturing on the client side with .capture() , if the capture is declined PayPal will automatically offer the payer the ability to try again, likely falling back to a modal window. No error handling by you is necessary, required, nor possible.
If you were capturing on the server side (which you are not doing), there is sample error handling code in this demo.

Where to enter paypal IPN url and how to pass custom data?

This seems a very naive set of questions, but I really couldn't find it.
Question 1) I am trying to implement IPN for PayPal. I want to enter The listener URL, but I couldn't find where. I've searched the internet and all the responses are old. Where could I put it?
Question 2) I am using the following code and I want to pass custom data so that when IPN sends me back the transaction message, It will have that custom data in it. I need to pass 2 custom data. How is this possible for the following code? I really couldn't find this one in the docs too.
<template>
<div id="paypal-button-container"></div>
</template>
<script>
export default {
props: ["amount"],
mounted() {
let self = this;
paypal
.Buttons({
createOrder: function(data, actions) {
// Set up the transaction
return actions.order.create({
purchase_units: [
{
amount: {
value: self.amount,
}
}
],
});
},
onApprove: function(data, actions) {
// Capture the funds from the transaction
return actions.order.capture().then(function(details) {
// Show a success message to your buyer
console.log(details);
self.$emit("paypalPaySuccess", {
order_id: details.id,
})
alert("Transaction completed by " + details.payer.name.given_name);
}).catch((err)=>{
})
}
})
.render("#paypal-button-container");
}
};
</script>
Addition of some questions
I am using all the events in dashboard.
I need to add some more questions, because I can't find the information I need.
Question 3) In client-side, I make action.orders.capture().then . which means that I capture funds immediatelly. When webhooks come to my back-end, there're 2 webhooks that come for single payment . (PAYMENT.CAPTURE.PENDING and CHECKOUT.ORDER.APPROVED) . a) why doesn't PAYMENT.CAPTURE.COMPLETED arrive as webhook event? b) what if I want to get DENIED events? If PAYMENT.CAPTURE.COMPLETED doesn't arrive, PAYMENT.CAPTURE.DENIED won't arrive too.
Question 4) As I said, there're 2 webhook events appearing for single payment on my back-end. The first one has id in it. Let's say I store this in my database as id and status(PENDING). Then another event came which is CHECKOUT.ORDER.APPROVED. Now, this one has different id then the previous one. So I can't really go back to database and update the status as this has different id. What should I do? One thing I noted is that the second came event has also another id (the previous came event) somewhere in captured object. is this what i should use it? which one is the final transaction_id?
Question 5) Turns out invoice_id should be unique each time. So I wanted to pass user_id but i guess i also should generate random string and append it to user_id something like this: invoice_id: '7,randomstring'. right?
Question 6) Same event sometimes comes twice. Why is that? Should I return status 200 in order for that same event not to come again? When will it be good to return that status? I guess after I update the database, right?
Q1: IPN is deprecated. You should be using webhooks, such as CHECKOUT.ORDER.APPROVED
Q2: There does not appear to be any specific way to pass custom data.
Option 1:
What I ended up doing was using the invoice_id field in purchase_units. I tested and found that I could pass a reasonably long string of characters and they did not have to be unique. It seems a bit of a hack but it works.
When you implement the CHECKOUT.ORDER.APPROVED webhook, you can then parse the data in purchase_unit invoice_id.
Option 2:
Another idea I considered which doesn't need to hack the invoice_id, is to send data to the server using the details from the capture event in the javascript, store that custom information, along with the order id in the database, and then when the webhook occurs, cross-match the order id to the saved one to retrieve the information.
I prefer option 1, but your needs may vary.
It's pretty indicative though of how poorly designed the whole PayPal API is. There are huge gaps in the implementation, very little documentation, and zero support.
I had to implement Stripe as well. The entire implementation took me 3 days. The same PayPal implementation took me over 3 weeks! The lack of documentation, having to constantly just try things out, and the inconsistency of the sandbox implementation were real time killers.

GetPrePaymentDisclosure returning Internal Error

I'd like to use this endpoint to display the calculated fees on my site before taking the user to the paypal site; however, when I query the endpoint with a valid payKey, I receive an internal error.
Endpoint: https://developer.paypal.com/docs/classic/api/adaptive-payments/GetPrePaymentDisclosure_API_Operation/
To generate the payKey, I'm POSTing to svcs.sandbox.paypal.com/AdapativePayments/Pay with the following request body:
{
"actionType":"PAY",
"currencyCode":"USD",
"feesPayer":"SENDER",
"requestEnvelope": { "errorLanguage": "en_US" },
"cancelUrl":"test.com/cancel",
"returnUrl":"test.com/return",
"receiverList": {
"receiver": [
{ "email": "someguy#email.com", "amount": "80.00" }
]
}
}
Any ideas as to what's going on?
Note: I had to edit out the valid URLs because I don't have enough reputation.
For GetPrePaymentDisclosure you must supply senderEmail in the PAY request for it to function.
This is because GetPrePaymentDisclosure is supposed to be used to determine the status of the transaction and see if it is covered by the Remittance Transfer Rule.
From the Documentation: This API is specific for merchants who must support the Consumer Financial Protection Bureau's Remittance Transfer Rule.
It does not work to determine the fees beforehand if it does not hit these rules. As all you will get back is [status] => NON_RTR with no information.
If you would like this functionality outside of RTR transactions you should submit a feature request on PayPal's Technical Support Site

How do I resolve a 400 bad request error using the paypal rest api?

I am currently testing credit card payments using the PayPal payments pro Rest API.
I keep getting the following error:
Exception in HttpConnection Execute: Invalid HTTP response The remote server returned an error: (400) Bad Request. --->
I created and activated my PayPal test account and as following:
Credit card
Credit card number:
ccnumber here
Credit card type:
Visa
Expiration date:
3/2019
When I enter the above information in my payment form and submit I keep getting the 400 bad request error.
I have gotton this to work before with the PayPAl joe shopper account but lost the test cc number.
I am 100% sure I have authenticated with the oauth2 credentials
What is the likely cause of this error?:
I am entering a random payer first name and last name .
Does the name have to exsist?
What could be causing the error
If anything goes wrong in the data given, paypal api will return a 400 - Bad request error.
When I got the error(PHP SDK), I caught the exception and $ex->getData() returned a json which contains the details about the bad request, in my case the currency code I given was wrong.
try {
$payment->create($apiContext);
} catch (PayPal\Exception\PPConnectionException $ex) {
var_dump(json_decode($ex->getData()));
exit(1);
}
Like others here have said, there are several things that can cause the (HTTP 400 - Bad Request).
In my case it was due to using the same invoice number. For temporary testing I just used the ShoppingCartID that I generated with a guid. You could also use a Random number generator as well. Ultimately you will need to generate an invoice number to the clients specifications.
-Good luck
I just had the same problem (HTTP 400 - Bad Request). The cause is a REST API request with "errors". In my case I had two causes:
I passed amount details to an Amount object but the sum of subtotal, shipping an tax was not equal to the amount total.
I passed amount currency values including a fracture, but passed not exactly two decimals but only one. Obviously the REST API is a bit touchy and accepts only either no or two decimals.
I passed an item_list in the Transaction object but the sum of the prices in the Item object was not equal to the subtotal given in the amount details.
I don't know a way of finding the actual cause of the error (if there is one).
In your case it could be the name of the payer that you add. Try without.
Edit:
I just tried out: It is indeed the payer info. I passed a payer_info with first and lastname and got HTTP 400 (without everything worked). I assume that payer_info must not be set by us but is set by PayPal when returning a Payment object.
Payer payer = new Payer { payment_method = "paypal", payer_info = new PayerInfo {
first_name = "Zaphod", last_name = "Beeblebrox"} }; // => HTTP 400 - Bad Request
firstly find out what exactly what Your error is
payment = PayPal::SDK::REST::DataTypes::Payment.new(...)
payment.create
# got Response[400]: Bad Request here
payment.error
# got {"name"=>"VALIDATION_ERROR", "details"=>[{"field"=>"transactions", "issue"=>"
then find out what Your issue here to get more info
https://developer.paypal.com/webapps/developer/docs/api/#validation-issues
I had the same issue. In my case it was Credit Card issue which might be over used. So I have taken new Credit card Number from this site Testing Credit card and replaced with the old one.
Here is Credit Card Information that I used
credit_card = new CreditCard()
{
billing_address = new Address()
{
city = "Johnstown",
country_code = "US",
line1 = "52 N Main ST",
postal_code = "43210",
state = "OH"
},
cvv2 = "874",
expire_month = 11,
expire_year = 2018,
first_name = "Joe",
last_name = "Shopper",
number = "4024007185826731", //New Credit card Number, Only Card type should match other details does not matter
type = "visa"
}
Note: All the Credit Card mentioned in PayPal site are not working, giving same issue for me. If it working for you then it's good otherwise use new testing Credit Card Number. Hope this will help someone.
Thank you! Enjoy Coding!
I too got struck with this issue, there might be many other possible root cause for this failure but then in my case all my passing argument is correct, but then it failed in sandbox environment, I just changed Payment option from PayPal Balance to Visa then it started working.
I recently had a 400 bad request error as well. In my case, I was processing a refund using RefundRequest, and it appears that PayPal recently changed the constraints on the "reason" field (although it isn't documented that I can find) so my lengthy descriptions of what was being returned were no longer acceptable. I found the error only because I saw another field where PayPal was now requiring that it be only single-byte alphanumeric characters. What actually helped though, was shortening the data I was putting through as a reason to simply "Returned items".
Obviously this is an old thread, and I'm sure the original problem was solved long ago, but if using some of the above methods to determine more information about an error don't yield results I would suggest submitting a request with the bare minimum of information until you can get it to go through. Then, start adding additional fields/information back to see if you can identify which field contains the information causing the bad request. That would have worked for my issue, at least.
Understand your errors
PayPal returns detailed data on every exception (except 500 INTERNAL_SERVER_ERROR), explaining what was the exact error you are seeing. You can retrieve that as follows:
Update your exception handling to catch PayPalConnectionException and print $ex->getData() as shown below:
try {
$payment->create($apiContext);
} catch (PayPal\Exception\PayPalConnectionException $ex) {
echo $ex->getCode(); // Prints the Error Code
echo $ex->getData(); // Prints the detailed error message
die($ex);
} catch (Exception $ex) {
die($ex);
}
It is due to the wrong currency or amount you have given.
I faced the issue in React JS
I was doing this. in the createOrder method.
purchase_units: [{
amount: {
currency_code: 'PHP',
value: totalAmount
}
}],
that was the issue. If i gave any currency other than USD i will get the 400 Error. So i removed the currency_code from here
And I moved the currency declaration into my component declaration.
like this.
options={{
clientId: " Your_ID " ,
currency: "PHP"
}}
Here you can give any currency you want. This solves my 400 Error
If you use Paypal Sandbox for testing Driver and on first try with buggy code it shows the client a 400 Bad request error.
Reason:- Paypal sent JWT auth and it was stored in the client's local storage.
Suggestion:- Try to clear the client's local storage cache (You only need to clear the Paypal Token).
This works for me, I hope it works for you :)