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

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)

Related

PayPal return URL with transaction or payment ID?

I'm using the following script to display a PP button on my website which works fine. You'll see that I have a return URL which also pulls in data (eg orderid) from my page:
<script>
paypal.Buttons({
// Sets up the transaction when a payment button is clicked
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: '2.00' // Can reference variables or functions. Example: `value: document.getElementById('...').value`
}
}]
});
},
// 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];
window.location.href = 'https://mywebsite.co.uk/signup-payment-confirmation.asp?fee=<%=fee%>&ct=<%=orderid%>';;
});
}
}).render('#paypal-button-container');
</script>
This works fine as when a transaction is made the user is simply redirected to the return URL and I record the data in my database. However, what I really need is for my return URL to also show the transaction ID from the PayPal transaction. I can then tally records in my database to those in the PayPal admin area.
actions.order.create / .capture are for simple use cases. If you intend to do anything with a database, do not use these client side functions. Any number of problems could prevent your system from recording a transaction after the fact.
Instead, use the actual 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 one of the (recently deprecated) Checkout-*-SDKs for the routes' API calls to PayPal, or your own HTTPS implementation of first getting an access token and then doing the call. 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.

Paypal shop link sequrity

For receiving money from clients here
https://www.paypal.com/buttons/smart
can be copy a code where is an onApprove function
onApprove: function(data, actions) {
return actions.order.capture().then(function(orderData) {
// Full available details
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
// 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('http://www.myservices.com/oneserviceBought?id=2323');//for example
});
How to rise security, that somebody (cyber attacker) cannot steal the service - instead of paying throw PP, copy the url http://www.myservices.com/oneserviceBought?id=2323 on his/her browser?
The only way to secure such information is for the transaction to be created and captured from the server, and for only the server to eventually return or provide the redirect (or other credentials) once a capture is verified as successful.
There are details within Set up standard payments that explain how to program a server integration. Read that information, including the code demo pattern it links to.

PayPal Smart Button transaction not charging after 'approval'

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

PayPal Checkout Buttons - client/server communication

I am integrating the smart checkout of Paypal. I do not understand what the javascript portion expects back from the server. This is what I have got in JS
...
...
createOrder: function(data, actions) {
return fetch('/paypal/sandbox/createOrder', {
method: 'post',
headers: {
'content-type': 'application/json'
}
}).then(function(res) {
return res.json();
}).then(function(data) {
return data.orderID;
});
},
...
...
And this is what the server side does when /paypal/sandbox/createOrder' is called …
$request = new OrdersCreateRequest();
$request->prefer('return=representation');
$request->body = $this->buildRequestBody();
$response = $this->PayPalClient->execute($request);
What I can not find anywhere: What am I supposed to do with the response? I tried to echo it plain, echo it as JSON, but I always get errors like "Expected order id"
What do I need to respond to the client request?
The structure of the communication between your createOrder frontend JS and your backend server route (which in turn calls the PayPal API) is something you define. It can be is simple or as complex as you need it to be. You can send as many parameters back and forth as you want. You can use JSON, XML, Morse code, RFC 1149, or whatever you want for this transmission between your client and server.
The only required bit of information is the Order ID, which your server-side code in turn gets from the PayPal API v2/checkout/orders call (as id in the response), and which your createOrder function must then propagate back to its caller once obtained, which in the sample code happens here:
...
return data.orderID;
One very simple implementation would be to echo the whole v2/checkout/orders API response , not just id, and change the above client-side code to be return data.id so it reads that key name