PayPal IPN verification - php

I have an IPN which is sending the messages correctly to my file, I have confirmed that I am receiving them, first here's my code:
echo "test";
// Response from Paypal
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$value = preg_replace('/(.*[^%^0^D])(%0A)(.*)/i','${1}%0D%0A${3}',$value);// IPN fix
$req .= "&$key=$value";
}
// assign posted variables to local variables
$data['item_name'] = $_POST['item_name'];
$data['item_number'] = $_POST['item_number'];
$data['payment_status'] = $_POST['payment_status'];
$data['payment_amount'] = $_POST['mc_gross'];
$data['payment_currency'] = $_POST['mc_currency'];
$data['txn_id'] = $_POST['txn_id'];
$data['receiver_email'] = $_POST['receiver_email'];
$data['payer_email'] = $_POST['payer_email'];
$data['custom'] = $_POST['custom'];
// post back to PayPal system to validate
$header = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$header .= "Host: www.sanbox.paypal.com\r\n";
$header .= "Accept: */*\r\n";
$header .= "Connection: Close\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "\r\n";
$fp = fsockopen ('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
if ($fp === FALSE) {
exit("Could not open socket");
}else{
if (!$fp) {
echo "Error code: 200";
}else{
fputs ($fp, $header . $req);
$res = stream_get_contents($fp, 2048);
echo "test2";
$res = trim($res);
if (strcmp($res, "VERIFIED") == 0){
echo "test3";
// Used for debugging
//#mail("you#youremail.com", "PAYPAL DEBUGGING", "Verified Response<br />data = <pre>".print_r($post, true)."</pre>");
// Validate payment (Check unique txnid & correct price)
$valid_txnid = check_txnid($data['txn_id']);
$valid_price = check_price($data['payment_amount'], $data['item_number']);
// PAYMENT VALIDATED & VERIFIED!
if($valid_txnid && $valid_price){
$orderid = updatePayments($data);
if($orderid){
echo "thanks for your payment!";
$lel = mysqli_query($con,"UPDATE stats SET stats.bankedgold = 10000 WHERE stats.id = $id4");
// Payment has been made & successfully inserted into the Database
}else{
echo "Error code: 100";
// E-mail admin or alert user
}
}else{
echo "Error code: 130";
// Payment made but data has been changed
// E-mail admin or alert user
}
}else if (strcmp ($res, "INVALID") == 0) {
echo "Error code: 170";
// PAYMENT INVALID & INVESTIGATE MANUALY!
// E-mail admin or alert user
// Used for debugging
//#mail("you#youremail.com", "PAYPAL DEBUGGING", "Invalid Response<br />data = <pre>".print_r($post, true)."</pre>");
}
echo strcmp($res,"VERIFIED" == 0);
fclose ($fp);
}
}
}
Now, a var_dump of $res gives:
HTTP/1.1 200 OK
Date: Mon, 02 Mar 2015 11:24:35 GMT
Server: Apache X-Frame-
Options: SAMEORIGIN
Set-Cookie: <REDACTED_FOR_PRIVACY>
Set-Cookie: navcmd=_notify-validate; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: navlns=0.0; expires=Wed, 01-Mar-2017 11:24:35 GMT; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: Apache=10.72.108.11.1425295475397401; path=/; expires=Wed, 22-Feb-45 11:24:35 GMT
Vary: Accept-Encoding,User-Agent Connection: close
Set-Cookie: <REDACTED_FOR_PRIVACY>
Set-Cookie: X-PP-SILOVER=; Expires=Thu, 01 Jan 1970 00:00:01 GMT
Set-Cookie: Apache=10.72.128.11.1425295475383817; path=/; expires=Wed, 22-Feb-45 11:24:35 GMT
Strict-Transport-Security: max-age=14400
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8 8
VERIFIED 0
But I need it to say just VERIFIED or INVALID so I can determine what to do from there.
A simple fix for me would be to add "strpos" and just see if it contains the words but I want to verify with you experts that it would be a good fix?

PayPal has started using HTTP/1.1 to send its responses. This means, among other things, that you get Transfer-Encoding: chunked in the response.
The chunked encoding requires parsing. Typically it will look like this:
8
VERIFIED
0
Translated, this gives "chunk of length 8 = VERIFIED || chunk of length 0 = end of stream"
If you can't parse this response, you can try to force PayPal to not use chunked encoding by specifying HTTP/1.0 in your validation response. Alternatively, use a better library like cURL to do the parsing for you - PayPal's demo IPN code was written for a very old version of PHP, or at least one with no extensions enabled. It seems like the programmer was ignorant of the http_build_query function to properly encode the $req verification data, which is just sad really. You'd think PayPal could afford competent developers *ahem* but anyway... Personally I "solved" it by just reading the Content-Length header - all the responses that PayPal can send here have different lengths. Sort of cheating, but it worked.
Hope this helps :)

Change this line:
$value = urlencode(stripslashes($value));
To this:
$value = urlencode($value);
You only need to use stripslashes there if you're runnning PHP with Magic Quotes on. As of PHP 5.4, that's (thankfully) no longer possible.
Paypal allows user data to contain backslashes (I have seen it in IPN data in the address_street variable). If you strip the backslashes out from IPN data that had them, Paypal will return an INVALID response.

Related

PayPal IPN error: line folding of header fields is not supported

A PayPal IPN script that's been running for years suddenly stopped working. PayPal is returning the following response:
HTTP/1.1 400 Bad Request
Connection: close
Content-Length: 46
content-type: text/plain; charset=utf-8
line folding of header fields is not supported
To summarize how PayPal IPN is supposed to work:
PayPal POSTs to an endpoint on your system
The endpoint must reply back to PayPal with the POST data it received
PayPal responds back with VERIFIED
In my case, PayPal cannot verify the response because, "line folding of header fields is not supported".
Google's not providing much on "line folding header fields". I can only assume it's something to do with header formatting. Here is the pertinent code:
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
// post back to PayPal system to validate
// 7/22/2013 - Update to use HTTP 1.1 instead of HTTP 1.0
$header = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n";
$header .= "Host: www.paypal.com\r\n ";
$header .= "Connection: close\r\n\r\n";
// Open a connection to PayPal.com
$fp = #fsockopen("ssl://{$target}", 443, $errno, $errstr, 30);
if (!$fp) {
// HTTP ERROR
}else{
#fputs ($fp, $header . $req);
while (!#feof($fp)) {
$res = #fgets ($fp, 1024);
if (strcmp ($res, "VERIFIED") == 0) {
$verified = true;
}else{
// Log failure
}
}
#fclose ($fp);
}
Any ideas what might be causing the error about line folding in regards to the headers?
Header folding is explained under https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4:
Historically, HTTP header field values could be extended over
multiple lines by preceding each extra line with at least one space
or horizontal tab (obs-fold).
I had to echo out the headers you are generating to see the problem myself, it is quite hard to spot:
$header .= "Host: www.paypal.com\r\n ";
The extra space after the line break here, means the next header line will start with that space - and that means, you are "folding headers", without having actually intended to do so.
Remove that extra trailing space, and things should work fine.

Why does tlstest.paypal.com work from browser but not from my PHP code (useful for Paypal IPN)?

After 2018 June 30th, Paypal won't accept non-TLS 1.2 + HTTP 1.1 requests anymore.
They created the URL https://tlstest.paypal.com/ to test if connections are OK. If we open this URL in a browser, we get a successful:
PayPal_Connection_OK
Quesiton: why does it fail when connecting from PHP with the following code? (I get no response at all, the browser is still in waiting "state" like this, so it doesn't even arrive at echo $errno; echo $errstr;)
<?php
$req = ''; // usually I use $req = 'cmd=_notify-validate'; for IPN
$header .= "POST / HTTP/1.1\r\n";
$header .= "Host: tlstest.paypal.com\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen('tls://tlstest.paypal.com', 443, $errno, $errstr, 30);
if (!$fp) {
echo $errno;
echo $errstr;
} else {
fputs($fp, $header);
while (!feof($fp))
{
$res = fgets($fp, 1024);
echo $res;
}
fclose($fp);
}
?>
Note:
this is not a duplicate of Paypal IPN HTTP/1.1 - doesn't provide any response at all, it is an empty string, the latter is outdated.
I have PHP 5.6.33-0+deb8u1 (cli) (built: Jan 5 2018 15:46:26) and openssl version text: OpenSSL 1.0.1t 3 May 2016
It works on my side by changing tls:// to ssl:// which makes absolutely no sense to me, but this is also why using fsockopen is a too low level library to just do HTTP exchanges with it (you should use a proper HTTP client library) and at the same time not configurable enough regarding TLS stuff.
With
$fp = fsockopen('tls://tlstest.paypal.com', 443, $errno, $errstr, 30);
I get :
HTTP/1.1 426 Unknown
Server: AkamaiGHost
Mime-Version: 1.0
Content-Type: text/html
Content-Length: 267
Expires: Fri, 22 Jun 2018 19:49:46 GMT
Date: Fri, 22 Jun 2018 19:49:46 GMT
Connection: keep-alive
Upgrade: TLS/1.2
<HTML><HEAD>
<TITLE>Access Denied</TITLE>
</HEAD><BODY>
<H1>Access Denied</H1>
You don't have permission to access "http://tlstest.paypal.com/" on this server.<P>
Reference #18.8024a17.1529696986.1fc51318
</BODY>
</HTML>
but with $fp = fsockopen('ssl://tlstest.paypal.com', 443, $errno, $errstr, 30);
I get:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 20
Date: Fri, 22 Jun 2018 20:05:35 GMT
Connection: keep-alive
And then it hangs, probably because it is a keep-alive connection and the buffer is smaller than 1024 so that you do not get the following body content.
Which is probably "PayPal_Connection_OK", as it exactly matches the length displayed in Content-Length.
This again shows that you should use an HTTP client library instead of trying to (badly) reimplement HTTP on top of fsockopen.
For completeness, here is a working code (full credit to PatrickMevzek's answer):
<?php
$req = '';
$header = "POST / HTTP/1.1\r\n";
$header .= "Host: tlstest.paypal.com\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen('ssl://tlstest.paypal.com', 443, $errno, $errstr, 3);
if (!$fp) {
echo $errno;
echo $errstr;
} else {
fputs($fp, $header);
while (!feof($fp))
{
$res = fgets($fp, 21); // 21 because length of "PayPal_Connection_OK"
echo $res;
}
fclose($fp);
}
?>
Here is the answer from server:
# php -f test.php
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 20
Date: Fri, 22 Jun 2018 20:19:56 GMT
Connection: keep-alive
PayPal_Connection_OK

Php PayPal IPN returns INVALID

I know that there have been a few changes to the PayPal IPN system as of May 15th, 2018. I happen to be in the middle of implementing my first IPN listener; and I'm not sure if I'm lost because of my resources (including SO posts, some dating back to 2009 on this subject) have been obsolesced by PayPal's changes, or merely because I am inexperienced in this field.
My suspicion is that I am pointing to the incorrect PayPal address:
$fh = fsockopen('ssl://www.paypal.com',443,$errno,$errstr,30);
//$fh = fsockopen('https://www.sandbox.paypal.com',80,$errno,$errstr,30);
//$fh = fsockopen('https://ipnpb.sandbox.paypal.com/cgi-bin/webscr',80,$errno,$errstr,30);
The first address completes in a successful handshake with PayPal, but returns INVALID as the IPN response. The second two handshake, but don't pass the if (!$fh) test.
I have tried both the code below, and the CURL code found in Chris Muench's answer here: PayPal IPN returns invalid in sandbox
<?php
// Paypal IPN code
header('HTTP/1.1 200 OK'); // send header
$resp = 'cmd=_notify-validate';
foreach ($_POST as $parm => $var){
$var = urlencode(stripslashes($var));
$resp .= "&$parm=$var";
}
$httphead = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$httphead .= "Content-Type: application/x-www-form-urlencoded\r\n";
$httphead .= "Content-Length: " . strlen($resp) . "\r\n\r\n";
// create a ="file handle" for writing to a URL to paypal.com on Port 443 (the IPN port)
$errno ='';
$errstr='';
$fh = fsockopen('ssl://www.paypal.com',443,$errno,$errstr,30);
//$fh = fsockopen('https://www.sandbox.paypal.com',443,$errno,$errstr,30);
//$fh = fsockopen('https://ipnpb.sandbox.paypal.com/cgi-bin/webscr',443,$errno,$errstr,30);
if (!$fh) {
// if-fh does not work - cnxn FAILED
}
else{
// Connection opened, so spit back the response and get PayPal's view whether it was an authentic notification
fputs ($fh,$httphead.$resp);
while (!feof($fh)){
$readresp = fgets ($fh, 1024);
if (strcmp(trim($readresp),"VERIFIED") == 0){
// if-fh works - cnxn OPEN';
// WE ALL WIN!!
}
else if(strcmp(trim($readresp),"INVALID") == 0){
// A possible hacking attempt, or
// In my case, a possible hacking false-positive
}
}
fclose ($fh);
}
?>
I'm testing using the IPN simulator in my sandbox account.
None of the SO recommended solutions have worked.
Please help!
For the record, this url seems to be working fine: https://www.paypal.com/cgi-bin/webscr

PayPal IPN leads to 502 Proxy Error

I am using the IPN code sample (for PHP) available on Github. When running this code via the IPN simulator, there is a huge loading time and after a few minutes PayPal finally throws this error.
Proxy Error
The proxy server received an invalid response from an upstream server. The proxy server could not handle the request POST /webapps/developer/applications/ipn_simulator.
Reason: Error reading from remote server
Another user faced the same exact issue here. However, it seems his problem was due to the fact he was not using the good script.
My ipn.php file, when accessed directly in the browser does not throw any error. It looks as follows:
/* INITIALIZATION */
require_once $_SERVER['DOCUMENT_ROOT'] . '/lib/aes.php'; // AES lib
require_once $_SERVER['DOCUMENT_ROOT'] . '/lib/db.php'; // DB configuration
require_once $_SERVER['DOCUMENT_ROOT'] . '/lib/mandrill/src/Mandrill.php'; // Mandrill mailer lib
$UA = $_SERVER['HTTP_USER_AGENT']; // User-Agent
//$proxy = '<my IP>:443';
$ip = getenv('HTTP_CLIENT_IP')?:
getenv('HTTP_X_FORWARDED_FOR')?:
getenv('HTTP_X_FORWARDED')?:
getenv('HTTP_FORWARDED_FOR')?:
getenv('HTTP_FORWARDED')?:
getenv('REMOTE_ADDR'); // IP address
$paypal_business = '< MY PAYPAL ID >'; // My PayPal ID
$license_amount = array( 10 => 0,
25 => 1,
50 => 2); // Type of license wrt to amount paid
$license_types = array( 0 => 1,
1 => 5,
2 => 20); // Number of licenses wrt type of license
$license_names = array( 0 => 'Single',
1 => 'Group',
2 => 'Company'); // Name of license wrt type of license
$donated = false;
/* MAILER powered by Mandrill */
function GmSendMail($bdy, $sbj, $em) {
try {
$mandrill = new Mandrill('< MY API KEY >'); // Mandrill API Key
$message = array(
'html' => $bdy,
'subject' => $sbj,
'from_email' => 'mail#example.com',
'from_name' => 'Company',
'to' => array(
array(
'email' => $em,
'type' => 'to'
)
),
'headers' => array('Reply-To' => 'support#example.com'),
'important' => true,
'track_opens' => true,
'track_clicks' => null,
'auto_text' => null,
'auto_html' => true,
'inline_css' => null,
'url_strip_qs' => null,
'preserve_recipients' => null,
'view_content_link' => null,
'tracking_domain' => null,
'signing_domain' => 'example.com',
'return_path_domain' => null,
'merge' => true,
'tags' => array('license-activation'),
'metadata' => array('website' => 'example.com')
);
$async = false;
$ip_pool = 'Main Pool';
$result = $mandrill->messages->send($message, $async, $ip_pool);
//print_r($result);
} catch(Mandrill_Error $e) {
// Mandrill errors are thrown as exceptions
echo 'An unexpected error occurred: ' . get_class($e) . ' - ' . $e->getMessage();
// A mandrill error occurred: Mandrill_Unknown_Subaccount - No subaccount exists with the id 'customer-123'
throw $e;
}
}
/* PAYPAL IPN: CHECK PAYMENT HAS BEEN COMPLETED */
// CONFIG: Enable debug mode. This means we'll log requests into 'ipn.log' in the same directory.
// Especially useful if you encounter network errors or other intermittent problems with IPN (validation).
// Set this to 0 once you go live or don't require logging.
define("DEBUG", 1);
// Set to 0 once you're ready to go live
define("USE_SANDBOX", 1);
define("LOG_FILE", "./ipn.log");
// 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);
$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";
}
// Post IPN data back to PayPal to validate the IPN data is genuine
// Without this step anyone can fake IPN data
if(USE_SANDBOX == true) {
$paypal_url = "https://www.sandbox.paypal.com/cgi-bin/webscr";
} else {
$paypal_url = "https://www.paypal.com/cgi-bin/webscr";
}
$ch = curl_init($paypal_url);
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);
if(DEBUG == true) {
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
}
// CONFIG: Optional proxy configuration
//curl_setopt($ch, CURLOPT_PROXY, $proxy);
//curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
// Set TCP timeout to 30 seconds
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
// CONFIG: Please download 'cacert.pem' from "http://curl.haxx.se/docs/caextract.html" and set the directory path
// of the certificate as shown below. Ensure the file is readable by the webserver.
// This is mandatory for some environments.
$cert = __DIR__ . "/lib/cacert.pem";
curl_setopt($ch, CURLOPT_CAINFO, $cert);
$res = curl_exec($ch);
if (curl_errno($ch) != 0) // cURL error
{
if(DEBUG == true) {
error_log(date('[Y-m-d H:i e] '). "Can't connect to PayPal to validate IPN message: " . curl_error($ch) . PHP_EOL, 3, LOG_FILE);
}
curl_close($ch);
exit;
} else {
// Log the entire HTTP response if debug is switched on.
if(DEBUG == true) {
error_log(date('[Y-m-d H:i e] '). "HTTP request of validation request:". curl_getinfo($ch, CURLINFO_HEADER_OUT) ." for IPN payload: $req" . PHP_EOL, 3, LOG_FILE);
error_log(date('[Y-m-d H:i e] '). "HTTP response of validation request: $res" . PHP_EOL, 3, LOG_FILE);
// Split response headers and payload
list($headers, $res) = explode("\r\n\r\n", $res, 2);
}
curl_close($ch);
}
// Inspect IPN validation result and act accordingly
if (strcmp ($res, "VERIFIED") == 0) {
//if (preg_match("!(VERIFIED)\s*\Z!",$res)) {
$payment_status = $_POST['payment_status'];
$payment_amount = $_POST['mc_gross'];
$payment_date = $_POST['payment_date'];
$txn_id = $_POST['txn_id'];
$receiver_business = $_POST['business'];
$payer_email = $_POST['payer_email'];
$email_hash = sha1($payer_email, TRUE);
$user_id = $_POST['custom'];
// Retrieve license information
$license = $license_amount[$payment_amount];
$nblic = $license_types[$license] - 1;
$name_license = $license_names[$license];
// Confirmation email variables
$subject_paypal = 'Premium License [' . $name_license . ']';
$body_paypal = '<p>Dear user.</p>';
if ( $payment_status == 'Completed' && $receiver_business == strtolower($paypal_business) ) {
/* DB interactions and checks */
$check = mysql_query("SELECT * FROM `premium` WHERE `id` = '$email_hash'");
/* 1) User already exists: Update DB */
if (mysql_num_rows($check) === 1) {
$row = mysql_fetch_assoc($check);
$transaction_id = $row['pa'];
if ($txn_id == $transaction_id) {
$donated = true;
die('Premium license already activated.');
} else {
$licenses_left = $row['la'];
$current_key = $row['k'];
$newnblic = $licenses_left + $nblic;
// UPDATE DB
mysql_query("UPDATE `premium` SET `lt` = '$license', `la` = '$newnblic', `t` = '$payment_date', `pa` = '$txn_id' WHERE `id` = '$email_hash' ");
// SEND EMAIL CONFIRMATION
GmSendMail($body_paypal, $subject_paypal, $payer_email);
}
/* 2) This is a NEW entry: Insert into DB
*/
} else {
// CREATE ENTRY INTO DB
mysql_query("INSERT INTO `premium` VALUES('$email_hash','$license','$nblic','$payment_date','$txn_id')");
// SEND EMAIL CONFIRMATION
GmSendMail($body_paypal, $subject_paypal, $payer_email);
}
}
if(DEBUG == true) {
error_log(date('[Y-m-d H:i e] '). "Verified IPN: $req ". PHP_EOL, 3, LOG_FILE);
}
} else if (strcmp ($res, "INVALID") == 0) {
// log for manual investigation
// Add business logic here which deals with invalid IPN messages
if(DEBUG == true) {
error_log(date('[Y-m-d H:i e] '). "Invalid IPN: $req" . PHP_EOL, 3, LOG_FILE);
}
}
The ipn.log file, when accessed directly from the browser reads:
[2014-02-15 11:53 America/Denver] HTTP request of validation request:POST /cgi-bin/webscr HTTP/1.1
Host: www.sandbox.paypal.com
Accept: */*
Connection: Close
Content-Length: 20
Content-Type: application/x-www-form-urlencoded
for IPN payload: cmd=_notify-validate
[2014-02-15 11:53 America/Denver] HTTP response of validation request: HTTP/1.1 200 OK
Date: Sat, 15 Feb 2014 18:53:08 GMT
Server: Apache
X-Frame-Options: SAMEORIGIN
Set-Cookie: < Cookie Hash >; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: cookie_check=yes; expires=Tue, 13-Feb-2024 18:53:08 GMT; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: navcmd=_notify-validate; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: navlns=0.0; expires=Mon, 15-Feb-2016 18:53:08 GMT; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: Apache=10.72.109.11.1392490388263396; path=/; expires=Mon, 08-Feb-44 18:53:08 GMT
Connection: close
Set-Cookie: X-PP-SILOVER=name%3DSANDBOX3.WEB.1%26silo_version%3D880%26app%3Dslingshot%26TIME%3D2495086418; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: X-PP-SILOVER=; Expires=Thu, 01 Jan 1970 00:00:01 GMT
Set-Cookie: Apache=10.72.128.11.1392490388253446; path=/; expires=Mon, 08-Feb-44 18:53:08 GMT
Vary: Accept-Encoding
Strict-Transport-Security: max-age=14400
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
INVALID
[2014-02-15 11:53 America/Denver] Invalid IPN: cmd=_notify-validate
For information, ipn.php is hosted on a subdomain with https enabled, .htaccess is clean and does not block any proxy. PHP 5.4 is used. I'm on a shared server with a dedicated IP.
I tried to uncomment the following lines in ipn.php:
// CONFIG: Optional proxy configuration
curl_setopt($ch, CURLOPT_PROXY, $proxy);
curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
with one time:
$proxy = 'mydedicatedIP:80';
another time:
$proxy = 'mydedicatedIP:443';
Without any success. Any help welcome !

Why is my PayPal IPN script failing?

I'm developing a lightweight e-commerce solution that uses PayPal as the payment gateway. However, my IPN callback is constantly returning an INVALID response. I even tried using the sample PHP script provided by PayPal:
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
// 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 ('ssl://www.paypal.com', 443, $errno, $errstr, 30);
// 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'];
if (!$fp) {
// HTTP ERROR
}
else {
fputs ($fp, $header . $req);
while (!feof($fp)) {
$res = fgets ($fp, 1024);
if (strcmp ($res, "VERIFIED") == 0) {
// 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
}
else if (strcmp ($res, "INVALID") == 0) {
// log for manual investigation
}
}
fclose ($fp);
}
But as I say, I keep getting an INVALID response. This is in fact the response I get using a PHP class that writes the response to a log file:
[2011-06-02 19:18:49] - FAIL: IPN Validation Failed.
IPN POST data from PayPal:
test_ipn=1,
payment_type=instant,
payment_date=11:07:43 Jun 02, 2011 PDT,
payment_status=Completed,
payer_status=verified,
first_name=John,
last_name=Smith,
payer_email=buyer#paypalsandbox.com,
payer_id=TESTBUYERID01,
business=seller#paypalsandbox.com,
receiver_email=seller#paypalsandbox.com,
receiver_id=TESTSELLERID1,
residence_country=US,
item_name1=something,
item_number1=AK-1234,
quantity1=1,
tax=2.02,
mc_currency=USD,
mc_fee=0.44,
mc_gross=15.34,
mc_gross_1=12.34,
mc_handling=2.06,
mc_handling1=1.67,
mc_shipping=3.02,
mc_shipping1=1.02,
txn_type=cart,
txn_id=4362187,
notify_version=2.4,
custom=xyz123,
invoice=abc1234,
charset=windows-1252,
verify_sign=AjbdIvvDAW2fh1O9jAbEym4myX.WAV7-jCEiEWMqoSkewvM6L3Co6oUQ
This is from the official PayPal IPN test tool. So something between PayPal's sample code and test tool is causing my script to fail. Any one have any ideas?
what is encoding of your html/php page ? (charset=windows-1252) ?

Categories