Paypal SandBox IPN always returns INVALID

As mentioned in one of the comments in an answer below, I tried following this tutorial. So now I have the following:


The ipn.php file:

<?php

    $ipn_post_data = $_POST;

    $url = 'https://www.sandbox.paypal.com/cgi-bin/webscr';

    // Set up request to PayPal
    $request = curl_init();
    curl_setopt_array($request, array
    (
        CURLOPT_URL => $url,
        CURLOPT_POST => TRUE,
        CURLOPT_POSTFIELDS => http_build_query(array('cmd' => '_notify-validate') + $ipn_post_data),
        CURLOPT_RETURNTRANSFER => TRUE,
        CURLOPT_HEADER => FALSE,
        CURLOPT_SSL_VERIFYPEER => TRUE,
        CURLOPT_CAINFO => 'cacert.pem',
    ));

    // Execute request and get response and status code
    $response = curl_exec($request);
    $status   = curl_getinfo($request, CURLINFO_HTTP_CODE);

    // Close connection
    curl_close($request);

    if($status == 200 && $response == 'VERIFIED')
    {
        $subject = "valid";
        $message = "good";
    }
    else
    {
        $subject = "invalid";
        $message = "bad";
    }

    $to = "[email protected]";
    $from = "[email protected]";

    $header  = 'MIME-Version: 1.0' . "\r\n";
    $header .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
    $header .= 'To: Oshirowanen <[email protected]>' . "\r\n";
    $header .= 'From: Me <[email protected]>' . "\r\n";

    mail($to,$subject,$message,$header);

?>

The received email:

Subject "invalid"
Message "bad"

Solution 1:

Edit:

Now that I can see the array you've outputted, try replacing this to get rid of the PHP array error:

foreach ($_POST as $key => $value) {
    if (!is_array($value)) {
        $value = urlencode(stripslashes($value));
        $req .= "&$key=$value";
    }
    else if (is_array($value)) {
        $paymentArray = explode(' ', $value[0]);
        $paymentCurrency = urlencode(stripslashes($paymentArray[0]));
        $paymentGross = urlencode(stripslashes($paymentArray[1]));
        $req .= '&mc_currency=' . $paymentCurrency . '&mc_gross=' . $paymentGross;
    }
}

Here is the edited code in full:

// read the post from PayPal system and add 'cmd'
$req = 'cmd=' . urlencode('_notify-validate');

foreach ($_POST as $key => $value) {
    if (!is_array($value)) {
        $value = urlencode(stripslashes($value));
        $req .= "&$key=$value";
    }
    else if (is_array($value)) {
        $paymentArray = explode(' ', $value[0]);
        $paymentCurrency = urlencode(stripslashes($paymentArray[0]);
        $paymentGross = urlencode(stripslashes($paymentArray[1]);
        $req .= '&mc_currency=' . $paymentCurrency . '&mc_gross=' . $paymentGross;
    }
}

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://www.paypal.com/cgi-bin/webscr');
curl_setopt($ch, CURLOPT_HEADER, 0);
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_HTTPHEADER, array('Host: www.paypal.com'));
$res = curl_exec($ch);
curl_close($ch);


// 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 (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
}

Check this out!

Edit: Check out the PayPal troubleshooting tips:

https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_admin_IPNTesting

Solution 2:

The problem is that you don't check the HTTP response code, so you are intepreting the "Invalid Host header" as the PayPal response, whilst it's the web server response (for the status code 400).
If you look at the PayPal documentation, there is a PHP example which is very similar to your code, since it uses the "fsockopen", "fputs" and "fgets" functions to communicate with the PayPal server.
But if you look carefully at the remark after the "fsockopen" call, you can read:

// Process validation from PayPal 
// TODO: This sample does not test the HTTP response code. All 
// HTTP response codes must be handled or you should use an HTTP 
// library, such as cUrl

And this is exacty your problem: you don't check that the HTTP response code is 200 (OK), before parsing the response body.
Also, using the "strtolower" function is not correct, since the real response from the PayPal server is always uppercase, as shown in the above cited example.
Even if the PayPal example uses the "fsockopen" approach, I think it should be much better to use the PHP cURL library to implement your IPN listener.
Have also a look at the following answers:

  • PHP cURL PayPal Sandbox
  • cURL or fsockopen for paypal ipn

However, if you really want to use the "fsockopen" function, you should always specify the "Host" header field in the POST request, as shown in the following snippet of code (taken from the PHP manual):

<?php
$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
if (!$fp) {
    echo "$errstr ($errno)<br />\n";
} else {
    $out = "GET / HTTP/1.1\r\n";
    $out .= "Host: www.example.com\r\n";
    $out .= "Connection: Close\r\n\r\n";
    fwrite($fp, $out);
    while (!feof($fp)) {
        echo fgets($fp, 128);
    }
    fclose($fp);
}
?>

UPDATE

Here is a simple function for recursive stripslashes/urlencoding:

<html>
<body>
<pre>
<?

$post = Array (
  "transaction" => Array("USD 20.00"),
  "payment_request_date" => "Sun Aug '05 08:49:20 PDT 2012",
  "return_url" => "http://000.000.000.000/success.php"
);

echo "before myUrlencode...\n";
print_r($post);

function myUrlencode($post) {
  foreach ($post as $key => $val) {
    if (is_array($val)) {
      $post[$key] = myUrlencode($val);
    } else {
      $post[$key] = urlencode(stripslashes($val));
    }
  }
  return($post);
}

echo "\nafter myUrlencode...\n";
print_r(myUrlencode($post));

?>
</pre>
</body>
</html>

Solution 3:

  1. Got it working using the basic sample code 4b,

  2. Cleared $ipnNotificationUrl = ""; from the basic sample code as I had a value in there which I added myself,

  3. Created a seller account instead of a business pro account in sandbox,

  4. Set the seller account to enable the ipn url,

  5. Used the following PHP 5.2 sample code for the ipn listener

  6. Added the 2 lines into the listener, as described here, the 2 lines can be seen below:

  7. Downloaded the cacert.pem certificate to my server from here and put it in the same directory as the ipn listener:

The 2 lines mentioned in point 6:

CURLOPT_SSL_VERIFYPEER => TRUE,
CURLOPT_CAINFO => 'cacert.pem',

I have no idea why the sandbox business pro account does not let me set an ipn url, but the seller account does.

Solution 4:

These links may resolve your problem,

Paypal: Invalid IPN problem

http://www.webmasterworld.com/ecommerce/4292847.htm

Paypal sandbox IPN return INVALID

Solution 5:

I am not sure what is exactly wrong right now with your code, but I was strugling wuth the same while ago and my fixes was to add HOST in the header and host have to be www.paypal.com. I used fsockopen method and work fine now.

In Curl I had a problem before with ssl. And solution was to put those lines:

curl_setopt($curl, CURLOPT_COOKIEJAR, dirname(__FILE__) . "/cookies.txt");
curl_setopt($curl, CURLOPT_COOKIEFILE, dirname(__FILE__) . "/cookies.txt");

where of course file cookies.txt have to exists. and more over I had to run one connection to page to get session data and later send post data.

Below is a header what is working fine for me with fsockopen method

$header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Host: www.paypal.com\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";