[ic] Paypal Instant Payment Notification IPN Scripting

Jeff Urban interchange-users@interchange.redhat.com
Tue Mar 19 11:33:00 2002


I'm integrating paypal instant payment notification into my interchange 
store.
I'm not experienced with scripting, so I'm going to seek some help and 
I've found two sources. Which looks like a better starting point? Any 
other suggestions?
Thanks,
Jeff Urban



• CHOICE ONE: from 
http://www.phpbuilder.com/forum/archives/1/2001/12/3/129749

Author: Randall Smith
Date: 2001-12-17 01:20:59
Subject: Complete Script for Paypal's IPN Service

Here is the script for using Paypal's Instant Payment Verification 
Service with PHP. It requires cURL for posting to a secure server. My 
setup is on Win2K/Apache.

To setup cURL for Win2K/Apache.

It's easy to setup. Everything you need is included in the PHP 4.10 
distribution. To enable cURL, just uncomment the cURL and openssl 
extensions in you php.ini file. You will then need to make sure that 
your extension path (defined in php.ini points to your extensions folder 
(php/extensions) that contains php_openssl.dll and php_curl.dll). You 
will also need to copy ssleay32.dll and libeay32.dll from php/dlls/ to 
somewhere in your path. Don't forget to restart apache. Now you should 
be able to rock.

Here is the Paypal IPN code:

Note: I'm not a programmer by profession, so don't make fun of my 
script. It works.

/* This script communicates with Paypal's Instant Payment Notification
System. It uses cURL and PHP. The original cURL function is by Aaron Web.
I have taken out the username/password attributes*/

function execute_requests($server, $path, $qstring) {

/* Set up cURL with curl_init. $ch is for Curl Handle. */
$ch = curl_init();

/* Set the type of request to POST */
curl_setopt($ch, CURLOPT_POST, 1);

/* Suppress header in cURL's output */
curl_setopt($ch, CURLOPT_HEADER, 0);

/* Make curl return the results to a variable instead of STDOUT */
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);

/* Set maximum time for cURL request */
curl_setopt($ch, CURLOPT_TIMEOUT, 5);

/* Construct request */
$requestedurl = "$server" . "/$path";

/* Set URL for request */
curl_setopt($ch, CURLOPT_URL, "$requestedurl");

/* Set query string for post request */
curl_setopt($ch, CURLOPT_POSTFIELDS, "$qstring");

/* Execute request */
$result = curl_exec($ch);

if(curl_error($ch)) {
echo "Error retrieving $requestedurl ";
exit;
}

curl_close ($ch);

return $result;
}

// Parse incoming form data and prepare it for posing to Paypal.

foreach ($HTTP_POST_VARS as $key=>$value) $qstring .= "$key=$value&";
$qstring .= "cmd=_notify-validate";


// Set variables for the cURL function.

//$server = "http://localhost";
//$path = "/mysql_host/paypal/dummy_post.php";
$server = "https://www.paypal.com";
$path = "/cgi-bin/webscr";

// This output contains the response from Paypal.

$output = execute_requests($server, $path, $qstring);

/* Probably not necessary, but I searched the response for the text 
"VERIFIED"
or "INVALID"*/

$verified = "ERROR";
if (ereg("VERIFIED",$output))
{
$verified = "VERIFIED";
}
if (ereg("INVALID",$output))
{
$verified = "INVALID";
}

/* The hard part is done. Now you can do what you like with the data.*/





• CHOICE TWO: from http://gotany.com/cgi/paypalipn.html

#!/usr/local/bin/perl -w
#
# paypal Instant Payment Verification script (paypalipn.cgi)
# Copyright (C) 2002  Andrew Moore <amoore@gotany.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# for a more robust and complete version of this program, see
# http://gotany.com

use strict;
use CGI;
use LWP;
use LWP::UserAgent;
use Data::Dumper;
use DBI;
use URI::Escape;

my $q = new CGI;
print $q->header( -type=>'text/html' );
print $q->start_html();

my $DSN = 'DBI:mysql:paypalipn;mysql_socket=/tmp/mysql.sock';
my $DB_AUTH = 'user:passwd';

my $dbh = DBI->connect($DSN, (split ':', $DB_AUTH),
                        { RaiseError => 1 });


my @columns = qw(notificationid receiver_email item_name item_number 
quantity invoice custom payment_status pending_reason payment_date 
payment_gross payment_fee txn_id txn_type first_name last_name 
address_street address_city address_state address_zip address_country 
address_status payer_email payer_status payment_type notify_version 
verify_sign status );

print "inserting...<br>\n";
my $insertid = insert( $q );
print "insertid = $insertid <br>\n";

print "posting...<br>\n";
my $content = postit( $q );

print "updating with:<br>\n";
print $q->pre( $content );
print "<br>\n\n";

if ( $content =~ /VERIFIED/ ) {
   my @bv = ( "VERIFIED", $insertid );
   $dbh->do( qq(update notification set status = ? where 
notificationid = ? ),
	    undef, @bv) or die $dbh->errstr;
} elsif ( $content =~ /INVALID/ ) {
   print "setting it to invalid<br>\n";
   my @bv = ( "INVALID", $insertid );
   $dbh->do( qq(update notification set status = ? where 
notificationid = ? ),
	    undef, @bv) or die $dbh->errstr;
} else {
   my @bv = ( undef, $insertid );
   $dbh->do( qq(update notification set status = ? where 
notificationid = ? ),
	    undef, @bv) or die $dbh->errstr;
}
print "done.<br>\n";

$dbh->commit;
$dbh->disconnect;


sub insert {
   my $q = shift;

   my $statement = qq(insert into notification ) . "( " . join( ", ", 
@columns ) . " ) " . qq( values ) . " ( ". join( ", ", map { "?" } 
@columns ) . " ) ";

   my @bind_values = map { $q->param( $_ ) ? $q->param( $_ ) : ""  } 
@columns;

   my $rv = $dbh->do($statement, undef, @bind_values) or die $dbh->errstr;
   # XXXXX should check for error, methinks.

   # XXXXX this makes it mysql specific.
   $insertid = $dbh->{'mysql_insertid'};

   return( $insertid );
}




sub postit {
   # pass me in the CGI object;
   my $q = shift;

   my $ua = new LWP::UserAgent;

   my %vars = $q->Vars();
   $vars{'cmd'} = '_notify-validate';
   my $content = join( '&', map{ $_ . "=" . uri_escape( $vars{$_} )} keys 
%vars );

   #my $req = HTTP::Request->new( POST => 
'http://www.mooresystems.com/~amoore/mailme.cgi', HTTP::Headers->new(), 
\$content );
   my $req = HTTP::Request->new( POST => 'https://www.paypal.com/cgi-
bin/webscr', HTTP::Headers->new(), \$content );

   # is this really necessary?
   $req->content_type('application/x-www-form-urlencoded');

   # Pass request to the user agent and get a response back
   my $res = $ua->request($req);

   # XXXX need to check for success here, then pass content back to check 
for VALID
   if ( $res->is_success() ) {
     return $res->content();
   } else {
     return 'FAILED';
   }
}


#  This sql should make the appropriate table in your MySQL database:
#  DROP TABLE IF EXISTS notification;
#  CREATE TABLE notification (
#    notificationid int(10) NOT NULL auto_increment,
#    receiver_email char(64) NOT NULL default '',
#    item_name char(64) default NULL,
#    item_number int(10) default NULL,
#    quantity int(10) default NULL,
#    invoice char(64) default NULL,
#    custom char(64) default NULL,
#    payment_status enum('Completed','Pending','Failed','Denied') 
default NULL,
#    pending_reason 
enum('echeck','intl','verify','address','upgrade','unilateral','other') 
default NULL,
#    payment_date char(50) default NULL,
#    payment_gross decimal(9,2) default NULL,
#    payment_fee decimal(9,2) default NULL,
#    txn_id char(50) default NULL,
#    txn_type enum('web_accept','cart','send_money') default NULL,
#    first_name char(64) default NULL,
#    last_name char(64) default NULL,
#    address_street char(64) default NULL,
#    address_city char(64) default NULL,
#    address_state char(64) default NULL,
#    address_zip char(64) default NULL,
#    address_country char(64) default NULL,
#    address_status enum('confirmed','unconfirmed') default NULL,
#    payer_email char(64) default NULL,
#    payer_status 
enum('verified','unverified','intl_verified','intl_unverified') default 
NULL,
#    payment_type enum('echeck','instant') default NULL,
#    notify_version enum('1.3') default NULL,
#    verify_sign char(64) default NULL,
#    status enum('VERIFID','INVALID') default NULL,
#    PRIMARY KEY  (notificationid)
#  ) TYPE=MyISAM;