From 995204eb5a8dbd5bc081cfacf3a5bb463df4d68a Mon Sep 17 00:00:00 2001 From: WhiteSpace-Strippers Date: Mon, 28 Jan 2013 03:08:21 +0100 Subject: [PATCH] WhiteSpace Strippers saved 1 bytes --- LICENSE | 40 ++-- README.md | 174 +++++++------- example/ipn.php | 90 ++++---- ipnlistener.php | 600 ++++++++++++++++++++++++------------------------ 4 files changed, 452 insertions(+), 452 deletions(-) diff --git a/LICENSE b/LICENSE index bad71e3..b3c829d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,27 +1,27 @@ Copyright (c) 2012, Micah Carrick All rights reserved. -Redistribution and use in source and binary forms, with or without modification, +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the name of Quixotix, LLC nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of Quixotix, LLC nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index a4afa9d..c2f2f6e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ PHP-PayPal-IPN ============== -A PayPal Instant Payment Notification (IPN) class for PHP 5. +A PayPal Instant Payment Notification (IPN) class for PHP 5. -Use the `IpnListener` class in your PHP IPN script to handle the encoding +Use the `IpnListener` class in your PHP IPN script to handle the encoding of POST data, post back to PayPal, and parsing of the response from PayPal. @@ -12,14 +12,14 @@ Features * Switch between live and sandbox by setting the `use_sandbox` property. * Supports both secure SSL and plain HTTP transactions by setting the `use_ssl` - property (SSL is recommended). + property (SSL is recommended). * Supports both cURL and fsockopen network libraries by setting the `use_curl` - property (cURL is recommended). + property (cURL is recommended). * Verifies an HTTP "200" response status code from the PayPal server. -* Get detailed plain text reports of the entire IPN using the `getTextReport()` - method for use in emails and logs to administrators. +* Get detailed plain text reports of the entire IPN using the `getTextReport()` + method for use in emails and logs to administrators. * Throws various exceptions to differentiate between common errors in code or - server configuration versus invalid IPN responses. + server configuration versus invalid IPN responses. Getting Started @@ -32,7 +32,7 @@ place to start. You should also have a [PayPal Sandbox Account][2] with a test buyer account and a test seller account. When logged into your sandbox account there is an IPN -simulator under the 'Test Tools' menu which you can used to test your IPN +simulator under the 'Test Tools' menu which you can used to test your IPN listener. [1]: https://cms.paypal.com/cms_content/US/en_US/files/developer/IPNGuide.pdf @@ -40,30 +40,30 @@ listener. Once you have your sandbox account setup, you simply create a PHP script that will be your IPN listener. In that script, use the `IpnListener()` class as shown -below. For a more thoroughly documented example, take a look at the +below. For a more thoroughly documented example, take a look at the `example/ipn.php` script in the source code. - use_sandbox = true; + $listener = new IpnListener(); + $listener->use_sandbox = true; - try { - $verified = $listener->processIpn(); - } catch (Exception $e) { - // fatal error trying to process IPN. - exit(0); - } + try { + $verified = $listener->processIpn(); + } catch (Exception $e) { + // fatal error trying to process IPN. + exit(0); + } - if ($verified) { - // IPN response was "VERIFIED" - } else { - // IPN response was "INVALID" - } + if ($verified) { + // IPN response was "VERIFIED" + } else { + // IPN response was "INVALID" + } - ?> + ?> Documentation @@ -85,16 +85,16 @@ __Problem__ The `processIpn()` method throws the following exception: - cURL error: [52] GnuTLS recv error (-9): A TLS packet with unexpected length was received. + cURL error: [52] GnuTLS recv error (-9): A TLS packet with unexpected length was received. __Solution__ When cURL is compiled with GnuTLS the call to PayPal will fail if the SSL version -is not explicitly set as a cURL option. Set the `force_ssl_v3` property to force +is not explicitly set as a cURL option. Set the `force_ssl_v3` property to force SSL 3: - $listener = new IpnListener(); - $listener->force_ssl_v3 = true; + $listener = new IpnListener(); + $listener->force_ssl_v3 = true; _Note: force_ssl_v3 is now true by default_ @@ -102,16 +102,16 @@ _Note: force_ssl_v3 is now true by default_ __Problem__ - PHP Warning: curl_setopt() [function.curl-setopt]: CURLOPT_FOLLOWLOCATION - cannot be activated when in safe_mode or an open_basedir is set in ... + PHP Warning: curl_setopt() [function.curl-setopt]: CURLOPT_FOLLOWLOCATION + cannot be activated when in safe_mode or an open_basedir is set in ... __Solution__ If you need PHP safe mode, you can disable CURLOPT_FOLLOWLOCATION using the `follow_location` property. - $listener = new IpnListener(); - $listener->follow_location = false; + $listener = new IpnListener(); + $listener->follow_location = false; _Note: follow_location is now false enabled by default_ @@ -123,57 +123,57 @@ Here is an example of a report returned by the `getTextReport()` method. Create your own reports by extending the `IpnListener()` class or by accessing the data directly in your ipn script. - -------------------------------------------------------------------------------- - [09/09/2011 8:35 AM] - https://www.sandbox.paypal.com/cgi-bin/webscr (curl) - -------------------------------------------------------------------------------- - HTTP/1.1 200 OK - Date: Fri, 09 Sep 2011 13:35:39 GMT - Server: Apache - X-Frame-Options: SAMEORIGIN - Set-Cookie: c9MWDuvPtT9GIMyPc3jwol1VSlO=Ch-NORlHUjlmbEm__KG9LupR4mfMfQTkx1QQ6hHDyc0RImWr88NY_ILeICENiwtVX3iw4jEnT1-1gccYjQafWrQCkDmiykNT8TeDUg7R7L0D9bQm47PTG8MafmrpyrUAxQfst0%7c_jG1ZL6CffJgwrC-stQeqni04tKaYSIZqyqhFU7tKnV520wiYOw0hwk5Ehrh3hLDvBxkpm%7cYTFdl0w0YpEqxu0D1jDTVTlEGXlmLs4wob2Glu9htpZkFV9O2aCyfQ4CvA2kLJmlI6YiXm%7c1315575340; domain=.paypal.com; path=/; Secure; HttpOnly - Set-Cookie: cookie_check=yes; expires=Mon, 06-Sep-2021 13:35:40 GMT; domain=.paypal.com; path=/; Secure; HttpOnly - Set-Cookie: navcmd=_notify-validate; domain=.paypal.com; path=/; Secure; HttpOnly - Set-Cookie: navlns=0.0; expires=Thu, 04-Sep-2031 13:35:40 GMT; domain=.paypal.com; path=/; Secure; HttpOnly - Set-Cookie: Apache=10.72.109.11.1315575339707456; path=/; expires=Sun, 01-Sep-41 13:35:39 GMT - X-Cnection: close - Transfer-Encoding: chunked - Content-Type: text/html; charset=UTF-8 - - VERIFIED - -------------------------------------------------------------------------------- - test_ipn 1 - payment_type instant - payment_date 06:34:51 Sep 09, 2011 PDT - payment_status Completed - address_status confirmed - payer_status verified - first_name John - last_name Smith - payer_email buyer@paypalsandbox.com - payer_id TESTBUYERID01 - address_name John Smith - address_country United States - address_country_code US - address_zip 95131 - address_state CA - address_city San Jose - address_street 123, any street - business seller@paypalsandbox.com - receiver_email seller@paypalsandbox.com - receiver_id TESTSELLERID1 - residence_country US - item_name something - item_number AK-1234 - quantity 1 - shipping 3.04 - tax 2.02 - mc_currency USD - mc_fee 0.44 - mc_gross 12.34 - mc_gross_1 9.34 - txn_type web_accept - txn_id 51991334 - notify_version 2.1 - custom xyz123 - charset windows-1252 - verify_sign Ah5rOpfPGo5g6FNg95DMPybP51J5AUEdXS1hqyRAP6WYYwaixKNDgQRR + -------------------------------------------------------------------------------- + [09/09/2011 8:35 AM] - https://www.sandbox.paypal.com/cgi-bin/webscr (curl) + -------------------------------------------------------------------------------- + HTTP/1.1 200 OK + Date: Fri, 09 Sep 2011 13:35:39 GMT + Server: Apache + X-Frame-Options: SAMEORIGIN + Set-Cookie: c9MWDuvPtT9GIMyPc3jwol1VSlO=Ch-NORlHUjlmbEm__KG9LupR4mfMfQTkx1QQ6hHDyc0RImWr88NY_ILeICENiwtVX3iw4jEnT1-1gccYjQafWrQCkDmiykNT8TeDUg7R7L0D9bQm47PTG8MafmrpyrUAxQfst0%7c_jG1ZL6CffJgwrC-stQeqni04tKaYSIZqyqhFU7tKnV520wiYOw0hwk5Ehrh3hLDvBxkpm%7cYTFdl0w0YpEqxu0D1jDTVTlEGXlmLs4wob2Glu9htpZkFV9O2aCyfQ4CvA2kLJmlI6YiXm%7c1315575340; domain=.paypal.com; path=/; Secure; HttpOnly + Set-Cookie: cookie_check=yes; expires=Mon, 06-Sep-2021 13:35:40 GMT; domain=.paypal.com; path=/; Secure; HttpOnly + Set-Cookie: navcmd=_notify-validate; domain=.paypal.com; path=/; Secure; HttpOnly + Set-Cookie: navlns=0.0; expires=Thu, 04-Sep-2031 13:35:40 GMT; domain=.paypal.com; path=/; Secure; HttpOnly + Set-Cookie: Apache=10.72.109.11.1315575339707456; path=/; expires=Sun, 01-Sep-41 13:35:39 GMT + X-Cnection: close + Transfer-Encoding: chunked + Content-Type: text/html; charset=UTF-8 + + VERIFIED + -------------------------------------------------------------------------------- + test_ipn 1 + payment_type instant + payment_date 06:34:51 Sep 09, 2011 PDT + payment_status Completed + address_status confirmed + payer_status verified + first_name John + last_name Smith + payer_email buyer@paypalsandbox.com + payer_id TESTBUYERID01 + address_name John Smith + address_country United States + address_country_code US + address_zip 95131 + address_state CA + address_city San Jose + address_street 123, any street + business seller@paypalsandbox.com + receiver_email seller@paypalsandbox.com + receiver_id TESTSELLERID1 + residence_country US + item_name something + item_number AK-1234 + quantity 1 + shipping 3.04 + tax 2.02 + mc_currency USD + mc_fee 0.44 + mc_gross 12.34 + mc_gross_1 9.34 + txn_type web_accept + txn_id 51991334 + notify_version 2.1 + custom xyz123 + charset windows-1252 + verify_sign Ah5rOpfPGo5g6FNg95DMPybP51J5AUEdXS1hqyRAP6WYYwaixKNDgQRR diff --git a/example/ipn.php b/example/ipn.php index 4e66cd6..af21e03 100644 --- a/example/ipn.php +++ b/example/ipn.php @@ -1,30 +1,30 @@ use_sandbox = true; /* -By default the IpnListener object is going going to post the data back to PayPal +By default the IpnListener object is going going to post the data back to PayPal using cURL over a secure SSL connection. This is the recommended way to post the data back, however, some people may have connections problems using this -method. +method. To post over standard HTTP connection, use: $listener->use_ssl = false; @@ -58,7 +58,7 @@ /* The processIpn() method will encode the POST variables sent by PayPal and then -POST them back to the PayPal server. An exception will be thrown if there is +POST them back to the PayPal server. An exception will be thrown if there is a fatal error (cannot connect, your server is not configured properly, etc.). Use a try/catch block to catch these fatal errors and log to the ipn_errors.log file we setup at the top of this file. @@ -68,11 +68,11 @@ $verified = $listener->processIpn($my_post_data); */ try { - $listener->requirePostMethod(); - $verified = $listener->processIpn(); + $listener->requirePostMethod(); + $verified = $listener->processIpn(); } catch (Exception $e) { - error_log($e->getMessage()); - exit(0); + error_log($e->getMessage()); + exit(0); } @@ -81,31 +81,31 @@ was "INVALID". */ if ($verified) { - /* - Once you have a verified IPN you need to do a few more checks on the POST - fields--typically against data you stored in your database during when the - end user made a purchase (such as in the "success" page on a web payments - standard button). The fields PayPal recommends checking are: - - 1. Check the $_POST['payment_status'] is "Completed" - 2. Check that $_POST['txn_id'] has not been previously processed - 3. Check that $_POST['receiver_email'] is your Primary PayPal email - 4. Check that $_POST['payment_amount'] and $_POST['payment_currency'] - are correct - - Since implementations on this varies, I will leave these checks out of this - example and just send an email using the getTextReport() method to get all - of the details about the IPN. - */ - mail('YOUR EMAIL ADDRESS', 'Verified IPN', $listener->getTextReport()); + /* + Once you have a verified IPN you need to do a few more checks on the POST + fields--typically against data you stored in your database during when the + end user made a purchase (such as in the "success" page on a web payments + standard button). The fields PayPal recommends checking are: + + 1. Check the $_POST['payment_status'] is "Completed" + 2. Check that $_POST['txn_id'] has not been previously processed + 3. Check that $_POST['receiver_email'] is your Primary PayPal email + 4. Check that $_POST['payment_amount'] and $_POST['payment_currency'] + are correct + + Since implementations on this varies, I will leave these checks out of this + example and just send an email using the getTextReport() method to get all + of the details about the IPN. + */ + mail('YOUR EMAIL ADDRESS', 'Verified IPN', $listener->getTextReport()); } else { - /* - An Invalid IPN *may* be caused by a fraudulent transaction attempt. It's - a good idea to have a developer or sys admin manually investigate any - invalid IPN. - */ - mail('YOUR EMAIL ADDRESS', 'Invalid IPN', $listener->getTextReport()); + /* + An Invalid IPN *may* be caused by a fraudulent transaction attempt. It's + a good idea to have a developer or sys admin manually investigate any + invalid IPN. + */ + mail('YOUR EMAIL ADDRESS', 'Invalid IPN', $listener->getTextReport()); } ?> diff --git a/ipnlistener.php b/ipnlistener.php index bb8c93d..649cfd0 100644 --- a/ipnlistener.php +++ b/ipnlistener.php @@ -1,312 +1,312 @@ use_ssl) { - $uri = 'https://'.$this->getPaypalHost().'/cgi-bin/webscr'; - $this->post_uri = $uri; - } else { - $uri = 'http://'.$this->getPaypalHost().'/cgi-bin/webscr'; - $this->post_uri = $uri; - } - - $ch = curl_init(); + + /** + * If true, the recommended cURL PHP library is used to send the post back + * to PayPal. If flase then fsockopen() is used. Default true. + * + * @var boolean + */ + public $use_curl = true; + + /** + * If true, explicitly sets cURL to use SSL version 3. Use this if cURL + * is compiled with GnuTLS SSL. + * + * @var boolean + */ + public $force_ssl_v3 = true; + + /** + * If true, cURL will use the CURLOPT_FOLLOWLOCATION to follow any + * "Location: ..." headers in the response. + * + * @var boolean + */ + public $follow_location = false; + + /** + * If true, an SSL secure connection (port 443) is used for the post back + * as recommended by PayPal. If false, a standard HTTP (port 80) connection + * is used. Default true. + * + * @var boolean + */ + public $use_ssl = true; + + /** + * If true, the paypal sandbox URI www.sandbox.paypal.com is used for the + * post back. If false, the live URI www.paypal.com is used. Default false. + * + * @var boolean + */ + public $use_sandbox = false; + + /** + * The amount of time, in seconds, to wait for the PayPal server to respond + * before timing out. Default 30 seconds. + * + * @var int + */ + public $timeout = 30; + + private $post_data = array(); + private $post_uri = ''; + private $response_status = ''; + private $response = ''; + + const PAYPAL_HOST = 'www.paypal.com'; + const SANDBOX_HOST = 'www.sandbox.paypal.com'; + + /** + * Post Back Using cURL + * + * Sends the post back to PayPal using the cURL library. Called by + * the processIpn() method if the use_curl property is true. Throws an + * exception if the post fails. Populates the response, response_status, + * and post_uri properties on success. + * + * @param string The post data as a URL encoded string + */ + protected function curlPost($encoded_data) { + + if ($this->use_ssl) { + $uri = 'https://'.$this->getPaypalHost().'/cgi-bin/webscr'; + $this->post_uri = $uri; + } else { + $uri = 'http://'.$this->getPaypalHost().'/cgi-bin/webscr'; + $this->post_uri = $uri; + } + + $ch = curl_init(); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); - curl_setopt($ch, CURLOPT_CAINFO, - dirname(__FILE__)."/cert/api_cert_chain.crt"); - curl_setopt($ch, CURLOPT_URL, $uri); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded_data); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $this->follow_location); - curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HEADER, true); - - if ($this->force_ssl_v3) { - curl_setopt($ch, CURLOPT_SSLVERSION, 3); - } - - $this->response = curl_exec($ch); - $this->response_status = strval(curl_getinfo($ch, CURLINFO_HTTP_CODE)); - - if ($this->response === false || $this->response_status == '0') { - $errno = curl_errno($ch); - $errstr = curl_error($ch); - throw new Exception("cURL error: [$errno] $errstr"); - } - } - - /** - * Post Back Using fsockopen() - * - * Sends the post back to PayPal using the fsockopen() function. Called by - * the processIpn() method if the use_curl property is false. Throws an - * exception if the post fails. Populates the response, response_status, - * and post_uri properties on success. - * - * @param string The post data as a URL encoded string - */ - protected function fsockPost($encoded_data) { - - if ($this->use_ssl) { - $uri = 'ssl://'.$this->getPaypalHost(); - $port = '443'; - $this->post_uri = $uri.'/cgi-bin/webscr'; - } else { - $uri = $this->getPaypalHost(); // no "http://" in call to fsockopen() - $port = '80'; - $this->post_uri = 'http://'.$uri.'/cgi-bin/webscr'; - } - - $fp = fsockopen($uri, $port, $errno, $errstr, $this->timeout); - - if (!$fp) { - // fsockopen error - throw new Exception("fsockopen error: [$errno] $errstr"); - } - - $header = "POST /cgi-bin/webscr HTTP/1.1\r\n"; - $header .= "Host: ".$this->getPaypalHost()."\r\n"; - $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; - $header .= "Content-Length: ".strlen($encoded_data)."\r\n"; - $header .= "Connection: Close\r\n\r\n"; - - fputs($fp, $header.$encoded_data."\r\n\r\n"); - - while(!feof($fp)) { - if (empty($this->response)) { - // extract HTTP status from first line - $this->response .= $status = fgets($fp, 1024); - $this->response_status = trim(substr($status, 9, 4)); - } else { - $this->response .= fgets($fp, 1024); - } - } - - fclose($fp); - } - - private function getPaypalHost() { - if ($this->use_sandbox) return self::SANDBOX_HOST; - else return self::PAYPAL_HOST; - } - - /** - * Get POST URI - * - * Returns the URI that was used to send the post back to PayPal. This can - * be useful for troubleshooting connection problems. The default URI - * would be "ssl://www.sandbox.paypal.com:443/cgi-bin/webscr" - * - * @return string - */ - public function getPostUri() { - return $this->post_uri; - } - - /** - * Get Response - * - * Returns the entire response from PayPal as a string including all the - * HTTP headers. - * - * @return string - */ - public function getResponse() { - return $this->response; - } - - /** - * Get Response Status - * - * Returns the HTTP response status code from PayPal. This should be "200" - * if the post back was successful. - * - * @return string - */ - public function getResponseStatus() { - return $this->response_status; - } - - /** - * Get Text Report - * - * Returns a report of the IPN transaction in plain text format. This is - * useful in emails to order processors and system administrators. Override - * this method in your own class to customize the report. - * - * @return string - */ - public function getTextReport() { - - $r = ''; - - // date and POST url - for ($i=0; $i<80; $i++) { $r .= '-'; } - $r .= "\n[".date('m/d/Y g:i A').'] - '.$this->getPostUri(); - if ($this->use_curl) $r .= " (curl)\n"; - else $r .= " (fsockopen)\n"; - - // HTTP Response - for ($i=0; $i<80; $i++) { $r .= '-'; } - $r .= "\n{$this->getResponse()}\n"; - - // POST vars - for ($i=0; $i<80; $i++) { $r .= '-'; } - $r .= "\n"; - - foreach ($this->post_data as $key => $value) { - $r .= str_pad($key, 25)."$value\n"; - } - $r .= "\n\n"; - - return $r; - } - - /** - * Process IPN - * - * Handles the IPN post back to PayPal and parsing the response. Call this - * method from your IPN listener script. Returns true if the response came - * back as "VERIFIED", false if the response came back "INVALID", and - * throws an exception if there is an error. - * - * @param array - * - * @return boolean - */ - public function processIpn($post_data=null) { - - $encoded_data = 'cmd=_notify-validate'; - - if ($post_data === null) { - // use raw POST data - if (!empty($_POST)) { - $this->post_data = $_POST; - $encoded_data .= '&'.file_get_contents('php://input'); - } else { - throw new Exception("No POST data found."); - } - } else { - // use provided data array - $this->post_data = $post_data; - - foreach ($this->post_data as $key => $value) { - $encoded_data .= "&$key=".urlencode($value); - } - } - - if ($this->use_curl) $this->curlPost($encoded_data); - else $this->fsockPost($encoded_data); - - if (strpos($this->response_status, '200') === false) { - throw new Exception("Invalid response status: ".$this->response_status); - } - - if (strpos($this->response, "VERIFIED") !== false) { - return true; - } elseif (strpos($this->response, "INVALID") !== false) { - return false; - } else { - throw new Exception("Unexpected response from PayPal."); - } - } - - /** - * Require Post Method - * - * Throws an exception and sets a HTTP 405 response header if the request - * method was not POST. - */ - public function requirePostMethod() { - // require POST requests - if ($_SERVER['REQUEST_METHOD'] && $_SERVER['REQUEST_METHOD'] != 'POST') { - header('Allow: POST', true, 405); - throw new Exception("Invalid HTTP request method."); - } - } + curl_setopt($ch, CURLOPT_CAINFO, + dirname(__FILE__)."/cert/api_cert_chain.crt"); + curl_setopt($ch, CURLOPT_URL, $uri); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded_data); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $this->follow_location); + curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, true); + + if ($this->force_ssl_v3) { + curl_setopt($ch, CURLOPT_SSLVERSION, 3); + } + + $this->response = curl_exec($ch); + $this->response_status = strval(curl_getinfo($ch, CURLINFO_HTTP_CODE)); + + if ($this->response === false || $this->response_status == '0') { + $errno = curl_errno($ch); + $errstr = curl_error($ch); + throw new Exception("cURL error: [$errno] $errstr"); + } + } + + /** + * Post Back Using fsockopen() + * + * Sends the post back to PayPal using the fsockopen() function. Called by + * the processIpn() method if the use_curl property is false. Throws an + * exception if the post fails. Populates the response, response_status, + * and post_uri properties on success. + * + * @param string The post data as a URL encoded string + */ + protected function fsockPost($encoded_data) { + + if ($this->use_ssl) { + $uri = 'ssl://'.$this->getPaypalHost(); + $port = '443'; + $this->post_uri = $uri.'/cgi-bin/webscr'; + } else { + $uri = $this->getPaypalHost(); // no "http://" in call to fsockopen() + $port = '80'; + $this->post_uri = 'http://'.$uri.'/cgi-bin/webscr'; + } + + $fp = fsockopen($uri, $port, $errno, $errstr, $this->timeout); + + if (!$fp) { + // fsockopen error + throw new Exception("fsockopen error: [$errno] $errstr"); + } + + $header = "POST /cgi-bin/webscr HTTP/1.1\r\n"; + $header .= "Host: ".$this->getPaypalHost()."\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: ".strlen($encoded_data)."\r\n"; + $header .= "Connection: Close\r\n\r\n"; + + fputs($fp, $header.$encoded_data."\r\n\r\n"); + + while(!feof($fp)) { + if (empty($this->response)) { + // extract HTTP status from first line + $this->response .= $status = fgets($fp, 1024); + $this->response_status = trim(substr($status, 9, 4)); + } else { + $this->response .= fgets($fp, 1024); + } + } + + fclose($fp); + } + + private function getPaypalHost() { + if ($this->use_sandbox) return self::SANDBOX_HOST; + else return self::PAYPAL_HOST; + } + + /** + * Get POST URI + * + * Returns the URI that was used to send the post back to PayPal. This can + * be useful for troubleshooting connection problems. The default URI + * would be "ssl://www.sandbox.paypal.com:443/cgi-bin/webscr" + * + * @return string + */ + public function getPostUri() { + return $this->post_uri; + } + + /** + * Get Response + * + * Returns the entire response from PayPal as a string including all the + * HTTP headers. + * + * @return string + */ + public function getResponse() { + return $this->response; + } + + /** + * Get Response Status + * + * Returns the HTTP response status code from PayPal. This should be "200" + * if the post back was successful. + * + * @return string + */ + public function getResponseStatus() { + return $this->response_status; + } + + /** + * Get Text Report + * + * Returns a report of the IPN transaction in plain text format. This is + * useful in emails to order processors and system administrators. Override + * this method in your own class to customize the report. + * + * @return string + */ + public function getTextReport() { + + $r = ''; + + // date and POST url + for ($i=0; $i<80; $i++) { $r .= '-'; } + $r .= "\n[".date('m/d/Y g:i A').'] - '.$this->getPostUri(); + if ($this->use_curl) $r .= " (curl)\n"; + else $r .= " (fsockopen)\n"; + + // HTTP Response + for ($i=0; $i<80; $i++) { $r .= '-'; } + $r .= "\n{$this->getResponse()}\n"; + + // POST vars + for ($i=0; $i<80; $i++) { $r .= '-'; } + $r .= "\n"; + + foreach ($this->post_data as $key => $value) { + $r .= str_pad($key, 25)."$value\n"; + } + $r .= "\n\n"; + + return $r; + } + + /** + * Process IPN + * + * Handles the IPN post back to PayPal and parsing the response. Call this + * method from your IPN listener script. Returns true if the response came + * back as "VERIFIED", false if the response came back "INVALID", and + * throws an exception if there is an error. + * + * @param array + * + * @return boolean + */ + public function processIpn($post_data=null) { + + $encoded_data = 'cmd=_notify-validate'; + + if ($post_data === null) { + // use raw POST data + if (!empty($_POST)) { + $this->post_data = $_POST; + $encoded_data .= '&'.file_get_contents('php://input'); + } else { + throw new Exception("No POST data found."); + } + } else { + // use provided data array + $this->post_data = $post_data; + + foreach ($this->post_data as $key => $value) { + $encoded_data .= "&$key=".urlencode($value); + } + } + + if ($this->use_curl) $this->curlPost($encoded_data); + else $this->fsockPost($encoded_data); + + if (strpos($this->response_status, '200') === false) { + throw new Exception("Invalid response status: ".$this->response_status); + } + + if (strpos($this->response, "VERIFIED") !== false) { + return true; + } elseif (strpos($this->response, "INVALID") !== false) { + return false; + } else { + throw new Exception("Unexpected response from PayPal."); + } + } + + /** + * Require Post Method + * + * Throws an exception and sets a HTTP 405 response header if the request + * method was not POST. + */ + public function requirePostMethod() { + // require POST requests + if ($_SERVER['REQUEST_METHOD'] && $_SERVER['REQUEST_METHOD'] != 'POST') { + header('Allow: POST', true, 405); + throw new Exception("Invalid HTTP request method."); + } + } }