PayPal IPN Issue, but works in Sandbox fine

Using the code located on GitHub here: https://github.com/Quixotix/PHP-PayPal-IPN

So, I have a php page, for example, which I use as my ipnlistener for the sa=paypal_verify URL in the variables, in step 3 of creating the button, for example:

 notify_url=http://mydomain.com/index.php?page=paypaltest;sa=paypal_verify return=http://mydomain.com/index.php?page=paypaltest;sa=thankyou rm=2 

Here is the code related to http://mydomain.com/index.php?page=paypaltest

 if (!empty($_REQUEST['sa']) && $_REQUEST['sa'] != 'thankyou') { // Require file for loading up global variables, etc. Might not be needed, but just in here in case. require_once('/public_html/Settings.php'); global $smcFunc, $context, $scripturl, $boarddir, $modSettings, $txt; if ($_REQUEST['sa'] == 'paypal_verify') { ini_set('log_errors', true); ini_set('error_log', '/ipn_errors.log'); // Here is where the actual IpnListener Class is defined // and uses cURL or fSocket to post back to paypal. require_once($boarddir . '/ipn/ipnlistener.php'); $listener = new IpnListener(); try { $listener->requirePostMethod(); $verified = $listener->processIpn(); } catch (Exception $e) { error_log($e->getMessage()); exit(0); } /* The processIpn() method returned true if the IPN was "VERIFIED" and false if it was "INVALID". */ if ($verified) { $errmsg = ''; // stores errors from fraud checks // 1. Make sure the payment status is "Completed" if ($_POST['payment_status'] != 'Completed') { // simply ignore any IPN that is not completed exit(0); } // Database Query in here (excluded) that selects sellers_email, product_name, and price from within the database for the actual purchase, based on the $_POST['item_name'] from paypal. // If return results are 0 from database table than do following: $errmsg .= "Product Not Found in the database: "; $errmsg .= $_POST['item_name']."\n"; // manually investigate errors from the fraud checking $body = "IPN failed fraud checks: \n$errmsg\n\n"; $body .= $listener->getTextReport(); mail(' myemail@address.com ', 'IPN Fraud Warning', $body); exit(0); // Set $sellers_email, $item_name, and $price variables from the database table. And than free the mysql result. // 2. Make sure seller email matches your primary account email. if ($_POST['receiver_email'] != $sellers_email) { $errmsg .= "'receiver_email' does not match: "; $errmsg .= $_POST['receiver_email']."\n"; } // 3. Make sure the amount(s) paid match if ($_POST['mc_gross'] != $price) { $errmsg .= "'mc_gross' does not match: "; $errmsg .= $_POST['mc_gross']."\n"; } // 4. Make sure the currency code matches if ($_POST['mc_currency'] != 'USD') { $errmsg .= "'mc_currency' does not match: "; $errmsg .= $_POST['mc_currency']."\n"; } // 5. Ensure the transaction is not a duplicate. // Attempt to grab a transaction id from the table where it goes. The column is id_txn, if it exists $errmsg .= "'txn_id' has already been processed: ".$_POST['txn_id']."\n"; // free database result. // Set $txn_id from paypal to the $txn_id variable. $txn_id = (string) $_POST['txn_id']; if (!empty($errmsg)) { // manually investigate errors from the fraud checking $body = "IPN failed fraud checks: \n$errmsg\n\n"; $body .= $listener->getTextReport(); mail(' myemail@address.com ', 'IPN Fraud Warning', $body); } else { // send email to buyer. // and just to be sure, send them to the sa=thankyou page! } } else { // send email to self with the errors listed } } } if (!empty($_REQUEST['sa']) && $_REQUEST['sa'] == 'thankyou') echo ' <div class="information">Thank you for purchasing this product. We sent you an email with your details and link to download this software.<br />'; echo ' <div class="cat_bar boardframe"> <h3 class="catbg"> PayPal Test Sale </h3> </div> <div class="roundframe blockframe"> This is just a Test Selling Page to be sure that the PayPal electronic downloads actually work! </div> <span class="lowerframe"><span><!-- // --></span></span> <br /> <div class="cat_bar boardframe"> <h3 class="catbg"> Purchase at 0.01 USD </h3> </div> <div class="roundframe blockframe"> <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top"> <input type="hidden" name="cmd" value="_s-xclick"> <input type="hidden" name="hosted_button_id" value="FJAGXAC7GCFSY"> <input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_paynow_SM.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!" style="border: none; background: none;"> <img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1"> </form> </div> <span class="lowerframe"><span><!-- // --></span></span> <br class="clear" />'; 

The ipnlistener.php file contains the following code:

 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, 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() */ 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 */ 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 200 if Successful */ public function getResponseStatus() { return $this->response_status; } /** * Get Text Report */ 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) { if (!empty($_POST)) { $this->post_data = $_POST; $encoded_data .= '&'.file_get_contents('php://input'); } else { throw new Exception("No POST data found."); } } else { $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."); } } 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."); } } } 

There are no errors in the error_log file (this file is actually created if an error occurs, so it does not even exist). And he never adds anything to the database. I know that item_name correct on the PayPal server and should match in the database table.

There seems to be a problem with ipnlistener or how is it related to it? Or what comes back from him?

Is there a way to check the return value from the ipnlistener.php file exactly? So can I really see it? Maybe if I could even see what will be sent to the following URL (from Paypal): http://mydomain.com/index.php?page=paypaltest;sa=paypal_verify

I am trying to track this so that I can pinpoint where he is mistaken ... Any ideas?

Perhaps this has something to do with this line in cURL:

 curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__)."/cert/api_cert_chain.crt"); 

I have never heard of installing an SSL certificate in the actual server file structure.

In addition, it is possible because in notify_url it has index.php , which adds additional headers. If so, can I somehow wash everything from the file before it captures the encoded data from PayPal and returns it back to paypal for verification? Thus, it is exactly the same as when PayPal sent it to notify_url .

I do not understand why it is so difficult! I am pulling my hair out on it now for 7 consecutive days, almost not sleeping!

I know this: cURL is enabled on my server. If I go to the actual notify_url page, it will be BLANK (like a completely blank page), which I believe is correct. When viewing the notification history, he says: "Sent" and "HTTP response code" 200.

I don’t know this: where is this happening? Where exactly does the PayPal notice appear? Why does this not apply to my database? I get no errors anywhere ...

+4
source share
3 answers

I found the problem, OMG, it was in the certification file:

The following simply changed:

 curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__)."/cert/api_cert_chain.crt"); 

The actual file path on my server, starting with / home /

curl_setopt ($ ch, CURLOPT_CAINFO, "FULL FILE PATH / cert / api_cert_chain.crt");

OMG I'm stupid! Works great! Yayyy!

+5
source

You should use the feedback URL as https://ipnpb.paypal.com/cgi-bin/webscr instead of https://www.paypal.com/cgi-bin/webscr for live.

+3
source

I struggled with this for a while, and it turned out to be my firewall.

During testing, I added two rules that let HTTPS go to www.paypal.com and www.sandbox.paypal.com, and it seemed to work fine. Looking back, it was just because the sandbox only has one IP address!

So, when I added www.paypal.com to the iptables firewall, he decided on the IP then, but, of course, later the IP that I received from the DNS was different, so firwall started to block.

As @Latha says, you should use ipnpb.paypal.com instead of www.paypal.com. This is because they list the IP addresses for this domain, so you can add them to the firewall specification. They are currently ...

 173.0.84.8 173.0.84.40 173.0.88.8 173.0.88.40 

They are soon (March 25, 2014) going to add a lot more, here you can find a complete list ...

https://ppmts.custhelp.com/app/answers/detail/a_id/92

0
source

Source: https://habr.com/ru/post/1490000/


All Articles