Answers.Body: <p>Here's a full list of all publicly known plugins...</p>
  
  <blockquote>
    <p><a href="https://straightupcraft.com/craft-plugins" rel="nofollow">https://straightupcraft.com/craft-plugins</a></p>
  </blockquote>
  
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2015-09-21T05:12:11.003Z
Answers.LastActivityDate: 2015-09-21T05:12:11.003Z
Answers.UserDisplayName: Lindsey D
Body: <p>Has anybody got a controller (or whatever plugin aparatus is needed) to process Paypal IPN?</p>
  
  <p><strong>Edit:</strong> Years later, I have accomplished this, and have some information which could be helpful if someone else wants PayPal IPN to function within a controller. There is a trick regarding CSRF, for example.</p>
  
  <p><strong>#1 CSRF</strong>
  PayPal is not going to send a Craft CSRF token, so unless you want to disable CSRF (not recommended), then your <code>config/general.php</code> needs a line similar to this:</p>
  
  <pre><code>'enableCsrfProtection' =&gt; (!isset($_SERVER['REQUEST_URI']) || $_SERVER['REQUEST_URI'] != '/actions/livestream/main/paypalipn'),
  </code></pre>
  
  <p>where the plugin is called <code>livestream</code>, the controller is <code>Main</code>, and the controller function is <code>actionPaypalipn</code></p>
  
  <p><strong>#2 Not logged in</strong> PayPal will also not be logged in to Craft, so the controller requires this:</p>
  
  <pre><code>protected $allowAnonymous = true;
  </code></pre>
  
  <p>or I suppose this:</p>
  
  <pre><code>protected $allowAnonymous = ['paypalipn'];
  </code></pre>
  
  <p>Again, the quotes 'paypalipn' are because the controller function is <code>actionPaypalipn</code>.</p>
  
  <p><strong>Full Code</strong> (cleaned up a bit)</p>
  
  <p>So those are the main bits. For those who want to look at someone else's code, it's here. Over at PayPal, the IPN Notification URL is set to <code>https://mywebsite.org/actions/livestream/main/paypalipn</code>. The plugin happens to be called Livestream.</p>
  
  <pre><code>class MainController extends Controller
  {
      protected $allowAnonymous = true;
  
      public function actionPaypalipn() {
          $request = Craft::$app-&gt;request-&gt;getBodyParams();
  
          // Go handshake with PayPalIPN to verify the transaction. 
          $success = Livestream::$plugin-&gt;paypal-&gt;verifyIPN($request);
          if ($success) {
              //It worked! Now do our business-side processing(emails,etc).
              Livestream::$plugin-&gt;otherservice-&gt;processSuccessfulPayment($request);
              return "IPN Verified";
          } else {
              return "IPN Failed";
          }
      }
  </code></pre>
  
  <p>And the service:</p>
  
  <pre><code>class Paypal extends Component
  {
  
      /***********************************************************
      *                                                          *
      * Helper functions for the PayPal IPN verification process *
      *                                                          *
      ***********************************************************/
  
      /** @var bool Indicates if the sandbox endpoint is used. */
      private $use_sandbox = false;
      /** @var bool Indicates if the local certificates are used. */
      private $use_local_certs = true;
  
      /** Production Postback URL */
      const VERIFY_URI = 'https://ipnpb.paypal.com/cgi-bin/webscr';
      /** Sandbox Postback URL */
      const SANDBOX_VERIFY_URI = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr';
  
      /** Response from PayPal indicating validation was successful */
      const VALID = 'VERIFIED';
      /** Response from PayPal indicating validation failed */
      const INVALID = 'INVALID';
  
      /**
       * Sets the IPN verification to sandbox mode (for use when testing,
       * should not be enabled in production).
       * @return void
       */
      public function useSandbox()
      {
          $this-&gt;use_sandbox = false;
      }
  
      /**
       * Sets curl to use php curl's built in certs (may be required in some
       * environments).
       * @return void
       */
      public function usePHPCerts()
      {
          $this-&gt;use_local_certs = false;
      }
  
      /**
       * Determine endpoint to post the verification data to.
       *
       * @return string
       */
      public function getPaypalUri()
      {
          if ($this-&gt;use_sandbox) {
              return self::SANDBOX_VERIFY_URI;
          } else {
              return self::VERIFY_URI;
          }
      }
  
      /***********************************************************
      * Main function for the PayPal IPN verification process    *
      * Sends the incoming post data back to PayPal using the    *
      * cURL library.                                            *
      *                                                          *
      * @return bool                                             *
      ***********************************************************/
  
      public function verifyIPN($raw_post_array) {
  
  
          /**************************************************
          * Verify the validity of the request with PayPal. *
          **************************************************/
  
          // BUILD THE QUERY TO POST BACK TO PAYPAL
          $req = 'cmd=_notify-validate&amp;' . http_build_query($raw_post_array, '', '&amp;');
  
          //Log it as we go.
          file_put_contents(SOME_PATH . "log.txt", "\n". date(DATE_RSS)."\n$req\n", FILE_APPEND);
  
          // Post the data back to PayPal, using curl. Throw exceptions if errors occur.
          $ch = curl_init($this-&gt;getPaypalUri());
          curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
          curl_setopt($ch, CURLOPT_POST, 1);
          curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
          curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
          curl_setopt($ch, CURLOPT_SSLVERSION, 6);
          curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
          curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
  
          // This is often required if the server is missing a global cert bundle, or is using an outdated one.
          if ($this-&gt;use_local_certs) {
              curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . "/cert/cacert.pem");
          }
          curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
          curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
          curl_setopt($ch, CURLOPT_HTTPHEADER, array(
              'User-Agent: PHP-IPN-Verification-Script',
              'Connection: Close',
          ));
          $res = curl_exec($ch);
          if ( ! ($res)) {
              $errno = curl_errno($ch);
              $errstr = curl_error($ch);
              curl_close($ch);
              error_log("cURL error: [$errno] $errstr", 3 , $errorLog );
          }
  
          $info = curl_getinfo($ch);
          $http_code = $info['http_code'];
          if ($http_code != 200) {
              error_log("PayPal responded with http code $http_code", 3 , $errorLog );
          }
  
          curl_close($ch);
  
          /************************************
          * Report what we found from PayPal. *
          ************************************/
  
          // Check if PayPal verified the IPN data, and if so, return true.
          if ($res == self::VALID) {
              return true;
          } else {
              return false;
          }
      }
  </code></pre>
  
Comments.ContentLicense: CC BY-SA 3.0
Comments.CreationDate: 2015-09-21T05:23:41.697Z
Comments.Text: I'm not the downvoter, but I'd imagine it's because the question "does not show any research effort". This would have been a much better question for Slack (for future reference).
Comments.UserDisplayName: Lindsey D
Comments.ContentLicense: CC BY-SA 3.0
Comments.CreationDate: 2015-09-27T06:57:12.930Z
Comments.Text: Thank you, Lindsey. I appreciate you've been helpful before too.  In this case, the whole point was to not invest much research effort. For me Paypal IPN is a clunky hassle to work out once every several years, and then forget.  But now I'm thinking it probably doesn't need to be done in Craft at all. Just a file or two somewhere on the public part of the website.  Anyway, it has to wait a couple of weeks.
Comments.UserDisplayName: kr37
Comments.ContentLicense: CC BY-SA 3.0
Comments.CreationDate: 2016-11-05T20:41:26.623Z
Comments.Text: I'm voting to close this question as off-topic because although I was intending to do this through Craft CMS, there is no need to have it within the CMS, so it is not really a Craft question.
Comments.UserDisplayName: kr37
Tags: <plugin-development><controller>
Title: Paypal IPN controller logic?
ThreadText: # Paypal IPN controller logic?
  
  (Asked by: username_0 on 2015-09-20)
  
  Has anybody got a controller (or whatever plugin aparatus is needed) to process Paypal IPN?
  
  **Edit:** Years later, I have accomplished this, and have some information which could be helpful if someone else wants PayPal IPN to function within a controller. There is a trick regarding CSRF, for example.
  
  **#1 CSRF**
  PayPal is not going to send a Craft CSRF token, so unless you want to disable CSRF (not recommended), then your `config/general.php` needs a line similar to this:
  
  ```
  'enableCsrfProtection' => (!isset($_SERVER['REQUEST_URI']) || $_SERVER['REQUEST_URI'] != '/actions/livestream/main/paypalipn'),
  
  ```
  
  where the plugin is called `livestream`, the controller is `Main`, and the controller function is `actionPaypalipn`
  
  **#2 Not logged in** PayPal will also not be logged in to Craft, so the controller requires this:
  
  ```
  protected $allowAnonymous = true;
  
  ```
  
  or I suppose this:
  
  ```
  protected $allowAnonymous = ['paypalipn'];
  
  ```
  
  Again, the quotes 'paypalipn' are because the controller function is `actionPaypalipn`.
  
  **Full Code** (cleaned up a bit)
  
  So those are the main bits. For those who want to look at someone else's code, it's here. Over at PayPal, the IPN Notification URL is set to `https://mywebsite.org/actions/livestream/main/paypalipn`. The plugin happens to be called Livestream.
  
  ```
  class MainController extends Controller
  {
      protected $allowAnonymous = true;
  
      public function actionPaypalipn() {
          $request = Craft::$app->request->getBodyParams();
  
          // Go handshake with PayPalIPN to verify the transaction. 
          $success = Livestream::$plugin->paypal->verifyIPN($request);
          if ($success) {
              //It worked! Now do our business-side processing(emails,etc).
              Livestream::$plugin->otherservice->processSuccessfulPayment($request);
              return "IPN Verified";
          } else {
              return "IPN Failed";
          }
      }
  
  ```
  
  And the service:
  
  ```
  class Paypal extends Component
  {
  
      /***********************************************************
      *                                                          *
      * Helper functions for the PayPal IPN verification process *
      *                                                          *
      ***********************************************************/
  
      /** @var bool Indicates if the sandbox endpoint is used. */
      private $use_sandbox = false;
      /** @var bool Indicates if the local certificates are used. */
      private $use_local_certs = true;
  
      /** Production Postback URL */
      const VERIFY_URI = 'https://ipnpb.paypal.com/cgi-bin/webscr';
      /** Sandbox Postback URL */
      const SANDBOX_VERIFY_URI = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr';
  
      /** Response from PayPal indicating validation was successful */
      const VALID = 'VERIFIED';
      /** Response from PayPal indicating validation failed */
      const INVALID = 'INVALID';
  
      /**
       * Sets the IPN verification to sandbox mode (for use when testing,
       * should not be enabled in production).
       * @return void
       */
      public function useSandbox()
      {
          $this->use_sandbox = false;
      }
  
      /**
       * Sets curl to use php curl's built in certs (may be required in some
       * environments).
       * @return void
       */
      public function usePHPCerts()
      {
          $this->use_local_certs = false;
      }
  
      /**
       * Determine endpoint to post the verification data to.
       *
       * @return string
       */
      public function getPaypalUri()
      {
          if ($this->use_sandbox) {
              return self::SANDBOX_VERIFY_URI;
          } else {
              return self::VERIFY_URI;
          }
      }
  
      /***********************************************************
      * Main function for the PayPal IPN verification process    *
      * Sends the incoming post data back to PayPal using the    *
      * cURL library.                                            *
      *                                                          *
      * @return bool                                             *
      ***********************************************************/
  
      public function verifyIPN($raw_post_array) {
  
          /**************************************************
          * Verify the validity of the request with PayPal. *
          **************************************************/
  
          // BUILD THE QUERY TO POST BACK TO PAYPAL
          $req = 'cmd=_notify-validate&' . http_build_query($raw_post_array, '', '&');
  
          //Log it as we go.
          file_put_contents(SOME_PATH . "log.txt", "\n". date(DATE_RSS)."\n$req\n", FILE_APPEND);
  
          // Post the data back to PayPal, using curl. Throw exceptions if errors occur.
          $ch = curl_init($this->getPaypalUri());
          curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
          curl_setopt($ch, CURLOPT_POST, 1);
          curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
          curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
          curl_setopt($ch, CURLOPT_SSLVERSION, 6);
          curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
          curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
  
          // This is often required if the server is missing a global cert bundle, or is using an outdated one.
          if ($this->use_local_certs) {
              curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . "/cert/cacert.pem");
          }
          curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
          curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
          curl_setopt($ch, CURLOPT_HTTPHEADER, array(
              'User-Agent: PHP-IPN-Verification-Script',
              'Connection: Close',
          ));
          $res = curl_exec($ch);
          if ( ! ($res)) {
              $errno = curl_errno($ch);
              $errstr = curl_error($ch);
              curl_close($ch);
              error_log("cURL error: [$errno] $errstr", 3 , $errorLog );
          }
  
          $info = curl_getinfo($ch);
          $http_code = $info['http_code'];
          if ($http_code != 200) {
              error_log("PayPal responded with http code $http_code", 3 , $errorLog );
          }
  
          curl_close($ch);
  
          /************************************
          * Report what we found from PayPal. *
          ************************************/
  
          // Check if PayPal verified the IPN data, and if so, return true.
          if ($res == self::VALID) {
              return true;
          } else {
              return false;
          }
      }
  
  ```
  
  **Tags:** plugin-development, controller
  
  > **Comments:**
  > **username_1**: I'm not the downvoter, but I'd imagine it's because the question "does not show any research effort". This would have been a much better question for Slack (for future reference).  
  > **username_0**: Thank you, username_1. I appreciate you've been helpful before too.  In this case, the whole point was to not invest much research effort. For me Paypal IPN is a clunky hassle to work out once every several years, and then forget.  But now I'm thinking it probably doesn't need to be done in Craft at all. Just a file or two somewhere on the public part of the website.  Anyway, it has to wait a couple of weeks.  
  > **username_0**: I'm voting to close this question as off-topic because although I was intending to do this through Craft CMS, there is no need to have it within the CMS, so it is not really a Craft question.  
  
  ---
  
  ## 1 answers
  
  ### Answer by username_1 on 2015-09-21. Score: 1
  
  Here's a full list of all publicly known plugins...
  
  > <https://straightupcraft.com/craft-plugins>
  
  
  ---
  
  