This page has been designed specifically for the printed screen. It may look different than the page you were viewing on the web.
Please recycle it when you're done reading.

The URI for this page is { http://blog.scrobbld.com }

PayPal Instant Payment Notification and PHP Posted on February 14th

PayPal Instant Payment Notification (IPN for short) has been covered elsewhere on the internet, from good implementations to fairly mediocre ones. But when you’re deciding what method to use for your new website, to enable a bit of easy ecommerce, the choice can be less obvious. More specifically, you’ve decided against using a prefab shopping cart (of which there are many, and which won’t be covered here). Also, this series of posts will assume a small amount of programming knowledge, preferably with PHP.

I won’t here plug PayPal for being the best option for every situation. Certainly the interface is slow (thanks to the SSL wrapper), the one on one support is limited, documentation can be sketchy, and generally the whole experience feels like a bit of a venture back to the 1990s when everything was happy, unpolished and just slightly naive. On the other hand, the community is extremely large, it is used by millions of people worldwide, and your customers can pay with their credit card without creating a PayPal account (unlike Google checkout). Added to this is the benefit of an easy setup, and a reasonably competent PayPal shopping cart (e.g. add to cart buttons, and buy now buttons). But this is also going off on a tangent.

Now, to decide upon whether to use IPN or not. At Scrobbld, we’ve been receiving notifications for the best part of a year without any of the dreaded service interruptions. IPN is what is known in the business as a “push” (as opposed to “pull”) technology, so we can say that you (or your server) is notified when a payment is made, so PayPal “pushes” the information to you, with no action required on your part. Alternatively, “pulling” is where your server requests the information from the central server at specific intervals. It is primarily used for validating that a customer has bought something, and that the transaction is not fraudulent, so with IPN you can update your own, local, database. If you don’t really need that, then check out Payment Data Transfer (PDT) which sends a limited subset of the order information back in a URL.

Referring to a couple of sources, mainly http://www.paypal.com/ipn, as well as http://www.micahcarrick.com/, who wrote a PHP class back in 2005, the technology itself has changed little, and I will go through a quick implementation of a simpler class than micah’s below (though uses the validate function from micahcarrick.com).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class ipn {
	//sandbox:
	//private $paypal_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
	//live site:
	private $paypal_url = 'https://www.paypal.com/cgi-bin/webscr';
	private $data = null;
	public function __construct() {
	    $this->data = new stdClass;
	}
	public function isa_dispute() {
	    //is it some sort of dispute.
	    return $this->data->txn_type == "new_case";
	}	
	public function validate() {
	    // parse the paypal URL
	    $response 	= "";
	    $url_parsed 	= parse_url($this->paypal_url); 
	    // generate the post string from the _POST vars aswell as load the
	    // _POST vars into an arry so we can play with them from the calling
	    // script.
	    $post_string 	= '';    
	    foreach ($_POST as $field=>$value) { 		
	        $this->data->$field = $value;
	        $post_string .= $field.'='.urlencode(stripslashes($value)).'&'; 
	    }
	    $post_string.="cmd=_notify-validate"; // append ipn command
	    // open the connection to paypal
	    $fp = fsockopen($url_parsed['host'],"80",$err_num,$err_str,10); 
	    if(!$fp) {          
	        // could not open the connection.  If logging is on, the error message
	        // will be in the log.    
	        return false;         
	    } else {  
	        // Post the data back to paypal
	        fputs($fp, "POST $url_parsed[path] HTTP/1.1\r\n"); 
	        fputs($fp, "Host: $url_parsed[host]\r\n"); 
	        fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n"); 
	        fputs($fp, "Content-length: ".strlen($post_string)."\r\n"); 
	        fputs($fp, "Connection: close\r\n\r\n"); 
	        fputs($fp, $post_string . "\r\n\r\n"); 
	        // loop through the response from the server and append to variable
	        while(!feof($fp)) { 
		    $response .= fgets($fp, 1024); 
	        } 
	        fclose($fp); // close connection
	    }
	    if (eregi("VERIFIED", $response)) {  
	        // Valid IPN transaction.
	        return $this->data;        
	    } else {  
	        // Invalid IPN transaction.  Check the log for details.
	        return false;         
	    }	      
	}
}

If you want to use Curl instead, which is possibly a more efficient way of doing things, you can use the following Curl headers:

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $this->paypal_url);
    //curl_setopt($ch, CURLOPT_VERBOSE, 1);
    //keep the peer and server verification on, recommended 
    //(can switch off if getting errors, turn to false)
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, TRUE);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, TRUE);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string);	
    $response = curl_exec($ch);
    if (curl_errno($ch)) {
        die("Curl Error: " . curl_errno($ch) . ": " . curl_error($ch));
    } 
    curl_close($ch);

So to use this code you have to set up a couple of things. First off, you should login to PayPal and alter your IPN settings to the relevant URL (under Profile > Instant Payment Notification Preferences), we’ll say http://www.example.com/ipn.php. Subsequently, you’ll need to put the following into your ipn.php file on your server (shared or otherwise).

<?php
include "ipn.class.php";
$ipn = new ipn();
if($data = $ipn->validate()) {
	//do your stuff here.
}
else {
	//not validated.
}
?>

Debugging can be a pain with this however, so what I would recommend doing is writing either to a file on your webserver, at numerous locations throughout the script, or sending yourself an email with any variables you might be interested in. Alternatively, you can use set_error_handler() to write any errors in the script to a separate file.

The tantalising bit here though is under //do your stuff here, which is a difficult topic to cover due to so many possible applications. What do you actually do with the information once you have validated it? You should definitely refer to the PayPal reference on IPN, which should be read by anyone utilising IPN. Importantly however, If a malicious user is aware of the exact information from an old transaction, and they know the IPN URL (http://www.example.com/ipn.php) on your server, they could just post this information to you and try to impersonate an old transaction. If you are dispatching tangible items therefore, this could become a bit of an expensive problem. So, you need to check that the transaction ID txn_id has not be stored in your database before, with one exception: The transaction ID does not change between a Pending transaction and a Completed transaction, so you should make sure that the payment_status is in fact “Completed” and that there was previously a “Pending” transaction with the same txn_id.

To conclude, regardless of your application, I would never recommend throwing this information away, because it can be invaluable for future reference, marketing and identifying repeat customers. Though it’s not much information, it is your information, and should be valued highly.

Trackback URL

Some Responses to “PayPal Instant Payment Notification and PHP” :

  1. You don’t really mean to include ipn.php in a file named ipn.php do you?
    ;-)

    Commented Jeff on February 25th, 2009.
  2. good point — will change that.

    Commented admin on April 2nd, 2009.
  3. [...] that’s about all there is to it. If you want the API to behave a little bit like IPN, e.g. a push methodology as opposed to a pull one, you can query the API whenever you receive an [...]

    Commented The PayPal API and PHP :: Scrobbld Blog on May 1st, 2009.
Leave your own comments about this post: