Hack 80 Process PayPal Payments Automatically


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
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).
|
#!/usr/bin/perl
$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';
$req->content_type('application/x-www-form-urlencoded');
$req->content($query);
$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;
$count++;
}
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";
close(OUTFILE);
&SendOK(); [8]
sub SendOK() {
print "content-type: text/plain\n\nOK\n"; [9]
exit;
}
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";
close(MAIL);
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].