ipn/ipn_handler.class.php
<?php
/**
* IPN Handler.
*
* @author Torleif Berger
* @link http://www.geekality.net/?p=1420
* @license http://creativecommons.org/licenses/by/3.0/
*
* @link https://cms.paypal.com/cms_content/US/en_US/files/developer/IPNGuide.pdf
* @link http://curl.haxx.se/docs/caextract.html
*/
abstract class IPN_Handler
{
const paypal_url = 'https://www.paypal.com/cgi-bin/webscr';
const paypal_sandbox_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
const charset = 'utf-8';
/**
* Validates and santizes IPN data from PayPal.
*
* @return mixed returns the processed data or FALSE if validation failed.
*/
public function process(array $post_data)
{
// Validate
$valid = self::validate($post_data);
if($valid !== TRUE)
return FALSE;
// Sanitize
return self::sanitize($post_data);
}
/**
* Validates IPN data.
*
* [!!] Verification will fail if the data has been alterend in *any* way.
*
* @param array raw ipn post data from paypal
* @return mixed returns the reply on error; otherwise `TRUE`
*/
protected static function validate(array $ipn_post_data)
{
// Choose url
if(array_key_exists('test_ipn', $ipn_post_data) && 1 === (int) $ipn_post_data['test_ipn'])
$url = self::paypal_sandbox_url;
else
$url = self::paypal_url;
// Set up request to PayPal
$request = curl_init();
curl_setopt_array($request, array
(
CURLOPT_URL => $url,
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => http_build_query(array('cmd' => '_notify-validate') + $ipn_post_data),
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_HEADER => FALSE,
CURLOPT_SSL_VERIFYPEER => TRUE,
CURLOPT_CAINFO => 'cacert.pem',
));
// Execute request and get response and status code
$response = curl_exec($request);
$status = curl_getinfo($request, CURLINFO_HTTP_CODE);
// Close connection
curl_close($request);
if($status == 200 && $response == 'VERIFIED')
return TRUE;
return $response;
}
protected static function sanitize(array $ipn_data)
{
// Just return empty array if empty
if( ! $ipn_data)
return array();
// Fix encoding
self::fix_encoding($ipn_data);
// Sort keys (easier to debug)
ksort($ipn_data);
return $ipn_data;
}
protected static function fix_encoding( & $ipn_data)
{
// If charset is specified
if(array_key_exists('charset', $ipn_data) && ($charset = $ipn_data['charset']))
{
// Ignore if same as our default
if($charset == self::charset)
return;
// Otherwise convert all the values
foreach($ipn_data as $key => &$value)
{
$value = mb_convert_encoding($value, self::charset, $charset);
}
// And store the charset values for future reference
$ipn_data['charset'] = self::charset;
$ipn_data['charset_original'] = $charset;
}
}
}