PayPal, Adaptive Payments, and IPN Part Two

In my previous post I detailed how to generate a VERIFIED response from PayPal's IPN. The beast was far from conquered though.

The Problem

Remember when I wrote about how the separate transactions of the payment are stored in the payments[n] array? Remember how members of that array had elements like transaction[n].id? Turns out PHP has a really hard time parsing these.

You'll actually end up with a heavily-malformed transaction array similar to the one below.

...
[transaction] => Array (
	[1] => 9L195623HB0883442
	[0] => USD 24.65
)
...

The Solution

Unfortunately, there isn't really an easy solution for this issue at the time of writing this article. PHP usually does a really good job at parsing $_POST variables but struggles when array members also have elements like transaction[n].id.

We will have to loop through each member of the raw post data and build a clean array manually. donut2d from the PayPal developers community wrote a nice function that you can use to accomplish just this. Check it out below.

public function decodePayPalIPN($raw_post) {
	if (empty($raw_post)) {
		return array();
	} // else:
	$post = array();
	$pairs = explode('&', $raw_post);
	foreach ($pairs as $pair) {
		list($key, $value) = explode('=', $pair, 2);
		$key = urldecode($key);
		$value = urldecode($value);
		// This is look for a key as simple as 'return_url' or as complex as 'somekey[x].property'
		preg_match('/(\w+)(?:\[(\d+)\])?(?:\.(\w+))?/', $key, $key_parts);
		switch (count($key_parts)) {
			case 4:
				// Original key format: somekey[x].property
				// Converting to $post[somekey][x][property]
				if (!isset($post[$key_parts[1]])) {
					$post[$key_parts[1]] = array($key_parts[2] => array($key_parts[3] => $value));
				} else if (!isset($post[$key_parts[1]][$key_parts[2]])) {
					$post[$key_parts[1]][$key_parts[2]] = array($key_parts[3] => $value);
				} else {
					$post[$key_parts[1]][$key_parts[2]][$key_parts[3]] = $value;
				}
				break;
			case 3:
				// Original key format: somekey[x]
				// Converting to $post[somkey][x] 
				if (!isset($post[$key_parts[1]])) {
					$post[$key_parts[1]] = array();
				}
				$post[$key_parts[1]][$key_parts[2]] = $value;
				break;
			default:
				// No special format
				$post[$key] = $value;
				break;
		}//switch
	}//foreach

	return $post;
}//decodePayPalIPN()

When using this function, make sure you pass the raw post data. You can get the raw post data by capturing PHP's input stream.

$raw_post = file_get_contents("php://input");

This function will return a properly formed array that you can actually work with! Take a look at the transaction array it returns.

[transaction] => Array (
	[0] => Array (
		[is_primary_receiver] => false
		[id_for_sender_txn] => 0BK80094E2676460X
		[receiver] => dan_1337006952_biz@enjoysmile.com
		[amount] => CAD 1.69
		[status] => Completed
		[id] => 3DD602158W667893G
		[status_for_sender_txn] => Completed
		[paymentType] => SERVICE
		[pending_reason] => NONE
	)

	[1] => Array (
		[paymentType] => SERVICE
		[id_for_sender_txn] => 7FS82605EE765862M
		[is_primary_receiver] => false
		[status_for_sender_txn] => Completed
		[receiver] => cause_1338098263_biz@enjoysmile.com
		[amount] => CAD 3.31
		[pending_reason] => NONE
		[id] => 3BG17092AR760020V
		[status] => Completed
	)
)

Finished... Right?

I can't answer this one for sure, but so far it seems to be the end of my PayPal headaches. I don't know why it has become so difficult to work with PayPal's IPN in PHP. Maybe PayPal's language of choice is something else, or just expects developers to really dig deep to find out how their systems work. Perhaps their APIs are changing so frequently that they decide not to document things so they can avoid updating them all the time.

Another romantic night spent with PayPal. Tonight she teased me with strangely formed variables, I couldn't quite understand what she was saying. But you know... spend a few hours listening to somebody and you will understand them much better. PayPal, I think it's best we see other people. It's you, not me. Don't make this harder than it is. Good night.