<?php
  /**
   * PP Listener Class
   *
   * @package Wojo Framework
   * @author wojoscripts.com
   * @copyright 2016
   * @version $Id: paypal.class.php, v1.00 2016-04-20 18:20:24 gewa Exp $
   */

  if (!defined("_WOJO"))
      die('Direct access to this location is not allowed.');

  class IpnListener
  {

      /**
       *  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, 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 live URI paypal.com is used for the
       *  post back. If false, the sandbox URI sandbox.paypal.com is used. Default false.
       *
       *  @var boolean
       */
      public $use_live = 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/cacert.pem");
          curl_setopt($ch, CURLOPT_URL, $uri);
          curl_setopt($ch, CURLOPT_USERAGENT, App::Core()->company);
          curl_setopt($ch, CURLOPT_POST, true);
		  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close', 'User-Agent: ' . App::Core()->company));
          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);
          curl_setopt($ch, CURLOPT_SSLVERSION, 6);
 
          $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_live)
              return self::PAYPAL_HOST;
          else
              return self::SANDBOX_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.");
          }
      }
  }
?>