As discussed in part 1, I recently automated my PayPal-based store, using PayPal's Instant Payment Notification (IPN) service.
For those interested in the technical details, perhaps implementing this for your own store, here's my code. This is written in PHP, but other languages can be used too.
Firstly, a couple of utility functions. For getPayPalShoppingCartValue(), given the PayPal transaction data, a key, and the (zero-based) index, this returns the corresponding value. Tries the key with the index appended, with an underscore and the index appended, or by itself; the documentation is somewhat inconsistent on how it is applied, though I think an underscore is usually used. All this code should still work fine if you aren't using the shopping cart, too.
function getPayPalShoppingCartValue(&$transaction, $key, $index = 0)
{
$value = $transaction[$key . ($index + 1)];
if (!$value)
$value = $transaction[$key . '_' . ($index + 1)];
if (!$value)
$value = $transaction[$key];
return $value;
}
Next, the exitWithError() function emails me the details of an error, for diagnostic purposes, then exits the script. You can optionally pass an array and it will be included in the email. It also outputs the error (normally wouldn't be seen). It calls another existing function of mine (not provided here) that returns the array as an ASCII property list; there are other ways to output it too. Of course, you should replace the mycompany.com email addresses with your own.
function exitWithError($parser, $error = '', $array = null)
{
$body = "An error occurred with the $parser parser:\n\n$error\n\n";
if ($array)
$body .= arrayToASCIIPropertyList($array);
$headers = "From: MyCompany <info@mycompany.com>\r\n";
$headers .= "X-Mailer: PHP/" . phpversion() . "\r\n";
mail("info@mycompany.com", "Store error: $error", $body, $headers);
header("Content-Type: text/text");
echo("$parser error: $error");
exit();
}
On to the main code. I actually have this in a function in my code, as it is just one processor function among others, but if you only have one, it can be at the top level of the script.
function handlePayPal()
{
// Copy to a local variable for convenience, and since the post back to PayPal might wipe it:
$transaction = $_POST;
// Test mode is activated by passing test=1 to my PayPal Store. It then uses PayPal's sandbox site instead:
$testMode = $transaction['test_ipn'] == 1;
if ($testMode)
{
mail("MyCompany <sales@mycompany.com>", "PayPal IPN starting", arrayToASCIIPropertyList($transaction), "From: MyCompany <sales@mycompany.com>\r\n");
$paypalDomain = 'www.sandbox.paypal.com';
$receiverEmail = 'paypal_sandbox_biz@mycompany.com';
}
else
{
$paypalDomain = 'www.paypal.com';
$receiverEmail = 'paypal@mycompany.com';
}
The above sets up things based on whether the purchase was via the PayPal sandbox or your live store. The sandbox is a great way to create fake customer and seller accounts for testing without spending real money. You can initiate this test mode by changing the action on your form from <https://www.paypal.com/cgi-bin/webscr> to <https://www.sandbox.paypal.com/cgi-bin/webscr>. You also need to change the form's "business" field to the sandbox receiver email address.
Next up, we post the received data back to PayPal, so they can confirm that they actually sent it. We construct and post urlencoded form data to their server, then fetch the response. If it's VERIFIED, we're good:
// Read the post from PayPal system and add 'cmd':
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value)
{
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
// Post back to PayPal system to validate:
$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Host: $paypalDomain:80\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen($paypalDomain, 80, $errno, $errstr, 30);
if (!$fp)
exitWithError('PayPal', 'Unable to connect to the PayPal server.', $transaction);
fputs($fp, $header . $req);
$verified = false;
// Read the response:
while (!feof($fp))
{
$line = fgets($fp, 1024);
if (strcmp($line, "VERIFIED") == 0)
$verified = true;
}
fclose($fp);
// Ensure PayPal verified the post:
if (!$verified)
exitWithError('PayPal', 'Received a transaction that PayPal did not verify as valid.', $transaction);
We then do some further checks, to ensure the payment went to the correct address, and the transaction has an acceptable status. I wrote this before realizing that pending transactions probably don't reach this point anyway (I think PayPal holds off notifying till they are completed), but it doesn't hurt to leave the pending logic in here:
// Ensure the payment went to me:
if ($transaction['receiver_email'] != $receiverEmail)
exitWithError('PayPal', 'The receiver email was not correct.', $transaction);
$transStatus = $transaction['payment_status'];
$isPending = ($transStatus == 'Pending');
if (!$isPending && $transStatus != 'Completed')
exitWithError('PayPal', "The transaction status was $transStatus.", $transaction);
So now we've got a valid transaction, so can begin to process it. Continue to part 3 with the remainder of the code!
Recent comments
9 hours 49 min ago
2 days 17 hours ago
3 days 23 hours ago
5 days 20 hours ago
1 week 14 hours ago
1 week 22 hours ago
1 week 3 days ago
1 week 4 days ago
1 week 4 days ago
1 week 6 days ago