I've tried multiple different examples of IPN classes and every time paypal returns with INVALID in the IPN simulator.
End point: https://ipnpb.sandbox.paypal.com/cgi-bin/webscr
I've done some testing, and the data I'm returning to paypal looks the same. e.g.
What paypal is sending:
payment_type=echeck&payment_date=Sun%20Jun%2010%202018%2010%3A17%3A15%20GMT%2B0100%20%28GMT%20Summer%20Time%29&payment_status=Completed&address_status=confirmed&payer_status=verified&first_name=John&last_name=Smith&payer_email=buyer#paypalsandbox.com&payer_id=TESTBUYERID01&address_name=John%20Smith&address_country=United%20States&address_country_code=US&address_zip=95131&address_state=CA&address_city=San%20Jose&address_street=123%20any%20street&business=seller#paypalsandbox.com&receiver_email=seller#paypalsandbox.com&receiver_id=seller#paypalsandbox.com&residence_country=US&item_name=something&item_number=AK-1234&quantity=1&shipping=3.04&tax=2.02&mc_currency=USD&mc_fee=0.44&mc_gross=12.34&mc_gross_1=12.34&txn_type=web_accept&txn_id=300182286¬ify_version=2.1&custom=xyz123&invoice=abc1234&test_ipn=1&verify_sign=undefined
What I'm sending Paypal
cmd=_notify-validate&payment_type=echeck&payment_date=Sun%20Jun%2010%202018%2010%3A17%3A15%20GMT%2B0100%20%28GMT%20Summer%20Time%29&payment_status=Completed&address_status=confirmed&payer_status=verified&first_name=John&last_name=Smith&payer_email=buyer#paypalsandbox.com&payer_id=TESTBUYERID01&address_name=John%20Smith&address_country=United%20States&address_country_code=US&address_zip=95131&address_state=CA&address_city=San%20Jose&address_street=123%20any%20street&business=seller#paypalsandbox.com&receiver_email=seller#paypalsandbox.com&receiver_id=seller#paypalsandbox.com&residence_country=US&item_name=something&item_number=AK-1234&quantity=1&shipping=3.04&tax=2.02&mc_currency=USD&mc_fee=0.44&mc_gross=12.34&mc_gross_1=12.34&txn_type=web_accept&txn_id=300182286¬ify_version=2.1&custom=xyz123&invoice=abc1234&test_ipn=1&verify_sign=undefined
Paypal returned: 'INVALID'
The code I'm currently using is this:
public function verifyIPN()
{
if ( ! count($_POST)) {
throw new Exception("Missing POST Data");
}
$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) {
// Since we do not want the plus in the datetime string to be encoded to a space, we manually encode it.
if ($keyval[0] === 'payment_date') {
if (substr_count($keyval[1], '+') === 1) {
$keyval[1] = str_replace('+', '%2B', $keyval[1]);
}
}
$myPost[$keyval[0]] = urldecode($keyval[1]);
}
}
// Build the body of the verification post request, adding the _notify-validate command.
$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 = rawurlencode(stripslashes($value));
} else {
$value = rawurlencode($value);
}
// Added this line to see if it helps
$value = str_replace('%40', '#', $value);
$req .= "&$key=$value";
}
// Post the data back to PayPal, using curl. Throw exceptions if errors occur.
$ch = curl_init($this->getPaypalUri());
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_SSLVERSION, 6);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
// This is often required if the server is missing a global cert bundle, or is using an outdated one.
if ($this->use_local_certs) {
curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . "/cert/cacert.pem");
}
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'User-Agent: PHP-IPN-Verification-Script',
'Connection: Close',
));
$res = curl_exec($ch);
if (!($res)) {
file_put_contents("paypal-errors.txt", "error no: ".$errno($ch)." errorstr: ".curl_error($ch));
$errno = curl_errno($ch);
$errstr = curl_error($ch);
curl_close($ch);
throw new Exception("cURL error: [$errno] $errstr");
}
$info = curl_getinfo($ch);
$http_code = $info['http_code'];
if ($http_code != 200) {
file_put_contents("paypal-errors.txt", "error http status response = ".$http_code);
throw new Exception("PayPal responded with http code $http_code");
}
curl_close($ch);
file_put_contents("paypal-sent.txt", "response: ".file_get_contents('php://input'));
file_put_contents("paypal-result.txt", "response: ".$res);
// Check if PayPal verifies the IPN data, and if so, return true.
if ($res == self::VALID) {
return true;
} else {
return false;
}
}
So at this point, I'm assuming there is something wrong with my environment. My server has TLS 1.2 and 1.3. I've ran out of ideas for troubleshooting. Any help would be greatly appreciated!
Related
When validating IPNs using cmd=_notify-validate, I am occasionally receiving a cryptic response code that is neither 'VERIFIED' nor 'INVALID', but instead appears to be binary and unintelligible. However, this behavior occurs unpredictably and the exact same call to notify-validate later will produce a valid response. This has been occurring intermittently for years and I've never been able to figure out where the issue is. Would it be something with PayPal, an error in my code, or something else I'm not considering?
Since this happens intermittently and the exact same request succeeds at a later time, I have worked around this by queuing and re-verifying any transactions that don't return a definitive response from PayPal. This is fine as a workaround but not really the optimal workflow especially as the number of transactions increases.
public static function Verify() {
$start = "url=members/ipn&";
$errno = $errstr = null;
$req = 'cmd=_notify-validate';
foreach($_REQUEST as $key => $value) {
if(!is_array($value)) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
}
$verified = false;
$fp = fsockopen("ssl://www.paypal.com", 443, $errno, $errstr, 30);
if(!$fp) {
Paypal::Log("ERROR", "Failed connecting to PayPal:$errstr ($errno)\n");
}
else {
if($errno || $errstr) {
Paypal::Log("ERROR", $errno.":".$errstr);
}
$header = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$header .= "Host:".$host."\r\n";
$header .= "Connection: clost\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: ".strlen($req)."\r\n\r\n";
fputs($fp, $header.$req);
$header = true;
while(!feof($fp)) {
$res = trim(fgets($fp, 4096));
if(strlen($res) == 0) {
$header = false;
continue;
}
if($header) {
continue;
}
if(strcmp($res, "VERIFIED") == 0) {
$verified = true;
break;
}
else {
$verified = false;
break;
}
}
}
fclose($fp);
return $verified;
}
Most of the responses from this code return 'VERIFIED' in plain text (in $res var), but sometimes it fails and returns what appears to be binary characters. I can't even paste the response here because it's all non-text characters and is unintelligible.
Another thing to note is that fsockopen is working and it is not producing any errors. It's just that $res sometimes has unknown data in it.
-- UPDATE 1 --
As suggested, I switched the verification process over to using cURL and followed PayPal's recommended guidelines.
$ch = curl_init('https://ipnpb.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_SSLVERSION, 6);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . "/cert/cacert.pem");
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'User-Agent: PHP-IPN-Verification-Script',
'Connection: Close',
));
if ( !($res = curl_exec($ch)) ) {
Paypal::Log("cURL Error:", curl_error($ch));
curl_close($ch);
exit;
}
curl_close($ch);
$info = curl_getinfo($ch);
$http_code = $info['http_code'];
if ($http_code != 200) {
Paypal::Log("HTTP Code:", $http_code);
}
Paypal::Log("Response", $res);
if(strcmp($res, "VERIFIED") == 0) {
$verified = true;
}
else {
$verified = false;
}
It's mostly working, however the intermittent problem is still occurring where once in a while a verification attempt returns a different response.
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
</body></html>
When the exact same call is made to verify the transaction, it works. So I'm just not understanding why sometimes this 400 Bad Request error occurs. Any ideas?
show in your code how you are getting $req - the 400 error sounds like you are grabbing some garbage somewhere that was not intended (or maybe not sent) by PayPal.
I picked this up years (and years) ago and it has never failed.... - perhaps the 'issue' mentioned is what you are seeing.
// Read POST data
// reading posted data 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);
$post = array();
foreach ($raw_post_array as $keyval) {
$keyval = explode('=', $keyval);
if (count($keyval) == 2)
$post[$keyval[0]] = urldecode($keyval[1]);
}
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
foreach ($post as $key => $value) {
$value = urlencode(addslashes($value));
$req .= "&$key=$value";
}
Hope this helps!
I’m getting INVALID IPN returns using MAMP for development and testing with the PayPal Sandbox.
I have reviewed a few dozen of the more recent posts that relate to this issue and found no successful advice.
My MAMP setup passed the PayPal test for compliance with TLS 1.2 and HTTP/1.1
I have the May 2019 casert.pem bundle in the same directory as the listener.
All of the transaction variables in the Pay Now button are integers, money_format or urlencoded text.
The transactions are COMPLETED. Sandbox IPN history has been unavailable since I started this test so HTTP code and other data are not available. (Can any one else access IPN history in the sandbox?)
The setup is essentially the same as a live application that has been working on a production site with SSL. So the obvious differences are: MAMP and no SSL (notify_url and return URLs are http and not https). However, I ran similar code in MAMP several months ago without problems.
I get a quick take on the error by holding the listener file open in my browser and refreshing it after I receive the correct ‘return’ PDT data. When live, the code sends emails on errors.
My code—leaving out the application code that runs if IPN verified--is pretty much a copy of the example code.
<?php
$paypal_url = “'https://www.sandbox.paypal.com/cgi-bin/webscr';
$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";
}
$ch = curl_init($paypal_url);
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_CAINFO, dirname(__FILE__) . '/cacert.pem');
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
if( !($res = curl_exec($ch)) ) {
echo "curl_error($ch)";
exit:
}
if (strcmp ($res, "VERIFIED") == 0) {
echo "VERIFIED";
} elseif (strcmp ($res, “INVALID”) == 0) {
echo “INVALID”;
}
else {
echo “NO IPN”;
}
?>
I've created a checkout system using PayPal. I'm using some code for the IPN that I got from GitHub, and I've added a piece of code to mark the item as sold in the inventory, which is stored in the website's MySQL database.
I have tested the code, and I have found that it carries out the CURL and a message is received at PayPal's end, but the code to update the database wasn't being executed. To test that the code to update the database worked, I moved it to before when the CURL is carried out. When I did this, the code ran, and the database was updated. So, there must be an issue that stops the code when the CURL is executed.
This is the entire code from the page that PayPal sends the IPN to.
<?php
// STEP 1: read POST data
// Reading POSTed data directly from $_POST causes serialization issues with array data in the POST.
// Instead, read raw POST data from the input stream.
$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 IPN message sent from PayPal and prepend 'cmd=_notify-validate'
$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.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;
}
// STEP 3: Inspect IPN validation result and act accordingly
if (strcmp ($res, "VERIFIED") == 0) {
// The IPN is verified, process it:
// 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 the notification
// assign posted variables to local variables
$item_number = $_POST['item_number'];
$item_name = $_POST['item_name'];
$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'];
include('sql.php');
mysqli_query($mysqli, $sql);
$sql = "UPDATE inventory SET sold='1' WHERE code='".$item_number."'";
if (mysqli_query($mysqli, $sql)) {
foreach($_POST as $key => $value) {
echo $key." = ". $value."<br>";
}
}else{
echo $mysqli->error;
}
mysqli_close($mysqli);
} else if (strcmp ($res, "INVALID") == 0) {
// IPN invalid, log for manual investigation
echo "The response from IPN was: <b>" .$res ."</b>";
}
curl_close($ch);
?>
I literally just did this two weeks ago. Such a pain in the butt. Here's what I used:
function connectAPI($data, $location){
$getData = array();
foreach($data as $key=>$value){
$getData[] = urlencode($key).'='.urlencode($value);
}
$location = $location . '?'.implode('&', $getData);
$ch = curl_init($location);
curl_setopt($ch,CURLOPT_USERAGENT,$_SERVER['HTTP_USER_AGENT']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$return['status'] = $status;
$return['response'] = $response;
return $return;
}
$input = $_POST;
$headers = getallheaders();
$location = 'https://ipnpb.paypal.com/cgi-bin/webscr';
$response = array('cmd'=>'_notify-validate');
$jsondata = $response + $input;
$resend = connectAPI($jsondata, $location);
if($resend['response'] === 'VERIFIED'){
// --- Do what you need to do.
}
echo $resend['response'];
I am developing a shopping cart application, and in that application, I decided to integrate PayPal IPN. However, it keeps returning INVALID. It successfully transfers money and is deducted from my account and moved to the buyer's account.
if ( ! count($_POST)) {
throw new \Exception("Missing POST Data");
}
$raw_post_data = file_get_contents('php://input');
$raw_post_array = explode('&', $raw_post_data);
$myPost = [];
foreach ($raw_post_array as $keyval) {
$keyval = explode('=', $keyval);
if (count($keyval) == 2) {
// Since we do not want the plus in the datetime string to be encoded to a space, we manually encode it.
if ($keyval[0] === 'payment_date') {
if (substr_count($keyval[1], '+') === 1) {
$keyval[1] = str_replace('+', '%2B', $keyval[1]);
}
}
$myPost[$keyval[0]] = urldecode($keyval[1]);
}
}
// Build the body of the verification post request, adding the _notify-validate command.
$req = 'cmd=_notify-validate';
$get_magic_quotes_exists = false;
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";
}
// Post the data back to PayPal, using curl. Throw exceptions if errors occur.
$ch = curl_init(self::VERIFY_URI);
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_SSLVERSION, 6);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
// This is often required if the server is missing a global cert bundle, or is using an outdated one.
curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . "/cert/cacert.pem");
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Connection: Close']);
$res = curl_exec($ch);
$info = curl_getinfo($ch);
$http_code = $info['http_code'];
if ($http_code != 200) {
throw new \Exception("PayPal responded with http code $http_code");
}
if ( ! ($res)) {
$errno = curl_errno($ch);
$errstr = curl_error($ch);
curl_close($ch);
throw new \Exception("cURL error: [$errno] $errstr");
}
curl_close($ch);
// Check if PayPal verifies the IPN data, and if so, return true.
if ($res == "VERIFIED") {
return 'success';
} else {
return print_r($res, true);
}
My inputs and outputs are exactly the same. However, this particular key is different:
INPUT: [payment_date] => 2016-12-09T15:07:18Z
OUTPUT: payment_date=2016-12-09T14%3A12%3A21Z (After the query is made).
I encountered this same error yesterday. Try the following:
$tokens = explode("\r\n\r\n", trim($res));
$res = trim(end($tokens));
if (strcmp($res, "VERIFIED") == 0) {
return "Success";
}else if (strcmp($res, "INVALID") == 0) {
//deal with invalid IPN
//mail admin or alert client
}
PayPal IPN variables are already encoded and there is no need to do it twice, replace with this code below
if(function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()){
$varvalue = urlencode(stripslashes($varvalue));
}
else {
$value = urlencode($value);
}
Does anyone know know how to integrate PayPal IPN on my website with PHP? I need to insert a record into my database after receiving the payment. But I can hardly find a sample code for this in the PayPal developers website.
here is the php sample code for IPN from PayPal developers website. I've marked the exact location for the database INSERT via a comment in the end of this 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://ipnpb.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
$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'];
// <---- HERE you can do your INSERT to the database
} else if (strcmp ($res, "INVALID") == 0) {
// log for manual investigation
}
?>
Paypal has released a Github Repository which contains sample codes for IPN Listeners. I would highly recommend checking and testing those out based on your preferred language.
You may check out the Github Repo at: https://github.com/paypal/ipn-code-samples
You can find the IPN PHP Class below:
<?php
class PaypalIPN
{
/** #var bool Indicates if the sandbox endpoint is used. */
private $use_sandbox = false;
/** #var bool Indicates if the local certificates are used. */
private $use_local_certs = true;
/** Production Postback URL */
const VERIFY_URI = 'https://ipnpb.paypal.com/cgi-bin/webscr';
/** Sandbox Postback URL */
const SANDBOX_VERIFY_URI = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr';
/** Response from PayPal indicating validation was successful */
const VALID = 'VERIFIED';
/** Response from PayPal indicating validation failed */
const INVALID = 'INVALID';
/**
* Sets the IPN verification to sandbox mode (for use when testing,
* should not be enabled in production).
* #return void
*/
public function useSandbox()
{
$this->use_sandbox = true;
}
/**
* Sets curl to use php curl's built in certs (may be required in some
* environments).
* #return void
*/
public function usePHPCerts()
{
$this->use_local_certs = false;
}
/**
* Determine endpoint to post the verification data to.
*
* #return string
*/
public function getPaypalUri()
{
if ($this->use_sandbox) {
return self::SANDBOX_VERIFY_URI;
} else {
return self::VERIFY_URI;
}
}
/**
* Verification Function
* Sends the incoming post data back to PayPal using the cURL library.
*
* #return bool
* #throws Exception
*/
public function verifyIPN()
{
if ( ! count($_POST)) {
throw new Exception("Missing POST Data");
}
$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) {
// Since we do not want the plus in the datetime string to be encoded to a space, we manually encode it.
if ($keyval[0] === 'payment_date') {
if (substr_count($keyval[1], '+') === 1) {
$keyval[1] = str_replace('+', '%2B', $keyval[1]);
}
}
$myPost[$keyval[0]] = urldecode($keyval[1]);
}
}
// Build the body of the verification post request, adding the _notify-validate command.
$req = 'cmd=_notify-validate';
$get_magic_quotes_exists = false;
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";
}
// Post the data back to PayPal, using curl. Throw exceptions if errors occur.
$ch = curl_init($this->getPaypalUri());
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_SSLVERSION, 6);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
// This is often required if the server is missing a global cert bundle, or is using an outdated one.
if ($this->use_local_certs) {
curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . "/cert/cacert.pem");
}
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'User-Agent: PHP-IPN-Verification-Script',
'Connection: Close',
));
$res = curl_exec($ch);
if ( ! ($res)) {
$errno = curl_errno($ch);
$errstr = curl_error($ch);
curl_close($ch);
throw new Exception("cURL error: [$errno] $errstr");
}
$info = curl_getinfo($ch);
$http_code = $info['http_code'];
if ($http_code != 200) {
throw new Exception("PayPal responded with http code $http_code");
}
curl_close($ch);
// Check if PayPal verifies the IPN data, and if so, return true.
if ($res == self::VALID) {
return true;
} else {
return false;
}
}
}
And the IPN Listener example:
<?php namespace Listener;
require('PaypalIPN.php');
use PaypalIPN;
$ipn = new PaypalIPN();
// Use the sandbox endpoint during testing.
$ipn->useSandbox();
$verified = $ipn->verifyIPN();
if ($verified) {
/*
* Process IPN
* A list of variables is available here:
* https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNandPDTVariables/
*/
}
// Reply with an empty 200 response to indicate to paypal the IPN was received correctly.
header("HTTP/1.1 200 OK");