Paypal Webhook Response - php

I have been trying to get Paypal's Webhooks to work but all ive been running into is problems. I am trying to make a simple payment process where the user just pays for a product and then I want to setup webhooks to alert me when the payment is made, refunded, or reversed. I have the payment process setup and that is not a problem. I am also hosting the webhook url at a SSL enabled URL.
When I have a purchase go through it sends the webhook response to the link and I have it upload a basic "test" variable to the database just to show that it is indeed being contacted. I also try to insert all of the post data into the database but that doesn't seem to work. Here is the code that the webhook contacts:
<?php
header('HTTP/1.1 200 OK');
$servername = "xxxx";
$username = "xxxx";
$password = "xxxx";
$db = "xxxx";
// Create connection
$conn = mysqli_connect($servername, $username, $password, $db);
if (!$conn) {
die("Connection failed: " . mysqli_connect_error());
}
$sql = "INSERT INTO vars (var) VALUES ('test')";
$conn->query($sql);
foreach ($_POST as $param_name => $param_val) {
echo "Param: $param_name; Value: $param_val<br />\n";
$sql = "INSERT INTO vars (var) VALUES ('" . $param_name . "')";
$conn->query($sql);
}
?>
So like i said I see it insert the "test" into the database but when I try to go through the post variables (which from what i understand there should be some) it doesnt upload any variables.
But when I use the IPN simulator on the paypal developer sandbox and I send it to the link it inserts all of the post variables.
So why is it contacting the URL but not including any of the information? Am I receiving it incorrectly and not processing it right?
Thank you.

First of all You should verify that this request really came from PayPal. For that you should send a little bit modified POST request back to PayPal.
$paypalurl = 'https://www.paypal.com/cgi-bin/webscr';
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
$ch = curl_init($paypalurl);
if ($ch == false) {
return false;
}
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
$res = curl_exec($ch);
In the response you should check if it's verified or not:
if ($res) && strcmp($res, "VERIFIED") == {
// your code here
}
So it's better to always use IPN simulator to test this thing, otherwise (by sending plain POST request to your script) it wouldn't be validated normally.
Besides, I would advise you to take a closer look to $_POST['payment_status'] variable. It could be "Completed" (which is ok when payment is processed normally), or "Refund", "Reversed". And process payment according to that. And of course I strongly recommend to validate input — check _POST request (for availability, possible SQL injections, etc) and then proceed to the rest.

$bodyReceived = file_get_contents('php://input');
This is the information you'll receive from paypal as a json object

Related

How can I prevent paypal duplicate payment using txn_id?

So I am using PHP cURL and PayPal IPN.
I don't want my customer to pay twice by mistake. I have heard that I can do that using txn_id but I don't know how.
Here is my code ...
<?php
// connecting to database
// Prepare the URL to send via cURL
$req = 'cmd=_notify-validate';
if(function_exists('get_magic_quotes_gpc')) {
$get_magic_quotes_exists = true;
}
foreach ($_POST as $key => $value) {
if($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1) {
$value = urlencode(stripslashes($value));
} else {
$value = urlencode($value);
}
$req .= "&$key=$value";
}
// Initial cURL
$ch = curl_init();
// Set opt
curl_setopt($ch, CURLOPT_URL,"https://www.paypal.com/cgi-bin/webscr");
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
// Return result
$result = curl_exec($ch);
// Close cURL connection
curl_close($ch);
// If condition
if($result == "VERIFIED"){
// perform database update
}
?>
How can I prevent the buyer from paying more than once?
The code you provided here is for PayPal IPN, which I think could help little to avoid duplicated payment. Instead, I recommend you adding invoice into your payment data. As PayPal system by default blocks payments with same Invoice ID, I hope this could help you avoid your customers from paying twice by mistake.
If you are integrating PayPal Payment Standard, please check below page for variable list.
https://developer.paypal.com/webapps/developer/docs/classic/paypal-payments-standard/integration-guide/Appx_websitestandard_htmlvariables/
If you are integrating PayPal Express Checkout, please check below pages.
https://developer.paypal.com/docs/classic/api/merchant/SetExpressCheckout_API_Operation_NVP/
https://developer.paypal.com/docs/classic/api/merchant/DoExpressCheckoutPayment_API_Operation_NVP/

PayPal Integration Not Working

I have PayPal integrated on my website. There's a form on my site which takes the customer to PayPal to finalise the payment then it returns them to my site. My site then sends a request back to PayPal with the transaction token for PDT so that I can run a few checks then auto-credit my customer's with the product they bought.
The system uses PHP and cURL to send process all this.
When I use: "www.sandbox.paypal.com/cgi-bin/webscr"
Along with the sandbox credentials everything works fine, everything.
Once I change it all to "www.paypal.com/cgi-bin/webscr"
It does not work, My code tells me that the request fails.
My Code:
$pp_hostname = "www.paypal.com";
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-synch';
$tx_token = $_GET['tx'];
$auth_token = "#######-hashed_out_here-########";
$req .= "&tx=$tx_token&at=$auth_token";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://$pp_hostname/cgi-bin/webscr");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
//set cacert.pem verisign certificate path in curl using 'CURLOPT_CAINFO' field here,
//if your server does not bundled with default verisign certificates.
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Host: $pp_hostname"));
$res = curl_exec($ch);
curl_close($ch);
if(!$res){
$this->twig->error("Request failed.");
}else{
$lines = explode("\n", $res);
$keyarray = array();
if (strcmp ($lines[0], "SUCCESS") == 0) {
for ($i='1'; $i<count($lines);$i++){
$test = explode("=", $lines[$i]);
if(empty($test[1])){
$keyarray[urldecode($test[0])] = '';
}else{
$keyarray[urldecode($test[0])] = urldecode($test[1]);
}
}
//give product
} else if (strcmp ($lines[0], "FAIL") == 0) {
$this->twig->error('Transaction failed!');
}
}
Not sure why I'm having a problem so I don't know how to go about fixing.
My PayPal Account is set up as Business, has PDT and IPN turned on as well as Auto-Return
EDIT: Also there isn't an error occurring with the cURL.
OK so if(!($res = curl_exec($ch))) is returning true, i.e its not working

PayPal Integration HTTP Error

I have PayPal integrated on my website. There's a form on my site which takes the customer to PayPal to finalise the payment then it returns them to my site. My site then sends a request back to PayPal with the transaction token for PDT so that I can run a few checks then auto-credit my customer's with the product they bought.
The system uses PHP and cURL to send process all this.
When I use: "www.sandbox.paypal.com/cgi-bin/webscr"
Along with the sandbox credentials everything works fine, everything.
Once I change it all to "www.paypal.com/cgi-bin/webscr"
It does not work, My code tells me that the request fails.
My Code:
$pp_hostname = "www.paypal.com";
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-synch';
$tx_token = $_GET['tx'];
$auth_token = "#######-hashed_out_here-########";
$req .= "&tx=$tx_token&at=$auth_token";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://$pp_hostname/cgi-bin/webscr");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
//set cacert.pem verisign certificate path in curl using 'CURLOPT_CAINFO' field here,
//if your server does not bundled with default verisign certificates.
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Host: $pp_hostname"));
$res = curl_exec($ch);
curl_close($ch);
if(!$res){
$this->twig->error("Request failed.");
}else{
$lines = explode("\n", $res);
$keyarray = array();
if (strcmp ($lines[0], "SUCCESS") == 0) {
for ($i='1'; $i<count($lines);$i++){
$test = explode("=", $lines[$i]);
if(empty($test[1])){
$keyarray[urldecode($test[0])] = '';
}else{
$keyarray[urldecode($test[0])] = urldecode($test[1]);
}
}
//give product
} else if (strcmp ($lines[0], "FAIL") == 0) {
$this->twig->error('Transaction failed!');
}
}
Not sure why I'm having a problem so I don't know how to go about fixing.
My PayPal Account is set up as Business, has PDT and IPN turned on as well as Auto-Return
It appears that I'm receiving HTTP Error, but which I cannot say, if I log the curl_error it simply says " " and if I log the curl_errno I get 0.
I managed to get this fixed. It was due to my server not having the ssl certificates installed, once I did so it worked fine.

Problems with PayPal IPN variables

Im using PayPal as main payment system, so I decided to add it inside my custom built script, however I have problems with IPN variables because when I try to echo any of those variables for example
$item_number
$txn_id
I get nothing from above variables. Everything is passed correctly to PayPal and the payment is received, but when user is redirected to my "Thank you" page where I need to grab that $item_number item number in order to update the database nothing is passed. Im doing testings using Sandbox for now.
This is the url values when redirected to my website:
http://example.com/thankyou.php?tx=8LT93279034418017&st=Completed&amt=20%2e00&cc=USD&cm=&item_number=29
this is my IPN code:
<?php
// STEP 1: Read POST data
// reading posted data from directly from $_POST causes serialization
// issues with array data in POST
// reading raw POST data from input stream instead.
$raw_post_data = file_get_contents('php://input');
$raw_post_array = explode('&', $raw_post_data);
$myPost = array();
foreach ($raw_post_array as $keyval) {
$keyval = explode ('=', $keyval);
if (count($keyval) == 2)
$myPost[$keyval[0]] = urldecode($keyval[1]);
}
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
if(function_exists('get_magic_quotes_gpc')) {
$get_magic_quotes_exists = true;
}
foreach ($myPost as $key => $value) {
if($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1) {
$value = urlencode(stripslashes($value));
} else {
$value = urlencode($value);
}
$req .= "&$key=$value";
}
// STEP 2: Post IPN data back to paypal to validate
$ch = curl_init('https://www.sandbox.paypal.com/cgi-bin/webscr');
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
// In wamp like environments that do not come bundled with root authority certificates,
// please download 'cacert.pem' from "http://curl.haxx.se/docs/caextract.html" and set the directory path
// of the certificate as shown below.
// curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__) . '/cacert.pem');
if( !($res = curl_exec($ch)) ) {
// error_log("Got " . curl_error($ch) . " when processing IPN data");
curl_close($ch);
exit;
}
curl_close($ch);
// STEP 3: Inspect IPN validation result and act accordingly
if (strcmp ($res, "VERIFIED") == 0) {
// check whether the payment_status is Completed
// check that txn_id has not been previously processed
// check that receiver_email is your Primary PayPal email
// check that payment_amount/payment_currency are correct
// process payment
// assign posted variables to local variables
$custom_value = $_POST['custom'];
$item_name = $_POST['item_name'];
$item_number = $_POST['item_number'];
$payment_status = $_POST['payment_status'];
$payment_amount = $_POST['mc_gross'];
$payment_currency = $_POST['mc_currency'];
$txn_id = $_POST['txn_id'];
$receiver_email = $_POST['receiver_email'];
$payer_email = $_POST['payer_email'];
} else if (strcmp ($res, "INVALID") == 0) {
// log for manual investigation
}
?>
Thanks a lot for help!
You are confusing PDT and IPN. PDT is what would send data back to your return URL. IPN happens completely separate from your checkout flow and isn't something anybody sees on screen, so echoing data wouldn't be seen by anybody.
IPN is indeed the recommended way to handle database updates, email notifications, etc. because even with Auto-Return enabled there is no guarantee users would make it back to your ReturnURL (when using Payments Standard), in which case the code would never run for those orders.
It looks like you just pulled the IPN sample from PayPal, so you need to customize it to suit your needs, and again, keep in mind it's not anything you'll see on screen. You'll need to check PayPal's IPN History and your web server logs to see if it's getting hit and if any errors may be happening.
Of course, you'll need to make sure you have IPN configured in your PayPal profile, too, or that you've set notify_url in your payment code.
So your thank you page would really be nothing more than that, and then IPN would handle updating the db and notifying the user about anything they need to know.

Paypal PDT error 4003

After many hours of messing about and trying to set up what should be the relatively simple process of sending a payment to www.sandbox.paypal.com and being redirected back to a page on my site with a transaction id in the querystring, I have finally achieved it.
I am now receiving an error message 'FAIL Error 4003'.
Here is the code I am using. It is pretty much the same as the paypal example (all I have done is echo out the responses):
<?php
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-synch';
$tx_token = $_GET['tx'];
$auth_token = "ZdoN6q4GLiRniR2BbOzEEF22GJOWHpVOXRtP7fAhBpvwwm5GyWcTzO_sSSO";
$req .= "&tx=$tx_token&at=$auth_token";
// post back to PayPal system to validate
$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen ('www.paypal.com', 80, $errno, $errstr, 30);
// If possible, securely post back to paypal using HTTPS
// Your PHP server will need to be SSL enabled
// $fp = fsockopen ('ssl://www.paypal.com', 443, $errno, $errstr, 30);
if (!$fp)
{
// HTTP ERROR
echo "HTTP Error";
}
else
{
fputs ($fp, $header . $req);
// read the body data
$res = '';
$headerdone = false;
while (!feof($fp))
{
$line = fgets ($fp, 1024);
if (strcmp($line, "\r\n") == 0) {
// read the header
$headerdone = true;
}
else if ($headerdone)
{
// header has been read. now read the contents
$res .= $line;
echo $line;
}
}
// parse the data
$lines = explode("\n", $res);
$keyarray = array();
if (strcmp ($lines[0], "SUCCESS") == 0)
{
for ($i=1; $i<count($lines);$i++)
{
list($key,$val) = explode("=", $lines[$i]);
$keyarray[urldecode($key)] = urldecode($val);
}
// check the payment_status is Completed
// check that txn_id has not been previously processed
// check that receiver_email is your Primary PayPal email
// check that payment_amount/payment_currency are correct
// process payment
$firstname = $keyarray['first_name'];
$lastname = $keyarray['last_name'];
$itemname = $keyarray['item_name'];
$amount = $keyarray['payment_gross'];
echo ("<p><h3>Thank you for your purchase!</h3></p>");
echo ("<b>Payment Details</b><br>\n");
echo ("<li>Name: $firstname $lastname</li>\n");
echo ("<li>Item: $itemname</li>\n");
echo ("<li>Amount: $amount</li>\n");
echo ("");
}
else if (strcmp ($lines[0], "FAIL") == 0) {
echo "Failure: " . $lines[0];
// log for manual investigation
}
}
fclose ($fp);
?>
<br />
Thank you for your payment. Your transaction has been completed, and a receipt for your purchase has been emailed to you. You may log into your account at www.sandbox.paypal.com/ie to view details of this transaction.
I have made sure to confirm the email addresses for both my sandbox merchant and buyer accounts and enabled PDT.
The client is redirected correctly back to my 'thank you' page with the following querystring paramaters - ?tx=4FU63684496248523&st=Pending&amt=29.90&cc=EUR&cm=&item_number=
Has anyone else encountered this error message? If so, what are the usual causes?
The problem was that I was sending my test http request to paypal.com rather than sandbox.paypal.com. The answer was in the FAQ left by Jukebox.
Check the script.When testing Payment Data Transfer (PDT) in the
Sandbox, make sure your PDT script POSTs back information to
www.sandbox.paypal.com. If testing on the Live PayPal site, make sure
the script POSTs data back to www.paypal.com. Currently, all sample
code on the Live and Sandbox sites "point" back to the live PayPal
site.
Hope it helps someone else get up and running quicker than I did. I am now onto my next issue which is the token being returned as empty rather than an error.. sigh..
Make sure your transaction ID is not expired. 4003 PDT error code also comes when your transaction ID is expired.
Here's what solved it for me...
In the Buy Now button, I had a business email address associated with my LIVE PayPal account whereas I need to use the business email address associated with my SANDBOX PayPal account as in:
<input type="hidden" name="business" value="user#host.com">
The value attribute needed to reflect the business email address associated with my sandbox account.
By the way, here's an alternate PHP cURL version of the above script that also does the trick of eliciting a response from PayPal's sandbox:
if (isset($_GET['tx'])) {
$tx = $_GET['tx'];
$identity_token = "INSERT_YOUR_IDENTITY_TOKEN_HERE";
//echo $tx;
$url = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
$nvpString="cmd=_notify-synch".
"&tx=$tx".
"&at=$identity_token";
//echo $nvpString;
//define where the data is going to
$curl = curl_init($url);
//tell cURL to fail if an error occurs
curl_setopt($curl, CURLOPT_FAILONERROR, 1);
//allow for redirects
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
//assign the returned data to a variable
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
//set the timeout
curl_setopt($curl, CURLOPT_TIMEOUT, 60);
//use POST
curl_setopt($curl, CURLOPT_POST, 1);
//set the POST data
curl_setopt($curl, CURLOPT_POSTFIELDS, $nvpString);
//execute the transaction
$response = curl_exec($curl);
//show errors
curl_error($curl);
//close the connection
curl_close($curl);
echo '<pre>';
print_r($response);
echo '</pre>';
}//end if (isset($_GET['tx']))
The Identity Token of my test seller account changed without notification. Using the new (correct) Identity Token fixed the problem.
Some more information on Paypal’s PTD can be found here:
http://www.secure-ebook.com/help/payment:paypal:fail_4003
$tx=$_REQUEST['tx'];
$paypal_url='https://www.paypal.com/cgi-bin/webscr?cmd=_notify-synch&tx='.$tx.'&at=token here';
$curl = curl_init($paypal_url);
$data = array(
"cmd" => "_notify-synch",
"tx" => $tx,
"at" => "token here"
);
$data_string = json_encode($data);
curl_setopt ($curl, CURLOPT_HEADER, 0);
curl_setopt ($curl, CURLOPT_POST, 1);
curl_setopt ($curl, CURLOPT_POSTFIELDS, $data_string);
curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt ($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt ($curl, CURLOPT_SSL_VERIFYHOST, 1);
$headers = array (
'Content-Type: application/x-www-form-urlencoded',
'Host: www.paypal.com',
'Connection: close'
);
curl_setopt ($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt ($curl, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($curl);
$lines = explode("\n", $response);
$keyarray = array();
if (strcmp ($lines[0], "SUCCESS") == 0) {
for ($i=1; $i<count($lines);$i++){
list($key,$val) = explode("=", $lines[$i]);
$keyarray[urldecode($key)] = urldecode($val);
}
$first_name=$keyarray['first_name'];
$last_name=$keyarray['last_name'];
$payment_status=$keyarray['payment_status'];
$business=$keyarray['business'];
$payer_email=$keyarray['payer_email'];
$payment_gross=$keyarray['payment_gross'];
$mc_currency=$keyarray['mc_currency'];
}

Categories