Using PayPal's IPN with Adaptive/Parallel Payments

Introduction

Often while working with PayPal's API, you would want some sort of action to take place once a payment is complete. This is where PayPal's Instant Payment Notification service comes into play. Once a payment is complete, PayPal will send your server a message, your server will acknowledge the message, and the payment will be confirmed. Sounds easy, right?

Not really

Due to the fact we are leaving an open point on our server for payment verification, PayPal has decided it has to be quite strict to ensure people don't go about sending bogus calls to your handler in an attempt to fake payments. Once your IPN handler receives the message, your server must send the exact message back with one tiny addition. PayPal will then verify your message is also legitimate before sending you back either VERIFIED or INVALID.

Prefer Pictures?

IPN diagram

Let's jump into the implementation. PayPal gives us a listener we can use with php, and it works fine for most cases, but will not work for Adaptive Payments and perhaps others. Don't believe me? Go try it. Chances are you've found this article because you're looking for the solution, so I don't need to prove it isn't working.

The important bit of the handler is as follows.

foreach ($_POST as $key => $value) {  
if($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1){  
	$value = urlencode(stripslashes($value)); 
} 
else { 
	$value = urlencode($value); 
} 
$req .= "&$key=$value"; 

}

You can see this simply takes each of the $_POST variables and adds them into an encoded string. This works for most simple payment methods on PayPal, but as soon as we jump to Adaptive or Parallel Payments, PayPal will return INVALID on all messages sent for verification.

The Problem

Check out this table. You'll notice there are some variables such as transaction[n].id that seem to represent an array of objects. In the code snippet above, urlencode is called on each $value. The problem is that urlencode will only accept strings (source) and transaction[n] is an array. Since the array won't get encoded, we end up with a malformed message that PayPal thinks is fraudulent and will return INVALID after we send it.

The Solution

Forget about trying to process the $_POST variables with a loop, actually- we'll be ignoring $_POST all together for verification purposes. Instead, we'll be taking exactly what our server receives, put the cmd=_notify-validate before the message, and use that to validate our IPN message. It's actually straight-forward.

$req = 'cmd=_notify-validate&'.file_get_contents("php://input");

After that, it's just a matter of replacing the request in PayPal's example handler. Using this method will generate a VERIFIED message on all payment types.

Finishing Up

Now that we have a VERIFIED IPN message, we can forget about our $req string and start verification on our end. Checking the payment_status is essential and can be easily accessed through the $_POST array ($_POST['status']).

PayPal can be really troublesome.