2
votes

I know this has been asked some times, but none of the solutions worked for me. I am using the Instant Payment Notification (IPN) simulator from paypal (https://developer.paypal.com/webapps/developer/applications/ipn_simulator) but I always receive: Unexpected response from PayPal: HTTP/1.1 400 Bad Request

<?php
    //Open a socket for the acknowledgement request
        $fp = fsockopen (ssl://www.sandbox.paypal.com, 443, $errno, $errstr, 30);

        if (!$fp) { 
            // fsockopen error
            throw new Exception("An error occured while using fsockopen(): [$errno] $errstr");
        } 

        //Set up the acknowledgement request headers
        $header .= "POST /cgi-bin/webscr HTTP/1.1\r\n";
        $header .= "Host: ssl://www.sandbox.paypal.com\r\n";
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $header .= "Content-Length: ".strlen($data)."\r\n";
        $header .= "Connection: Close\r\n\r\n";

        // Post request back to PayPal for validation
        fputs ($fp, $header . $data);
        while (!feof($fp)) {                     // While not EOF
            $res = fgets ($fp, 1024);              // Get the acknowledgement response
            if (strcmp ($res, "VERIFIED") == 0)  {  // Response is VERIFIED
                $response = 'verified';
            }
            else if (strcmp ($res, "INVALID") == 0)  { // Response is INVALID
                $response = 'invalid';
            } 
            else {
                throw new Exception("Unexpected response from PayPal: $res");
            }
        }

Edit: After changing

$header .= "Host: ssl://www.sandbox.paypal.com\r\n";

to

$header .= "Host: www.sandbox.paypal.com\r\n";

I now receive a HTTP/1.1 200 OK response. Another problem is, that my if() part is not working, it always jumps into the last

 else {
                throw new Exception("Unexpected response from PayPal: $res");
  }

Is there anything wrong with if (strcmp ($res, "VERIFIED") == 0) ?

UPDATE 2: Here is my full code. Maybe someone can find the mistakes:

class Paypal {
    protected $sandbox = false;
    protected $data = null;

    const SANDBOX_URL = 'www.sandbox.paypal.com';
    const PAYPAL_URL = 'www.paypal.com';


    public function __construct($sandbox = false) { 
        $this->sandbox = $sandbox;
    }

    public function receiveData() {
        if (empty($_POST)) {
            throw new Exception('No $_POST data found');
        }
        $this->data = $_POST;
        // Read the notification from PayPal and create the acknowledgement response
        $req = 'cmd=_notify-validate';               // add 'cmd' to beginning of the acknowledgement you send back to PayPal

        foreach ($_POST as $key => $value) {         // Loop through the notification NV pairs
            $value = urlencode(stripslashes($value));  // Encode the values
            $req .= "&$key=$value";                    // Add the NV pairs to the acknowledgement
        }

        if ($this->fsock($req))
            return true;
        else
            return false;
    }

    public function getData() {
        return $this->data;
    }

    private function fsock($data) {
        //Open a socket for the acknowledgement request
        $fp = fsockopen ('ssl://'.$this->getURL(), 443, $errno, $errstr, 30);

        if (!$fp) { 
            // fsockopen error
            throw new Exception("An error occured while using fsockopen(): [$errno] $errstr");
        } 

        //Set up the acknowledgement request headers
        $header .= "POST /cgi-bin/webscr HTTP/1.1\r\n";
        $header .= "Host: ".$this->getURL()."\r\n";
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $header .= "Content-Length: ".strlen($data)."\r\n";
        $header .= "Connection: Close\r\n\r\n";

        // Post request back to PayPal for validation
        fputs ($fp, $header . $data);
        while (!feof($fp)) {                     // While not EOF
            $res = fgets ($fp, 1024);              // Get the acknowledgement response
            if (strcmp ($res, "VERIFIED") == 0)  {  // Response is VERIFIED
                $response = 'verified';
                // Notification protocol is complete, OK to process notification contents

                // Possible processing steps for a payment might include the following:

                // Check that the payment_status is Completed
                // 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)  { // Response is INVALID
                $response = 'invalid';
            } 
            else {
                throw new Exception("Unexpected response from PayPal: $res");
            }
        }
        fclose ($fp);  //close file pointer 
        if ($response == 'verified')
            return true;
        else
            return false;
    }

    private function getURL() {
        if ($this->sandbox) 
            return self::SANDBOX_URL;
        else                
            return self::PAYPAL_URL;
    }   
}

$paypal = new Paypal(true);
try {
    if ($paypal->receiveData()) {
        // success
    }
    else {
        // fail
    }
}
catch (Exception $e) {
    // exception
    echo 'Exception: ',  $e->getMessage(), "\n";
}
5

5 Answers

4
votes

There seems to be a number of issues with your script. For example, $data is not initialized so your posting nothing. But the reason you are getting 400 error is becase $header .= "Host: ssl://www.sandbox.paypal.com\r\n";

should be

$header .= "Host: www.sandbox.paypal.com\r\n";

2
votes

I maintain some current IPN programs and it looks to me like your method of reading data is not likely to obtain the full response from PayPal. TCP data are sent in packets, and PayPal response are not sent as a single buffer, so it is actually very unlikely that a single read would actually contain as many bytes as possible up to the 1,024 bytes requested.

In order to implement in your own code, you would have to loop and append to a variable what you read until reaching the EOF.

Also, you can look at this PHP example code here:

https://www.x.com/developers/PayPal/documentation-tools/code-sample/216623

I think that I can see your mistake now. Your fgets statement reads only one line of the response at a time because it stops reading when length is reached or when a newline is reached, whichever comes first. The line you are looking for is in the body of Pay Pal's response, not in the first few lines (header, etc.). Yet your else statement is included within the loop. Therefore your else statement is always executed once the first line of the header is received from PayPal.

I suggest two changes. Move the failure code after the loop, because failure isn't actually known for sure until the EOF has been reached.

The other change I suggest is to use strpos instead of strcmp. Although the paypal response body contains just the single word verified, there is a good chance that CR or LF or both will be part of that line which fgets reads. And so strcmp will fail. You can use the condition strpos ($res, "VERIFIED") !== false in place of your existing condition (and the same change for checking if INVALID was received.

This is some possible code that might work (I can't test this code in your situation, and code that is untested can never be sure of working). Please just take this code as a suggestion, not as a literal answer:

   // Post request back to PayPal for validation
    fputs ($fp, $header . $data);
    while (!feof($fp)) {                     // While not EOF
        $res = fgets ($fp, 1024);              // Get the acknowledgement response
        if (strpos ($res, "VERIFIED") !== false)  {  // Response is VERIFIED
            $response = 'verified';
            // Notification protocol is complete, OK to process notification contents

            // Possible processing steps for a payment might include the following:

            // Check that the payment_status is Completed
            // Check that receiver_email is your Primary PayPal email
            // Check that payment_amount/payment_currency are correct
            // Process payment
            break;
        }
        else if (strpos ($res, "INVALID") !== false)  { // Response is INVALID
            $response = 'invalid';
            break;
        } 
    }
    fclose ($fp);  //close file pointer 
    if ($response == 'verified')
        return true;
    else if ($response == 'invalid') {
            return false;
        }
    else { // no else statement is actually needed here,
           // since the previous conditions both return
        throw new Exception("Unexpected response from PayPal: $res");
        return false;
    }
1
votes

your If statement is correct , all you need to do is to print the value you are checking againt "VERIFIED" for heading/trailing charecters : copy something like this to inside your else block :

    var_dump($res) ; die;

so you can see what you get :)

0
votes

Socket-level HTTP communication makes your code very error-prone. I would recommend you to use an HTTP client library like cURL that will handle the request/response cycle for you, follow potential redirects and read/write headers/trailers.

0
votes

Please clean your browser cache or restart the browser.

This will definitely solve your issue of bad request.