I'm working on creating an Instant Payment Notification page to work with Paypal's payment gateway. This is from their documentation in PP_OrderManagement_IntegrationGuide.pdf:

NOTE:You can implement IPN without SSL, but PayPal recommends against doing so.
1. Your POST must be sent to https://www.paypal.com/cgi-bin/webscr.
.
.
.

AND YET....the sample code they provide at
http://www.paypal.com/cgi-bin/webscr?cmd=p/pdn/ipn-codesamples-pop-outside#php

has this:

$fp = fsockopen ('www.paypal.com', 80, $errno, $errstr, 30);

For anyone new to paypal work, this is pretty much typical.

QUESTIONS:
1) If I want to POST to paypal securely using fsockopen, is it possible? Can I do so by changing that to this:

$fp = fsockopen ('ssl://www.sandbox.paypal.com/cgi-bin/webscr', 43, $errno, $errstr, 30);

2) should I use cURL or something?

3) the complete lack of headers seems rather unprofessional. I saw this example in the php documentation and was considering adapting it but I'm not sure if the headers might work for this application or not. The connection:close header seems oddly placed.

<?php

$host = gethostbyaddr($_SERVER['REMOTE_ADDR']);

# working vars
$host = 'www.example.com';
$service_uri = '/cgi-bin/processACT';
$vars ='code=22&act=TEST';

# compose HTTP request header
$header = "Host: $host\r\n";
$header .= "User-Agent: PHP Script\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: ".strlen($vars)."\r\n";
$header .= "Connection: close\r\n\r\n";

$fp = pfsockopen("ssl://".$host, 443, $errno, $errstr);
if (!$fp) {
   echo "$errstr ($errno)<br/>\n";
   echo $fp;
} else {
   fputs($fp, "POST $service_uri  HTTP/1.1\r\n");
   fputs($fp, $header.$vars);
   fwrite($fp, $out);
   while (!feof($fp)) {
       echo fgets($fp, 128);
   }
   fclose($fp);
}

?> 

    I think you are confusing fsockopen() with fopen().

    If you provide a HTTPS URL, you should use fopen(), not fsockopen().

    I generally recommend against writing your own HTTP implementation, so I'd use fopen("https://whatever...

    In fact in our implementation of paypal IPN, that is exactly what I did (albeit via various subroutines as I reused code from elsewhere in the application).

    Mark

      I'm not confusing them...they're confusing me 😉

      You're always there for me mark.

      All of the IPN examples i've found use fsockopen. I would wager fopen is a higher level protocol and preferable by me for that reason. BUT i haven't seen any examples using fopen to POST to a url which is what i must do for IPN. if you are using fopen(), how do you effect a POST operation to a remote url? which mode do you use?

        Here's one I made earlier- a function to perform a form-encoded HTTP(S) POST.

        /*
         * Sends a form-encoded HTTP(S) POST to a given URL.
         * Returns a string containing the response.
         *
         * If response code is not 200, throws an error msg.
         */
        function SendHttpPost($url, & $fields)
        {
        	$urlencoded = '';
        	foreach (array_keys($fields) as $key) {
        		$urlencoded = $urlencoded . "$key=" . rawurlencode($fields[$key]);
        		$urlencoded .= '&';
        	}
        	$context_options = array(
        		'http'=>array(
        		'method'=>"POST",
        		'header'=>"Accept-language: en\r\n" .
        			"Content-length: " . strlen($urlencoded) . "\r\n" .
        			"Content-type:application/x-www-form-urlencoded\r\n",
        		'content'=> $urlencoded)
        	);
        	// Make sure the HTTPS options are the same as HTTP
        	$context_options['https'] = $context_options['http'];
        	$context = stream_context_create($context_options);
        	$fp = fopen($url, 'r', false, $context);
        	// Get the response...
        	$response = '';
        	/* For HTTPS, IIS closes the SSL socket improperly hence
        	 * generating a warning. We want to suppress this, as some
        	 * servers will be doing this.
        	 *
        	 * see http://uk.php.net/manual/en/wrappers.http.php
        	 */
        	$old_error_reporting = error_reporting();
        	error_reporting($old_error_reporting & ~E_WARNING);
        	// Get the headers
        	$metadata = stream_get_meta_data($fp);
        	foreach($metadata['wrapper_data'] as $responseheader) {
        		// Look for the status header...
        		if (substr($responseheader,0,5) == 'HTTP/') {
        			$bits = split(' ', $responseheader);
        			$httpstatus = (int) $bits[1];
        			$httpstatusmessage = $responseheader;
        		}
        	}
        	// There is no need to check the HTTP status, as fopen wrapper
        	// that for us anyway.
        	// Read the the response data
        	while (!feof($fp)) {
        		$response .= fread($fp, 8192);
        	}
        	fclose($fp);
        	error_reporting($old_error_reporting);
        	return $response;
        }
        

        You'll see that this is actually fairly cludgy in some ways. If we were using PHP5 (which we were not when this was originally written), the "while (! feof" loop could be replaced by a call to stream_get_contents.

        Note also that the above is fairly oblivious to character encoding; it will probably only work when the local and remote encoding is the same.

        It also assumes that the key-names in $fields do not require url encodign, e.g. are alphanumeric (they typically are in most server-server POST situations including Paypal)

        Mark

          cool. thanks.

          Will there be any requirements for my server to do https such as having openssl installed or anything?

            sneakyimp wrote:

            Will there be any requirements for my server to do https such as having openssl installed or anything?

            Yes, of course- openssl must be enabled and allow_fopen_url should be turned on, but these things are easy to sort out in a production environment.

            Mark

              It appears to be working! Thanks MarkR. I'm wondering, though...how can I be sure the connection is in fact encrypted?

                If you're that worried that it might be somehow cunningly using http even though it's told to use https, install a network traffic monitor (e.g. tcpdump, or use Ethereal etc) and look at the connection during the callback.

                Also I think you'll find if you examine the stream metadata in $metadata, it should contain SSL certificate information, which it couldn't get from a HTTP connection.

                Mark

                  It looks like i would need root level access to use tcpdump. Sadly, I'm on a shared hosting server for this project with NO command line access whatsoever. I'm pushing for a dedicated machine where i get root access.

                  Ethereal also appears to require that sort of access. I didn't download the documentation, but I did read around in there a bit.

                  $metadata? what is that?

                    sneakyimp wrote:

                    It looks like i would need root level access to use tcpdump. Sadly, I'm on a shared hosting server for this project with NO command line access whatsoever. I'm pushing for a dedicated machine where i get root access.

                    In which case, try it on your development server first. I had assumed you were doing that anyway.

                    $metadata? what is that?

                    A variable in my code above which stores the stream metadata from stream_get_meta_data

                    Mark

                      Write a Reply...