Hack 80 Process PayPal Payments Automatically


Use Instant Payment Notification to fulfill orders without human intervention.

When a bidder pays for an auction with PayPal or completes an order from your online store (see [Hack #79]), PayPal notifies you with a single email. Since the last thing any busy seller wants to do is deal with a bunch of emails, PayPal offers the free Instant Payment Notification (IPN) feature.

7.10.1 Setting Up IPN

The premise is pretty simple: as soon as a payment is received, PayPal contacts your server and submits all the details of the transaction to your script. Your script then processes and stores the data in whatever way you see fit.

To start using IPN, all you need to do is enable the feature and specify the URL of your script. Log into PayPal and go to My Account Profile Instant Payment Notification Preferences. Click Edit to change the current settings.

7.10.2 The Script

The following Perl script[1] does everything required to accept IPN notifications; all you need to do is modify the $workdir variable to reflect a valid path on your server.

[1] Portions based on sample code by PayPal and David W. Van Abel (www.perlsources.com).

This script requires the LWP::UserAgent Perl module by Alan E. Derhaag, available at search.cpan.org/perldoc?LWP::UserAgent, necessary to facilitate communication with the PayPal server. See [Hack #17] for installation instructions.

$workdir = "/usr/local/home";

read (STDIN, $query, $ENV{'CONTENT_LENGTH'});     [1]
$query .= '&cmd=_notify-validate';     [2]

use LWP::UserAgent;
$ua = new LWP::UserAgent;
$req = new HTTP::Request 'POST','http://www.paypal.com/cgi-bin/webscr';
$res = $ua->request($req);     [3]

@pairs = split(/&/, $query);     [4]
$count = 0;
foreach $pair (@pairs) {
  ($name, $value) = split(/=/, $pair);
  $value =~ tr/+/ /;
  $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
  $variable{$name} = $value;

if ($variable{'payment_status'} ne "Completed") { &SendOK(); }     [5]
if (-e "$workdir/$variable{'txn_id'}.txt") { &SendOK(); }     [6]

open(OUTFILE,">$workdir/$variable{'txn_id'}.txt");     [7]
  print OUTFILE "[ipn_data]\r\n";
  print OUTFILE "email=$variable{'payer_email'}\r\n";
  print OUTFILE "firstname=$variable{'first_name'}\r\n";
  print OUTFILE "lastname=$variable{'last_name'}\r\n";
  print OUTFILE "address1=$variable{'address_street'}\r\n";
  print OUTFILE "address2=$variable{'address_status'}\r\n";
  print OUTFILE "city=$variable{'address_city'}\r\n";
  print OUTFILE "state=$variable{'address_state'}\r\n";
  print OUTFILE "zip=$variable{'address_zip'}\r\n";
  print OUTFILE "country=$variable{'address_country'}\r\n";
  print OUTFILE "product=$variable{'item_name'}\r\n";
  print OUTFILE "quantity=$variable{'quantity'}\r\n";
  print OUTFILE "total=$variable{'payment_gross'}\r\n";
  print OUTFILE "custom1=$variable{'option_selection1'}\r\n";
  print OUTFILE "custom2=$variable{'option_selection2'}\r\n";
&SendOK();     [8]

sub SendOK() {
  print "content-type: text/plain\n\nOK\n";     [9]

Here's how it works. First, the data received from PayPal [1] is appended with an additional variable [2], and then sent back to PayPal for validation [3]. This will prevent an unscrupulous user from attempting to trick your server into thinking an order has been received. Immediately thereafter, PayPal returns the data, and the script parses it into separate variables [4].

Next, the script checks to see if the transaction status is "Completed" [5] and if the transaction ID (txn_id) has been processed already [6]. If either test fails, the script quits by way of the SendOK function.

Finally, the script writes the pertinent information to a file [7], the name of which is simply the transaction ID. You'll undoubtedly want to change the fields that are recorded, the format of the file, and the path ($workdir) in which the files are stored.

To ensure that your server is properly notified of the transaction, PayPal sends the data repeatedly until it receives an OK signal [9] from your script. Furthermore, a single transaction can trigger a bunch of notifications, which is why you'll need to filter out incomplete or duplicate entries (lines [5] and [6]).

7.10.3 Hacking the Script

Ultimately, the only thing this script does is read the data received from IPN, validate it, and store it in a text file. The format of the file in this example is that of a Windows INI Configuration File, making it easy for a Windows application to read the data using the GetPrivateProfileString API call.

Naturally, you can store the data in whatever format you choose, including a database or even a specially formatted email. IPN is commonly used for automatic order fulfillment, wherein the server sends the customer a software registration key, subscription password, or other electronically transmitted product. But you can also use IPN to integrate incoming payments with your shipping system to produce prepaid shipping labels automatically (see [Hack #68]).

PayPal email notifications can sometimes be unreliable, either taking a while to show up or not showing up at all. You can remedy this by supplementing it with your own email notification, such as the following (placed immediately before line [8]):

open(MAIL,"|/usr/sbin/sendmail -t");
  print MAIL "To: $variable{'receiver_email'}\n";
  print MAIL "From: $variable{'payer_email'}\n";
  print MAIL "Reply-To: $variable{'payer_email'}\n";
  print MAIL "Subject: New IPN Order Received\n\n";
  print MAIL "Order $variable{'txn_id'} has been received via IPN\n";
  print MAIL "and stored in file $workdir/$variable{'txn_id'}.txt\n";

7.10.4 See Also

Full documentation, including a list of all supported fields, is available in the PayPal Instant Payment Notification Manual, available at www.paypal.com. Further examples and commercial versions of the script are available from a variety of sources, including www.ipnhosting.com, www.paypalipn.com, and www.perlsources.com.

IPN is sometimes used in conjunction with off-eBay web sites that accept PayPal payments, as described in [Hack #79].