Paypal IPN Return INVALID All The Time Issue
The Paypal IPN returning invalid all the time is an old issue but I will try to help you to solve this issue. I will show you two methods on how to solve this problem, first method is very simple, this is not actually a method because even if Paypal's documentation is very big and you can find source code too, they never say that IPN Simulator is not working so, your code maybe works perfectly but if you try to test your implementation with IPN Simulator it will return all the time INVALID. I tried a lot of methods and from what I tried all the time I got INVALID response. If you are in the same situation check to do a manual payment in sandbox mode and check what response you get, you probably will get the VERIFIED response as I got.
If you are begginer with Paypal the second method is for you.
First you have to create a new class, I named it PayPalIPN.php and in this file you have to place the following code:
<?php class PaypalIPN { /** @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 = true; } /** * 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; } } /** * Verification Function * Sends the incoming post data back to PayPal using the cURL library. * * @return bool * @throws Exception */ public function verifyIPN() { if ( ! count($_POST)) { throw new Exception("Missing POST Data"); } $raw_post_data = file_get_contents('php://input'); $raw_post_array = explode('&', $raw_post_data); $myPost = array(); foreach ($raw_post_array as $keyval) { $keyval = explode('=', $keyval); if (count($keyval) == 2) { // Since we do not want the plus in the datetime string to be encoded to a space, we manually encode it. if ($keyval[0] === 'payment_date') { if (substr_count($keyval[1], '+') === 1) { $keyval[1] = str_replace('+', '%2B', $keyval[1]); } } $myPost[$keyval[0]] = urldecode($keyval[1]); } } // Build the body of the verification post request, adding the _notify-validate command. $req = 'cmd=_notify-validate'; $get_magic_quotes_exists = false; if (function_exists('get_magic_quotes_gpc')) { $get_magic_quotes_exists = true; } foreach ($myPost as $key => $value) { if ($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1) { $value = urlencode(stripslashes($value)); } else { $value = urlencode($value); } $req .= "&$key=$value"; } // 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); throw new Exception("cURL error: [$errno] $errstr"); } $info = curl_getinfo($ch); $http_code = $info['http_code']; if ($http_code != 200) { throw new Exception("PayPal responded with http code $http_code"); } curl_close($ch); // Check if PayPal verifies the IPN data, and if so, return true. if ($res == self::VALID) { return true; } else { return false; } } }
In the above function you have all you need to handle the ipn messages. In the verify function it will get the _POST message and it will re-send to paypal in order to get the VERIFED response, then in your main controller you have to include this class and call it using the following code:
require('PaypalIPN.php'); use PaypalIPN; $ipn = new PaypalIPN(); // Use the sandbox endpoint during testing. $ipn->useSandbox(); $verified = $ipn->verifyIPN(); if ($verified) { } // Reply with an empty 200 response to indicate to paypal the IPN was received correctly. header("HTTP/1.1 200 OK");
You must return header 200 because the Paypal is waiting for it, if you don't return the 200 header you won't get the right status from Paypal. Then in if statement you place all functions you need. Very important if the Paypal return all the time INVALID response check your implementation by making a manual payment because IPN Simulator is not working, they have an issue but I didn't find any specification in their documentation.