简体   繁体   English

PayPal订阅付款和IPN重复处理

[英]PayPal subscription payments and recurring IPN handling

I need advice on PayPal subscription payments IPN handling. 我需要有关贝宝订阅付款IPN处理的建议。 I have written an IPN handler/listener based on PayPal code samples. 我已经基于PayPal代码示例编写了IPN处理程序/侦听器。 The listener copies the IPN message back to PayPal preceded by cmd=_notify-validate. 侦听器将IPN消息复制回PayPal,然后再发送cmd = _notify-validate。 I can setup a subscription with no problems, ie, user enters their details and this, together with their order information is passed to PayPal where they log in to their account and agree subscription. 我可以毫无问题地设置订阅,即,用户输入他们的详细信息,并将其连同其订单信息一起传递到PayPal,在此他们登录其帐户并同意订阅。 On successful response from PayPal the order is confirmed and my database updated. 在贝宝(PayPal)成功响应后,订单即被确认并且我的数据库已更新。 The problem I am having is the recurring payment notification. 我遇到的问题是定期付款通知。 I have set up the subscriptions to occur daily via the PayPal Sandbox and each time PayPal advises the customer payment pending the customer logs in to their PayPal account and accepts payment, which results in another IPN confirming payment complete. 我已将订阅设置为每天通过PayPal沙盒进行,并且每次PayPal建议客户付款,直到客户登录其PayPal帐户并接受付款,这将导致另一个IPN确认付款完成。 I am posting back the IPN messages preceded by the validate request and receiving a null response from PayPal Sandbox. 我正在回发IPN消息,该IPN消息之前是validate请求,并且从PayPal Sandbox收到空响应。 I am expecting to receive “VERIFIED” or “INVALID” as per PayPal documentation? 根据PayPal文档,我希望收到“已验证”或“无效”吗? However, the PayPal response to the returned message is “” or null? 但是,贝宝对返回消息的响应是“”还是空? The IPN validation code looks like this and uses “ https://www.sandbox.paypal.com/cgi-bin/webscr ” as the URL: IPN验证代码如下所示,并使用“ https://www.sandbox.paypal.com/cgi-bin/webscr ”作为URL:

  $url_parsed=parse_url($this->paypal_url);

  // generate the post string from the _POST vars and load the _POST vars into an array
  $post_string = "cmd=_notify-validate"; // start IPN response with validate command
  foreach ($_POST as $field=>$value) {
     $post_string .= '&';
     $this->ipn_data["$field"] = $value;
     $post_string .= $field.'='.urlencode(stripslashes($value));
  }

  // open the connection to PayPal
  $fp = fsockopen($url_parsed[host],443,$err_num,$err_str,30);

  if(!$fp) {

     // could not open the connection.  If logging is on, log the error message
     $this->last_error = "fsockopen error no. $errnum: $errstr";
     $this->log_ipn_results(false);
     return false;

  } else {

     // Post the data back to PayPal
     fputs($fp, "POST $url_parsed[path] HTTPS/1.1\r\n");
     fputs($fp, "Host: $url_parsed[host]\r\n");
     fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
     fputs($fp, "Content-length: ".strlen($post_string)."\r\n");
     fputs($fp, "Connection: close\r\n\r\n");
     fputs($fp, $post_string . "\r\n\r\n");

     // loop through the response from the server and append to variable
     while(!feof($fp)) {
        $this->ipn_response .= fgets($fp, 1024);
     }

     fclose($fp); // close connection

  /* PayPal sends a single word back, which is VERIFIED if the message originated with PayPal
     or INVALID if there is any discrepancy with what was originally sent */
  if (strcmp ("INVALID", $this->ipn_response) != 0) {
  // The above is a work around to address null response! For now!
     // Valid IPN transaction.
     $this->log_ipn_results(true);
     return true;

  } else {

     // Invalid IPN transaction.  Check the log for details.
     $this->last_error = 'IPN Validation Failed.';
     $this->log_ipn_results(false);
     return false;
  }

I have tested the timeout and believe the process is well within the time limit of 30 seconds, and confirmed the structure of the $post_string replicates the original message with cmd at the start. 我已经测试了超时时间,并认为该过程很好地在30秒的时间限制内,并确认$ post_string的结构在开始时使用cmd复制了原始消息。 The only other issue I can think of is the return posting of the IPN vars is sent from a page secured by a SSL certificate? 我能想到的唯一另一个问题是IPN var的返回发布是从受SSL证书保护的页面发送的吗? Regardless, unless I am missing something I do not believe that the PayPal Sandbox is actually responding hence null result? 无论如何,除非我丢失了某些东西,否则我不认为PayPal Sandbox实际上在响应,因此结果为空? Any advice or guidance would be greatly appreciated as I am relying on multiple daily subscription payment periods to test this via Sandbox. 任何建议或指导都将不胜感激,因为我依靠多个每日订阅付款期来通过Sandbox进行测试。

Don't strip slashes from the values when you post back the parameters for verification of the transaction. 回发参数以验证交易时,请不要从值中去除斜线。 They must be posted back as received. 它们必须按收到的方式回发。

I implemented PHP Curl handler as follows: 我实现了PHP Curl处理程序,如下所示:

<?php

   function validate_ipn() {

      // CONFIG: Enable debug mode. This means we'll log requests into 'ipn.log' in the same directory.
      // Especially useful if you encounter network errors or other intermittent problems with IPN (validation).
      // Set this to 0 once you go live or don't require logging.
      define("DEBUG", 1);
      define("LOG_FILE", "../log/ipn.log");

      // Set to 0 once you're ready to go live
      define("USE_SANDBOX", 1);

      // Read POST data
      // reading posted data directly from $_POST causes serialization
      // issues with array data in POST. Reading raw POST data from input stream instead.
      $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)
                      $myPost[$keyval[0]] = urldecode($keyval[1]);
      }
      // read the post from PayPal system and add 'cmd'
      $req = 'cmd=_notify-validate';
      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 IPN data back to PayPal to validate the IPN data is genuine
      // Without this step anyone can fake IPN data
      if (USE_SANDBOX == true) {
         $paypal_url = "https://www.sandbox.paypal.com/cgi-bin/webscr";
      } else {
         $paypal_url = "https://www.paypal.com/cgi-bin/webscr";
      }

      $ch = curl_init($paypal_url);
      if ($ch == FALSE) {
         return FALSE;
      }

      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_SSL_VERIFYPEER, 1);
      curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
      curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);

      if (DEBUG == true) {
         curl_setopt($ch, CURLOPT_HEADER, 1);
         curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
      }

      // CONFIG: Optional proxy configuration
      //curl_setopt($ch, CURLOPT_PROXY, $proxy);
      //curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);

      // Set TCP timeout to 30 seconds
      curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
      curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));

      // CONFIG: Please download 'cacert.pem' from "http://curl.haxx.se/docs/caextract.html" and set the directory path
      // of the certificate as shown below. Ensure the file is readable by the webserver.
      // This is mandatory for some environments.

      //$cert = __DIR__ . "./cacert.pem";
      //curl_setopt($ch, CURLOPT_CAINFO, $cert);

      $res = curl_exec($ch);
      if (curl_errno($ch) != 0) { // cURL error
         if (DEBUG == true) {
            error_log(date('[Y-m-d H:i e] '). "Can't connect to PayPal to validate IPN message: " . curl_error($ch) . PHP_EOL, 3, LOG_FILE);
         }
         curl_close($ch);
         exit;

      } else {
         // Log the entire HTTP response if debug is switched on.
         if (DEBUG == true) {
            error_log(date('[Y-m-d H:i e] '). "HTTP request of validation request:". curl_getinfo($ch, CURLINFO_HEADER_OUT) ." for IPN payload: $req" . PHP_EOL, 3, LOG_FILE);    
            error_log(date('[Y-m-d H:i e] '). "HTTP response of validation request: $res" . PHP_EOL, 3, LOG_FILE);

            // Split response headers and payload
            list($headers, $res) = explode("\r\n\r\n", $res, 2);
         }
         curl_close($ch);
      }

      // Inspect IPN validation result and act accordingly
      if (strcmp ($res, "VERIFIED") == 0) {

         if (DEBUG == true) {
            error_log(date('[Y-m-d H:i e] '). "Verified IPN: $req ". PHP_EOL, 3, LOG_FILE);
         }
         return true;
      } else if (strcmp ($res, "INVALID") == 0) {
         // log for manual investigation
         // Add business logic here which deals with invalid IPN messages
         if (DEBUG == true) {
            error_log(date('[Y-m-d H:i e] '). "Invalid IPN: $req" . PHP_EOL, 3, LOG_FILE);
         }
         return false;
      }

   }

?>

and it worked! 而且有效!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM