0

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?

  • 3
    Why are you using fsock instead of a more reliable cURL call? – aynber Jul 11 '19 at 16:54
  • Not sure why fsock was used here, but I've switched it to cURL following the example provided by PayPal (https://developer.paypal.com/docs/classic/ipn/ht-ipn/). Since the issue is not reproducible on command I'll have to watch for any new failures. Initially though it is working using cURL. – Stephen Walker Jul 12 '19 at 17:01
  • Ok so now using cURL, I have encountered a failed verification as I was before, so the problem is not yet solved. However, it is now showing a more intelligible response which is a 400 Bad Request. The full response is in html form: ` 400 Bad Request

    Bad Request

    Your browser sent a request that this server could not understand.

    `
    – Stephen Walker Jul 13 '19 at 21:36
  • I updated my original question with further details in light of using cURL. – Stephen Walker Jul 13 '19 at 21:43
  • I got the same error with cURL, but the problem sometimes happened, sometimes not. For example, a payment is completed and validated successfully but if you make the same payment again 1 minute later you get this 400 error, you try again and everything goes well. Crazy! I think this is a problem from PayPal instead of the code. – Hung Tran Jul 05 '21 at 03:25

1 Answers1

0

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!

Apps-n-Add-Ons
  • 2,026
  • 1
  • 17
  • 28
  • Thanks, though I don't see this being the issue. I am cleaning the data the same way using urlencode and addslashes. Furthermore, I am logging the complete request string sent through cURL and the attempts that produce the 400 Bad Request error exactly match the retries that then succeed. Not seeing any junk or invalid characters in it. I've compared the failed requests to the successful ones and they match exactly character by character. So it seems that there is a communication error occurring. Switching to cURL (instead of fsock) has not fixed it either. – Stephen Walker Jul 14 '19 at 16:56
  • Another thing to note is that I have often seen these errors happening in groups, as though during certain periods of time all requests return an error, then for reasons unknown it's working again. As long as I retry any failed requests later, I am eventually able to verify all the IPNs. – Stephen Walker Jul 14 '19 at 16:57
  • "foreach($_REQUEST as $key => $value)" is NOT the same..... - see the notes at the top of my answer...... I recommend you try using 'php://input' data - it has never failed in years (we don't have a retry mechanism and we always get the IPN data, though, granted, we don't do a large volume through Paypal either). You could also try looking at the data as binary - that often reveals 'unseen' characters. Otherwise, you are suggesting PayPal fails, which may be true, though would be reported by thousands.... – Apps-n-Add-Ons Jul 14 '19 at 17:17
  • Also, check out https://stackoverflow.com/questions/14008067/what-post-serialization-issues-does-paypal-php-ipn-example-refer-to?noredirect=1&lq=1 – Apps-n-Add-Ons Jul 14 '19 at 17:17
  • Ok thanks for the additional information. I've updated my code to exactly match your example. I'll report back once I can see if this solves the problem or if errors are still encountered. – Stephen Walker Jul 15 '19 at 17:23