2013-09-21 49 views
4

我目前正在開發針對開源論壇軟件的修改。此修改允許用戶通過該論壇軟件進行捐贈。Paypal IPN問題 - 未處理一些付款

但是,最近用戶報告了可能由我的代碼引起的問題。我使用另一個開源庫來處理IPN連接 - An IPN Listener PHP class

誰報告此問題已得到了以下電子郵件用戶:

你好<My Name>

請檢查您的服務器處理貝寶即時付款 通知(IPN)。發送到 以下網址即時付款通知是失敗:

http://www.MySite.com/donate/handler.php

如果不認識到這一點的網址,您可以使用所代表您使用IPN服務提供商 。請通過以上信息與您的服務提供商 聯繫。如果問題仍然存在,您的帳戶可能會禁用IP地址爲 。

感謝您對此問題的及時關注。

真誠的,貝寶

我擔心的問題來自於我的身邊,所以我要看看這一點,並確保。

我輕微修改了IPN Listener腳本,這導致我認爲我的修改導致了此問題。貝寶最近也有一些變化,可能會引發這個問題。

這是how類的樣子瞬間:

/** 
* PayPal IPN Listener 
* 
* A class to listen for and handle Instant Payment Notifications (IPN) from 
* the PayPal server. 
* 
* https://github.com/Quixotix/PHP-PayPal-IPN 
* 
* @package PHP-PayPal-IPN 
* @author  Micah Carrick 
* @copyright (c) 2011 - Micah Carrick 
* @version 2.0.5 
* @license http://opensource.org/licenses/gpl-license.php 
* 
* This library is originally licensed under GPL v3, but I received 
* permission from the author to use it under GPL v2. 
*/ 
class ipn_handler 
{ 
    /** 
    * 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 = 60;  

    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) 
    { 
     global $user; 

     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_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($user->lang['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) 
    { 
     global $user; 

     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($user->lang['FSOCKOPEN_ERROR'] . "[$errno] $errstr"); 
     } 

     $header = "POST /cgi-bin/webscr HTTP/1.1\r\n"; 
     $header .= "Content-Length: " . strlen($encoded_data) . "\r\n"; 
     $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; 
     $header .= "Host: " . $this->getPaypalHost() . "\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 ipn_handler::SANDBOX_HOST; 
     } 
     else 
     { 
      return ipn_handler::PAYPAL_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) 
    { 
     global $user; 

     $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($user->lang['NO_POST_DATA']); 
      } 
     } 
     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($user->lang['INVALID_RESPONSE'] . $this->response_status); 
     } 

     if (strpos(trim($this->response), "VERIFIED") !== false) 
     { 
      return true; 
     } 
     elseif (trim(strpos($this->response), "INVALID") !== false) 
     { 
      return false; 
     } 
     else 
     { 
      throw new Exception($user->lang['UNEXPECTED_ERROR']); 
     } 
    } 

    /** 
    * Require Post Method 
    * 
    * Throws an exception and sets a HTTP 405 response header if the request 
    * method was not POST. 
    */  
    public function requirePostMethod() 
    { 
     global $user; 

     // require POST requests 
     if ($_SERVER['REQUEST_METHOD'] && $_SERVER['REQUEST_METHOD'] != 'POST') 
     { 
      header('Allow: POST', true, 405); 
      throw new Exception($user->lang['INVALID_REQUEST_METHOD']); 
     } 
    } 
} 

是否有任何問題與此腳本,這是造成這個問題?

P.S:URL donate/handler.php的確是IPN處理程序/偵聽器文件,所以它是一個公認的URL。

+0

我沒有這個東西的工作,但它似乎不是你奇怪的是,*用戶*將獲得電子郵件。你確定地址配置正確嗎?貝寶是否不提供有關出錯的更多信息的日誌?他們應該。 –

+0

對不起,我不明白:我發佈了這個修改和**用戶**是他自己的論壇的所有者,他是誰報告了這個問題。我沒有使用自己的修改,所以我不應該收到任何電子郵件。 – Aborted

回答

1

我的代碼的確存在一個問題。我做了一些鑄造錯誤,導致IPN失敗。此問題已得到解決。

感謝大家的幫助。

3

檢查您的Web服務器日誌。這將告訴你當IPN腳本被擊中時會出現什麼結果,並且由於失敗,你必須得到某種500內部服務器錯誤。日誌會給你的錯誤信息,你通常會在屏幕上看到,像一個語法錯誤,行號等

我喜歡故障排除做的,也就是通過構建一個創建自己的模擬器將操作設置爲我的IPN偵聽器的URL的基本HTML表單。使用您希望從IPN獲得的名稱/值添加隱藏字段,然後可以將其加載到瀏覽器中並直接提交,以便您可以在屏幕上查看結果。你可能會發現你的代碼有錯誤導致腳本無法完成。

請記住,當以這種方式測試時,數據不是來自PayPal,所以它不會被驗證。您需要確保您的代碼邏輯設置爲相應地處理。

一旦您能夠以一種平穩的方式運行測試,我會使用PayPal IPN偵聽器作爲另一個確認,然後您可以放心,您已經解決了這個問題。

+0

謝謝你的提示。我一定會通過他們檢查情況。無論如何,由於您似乎熟悉Paypal IPN代碼,您是否介意通過我提供的課程來閱讀並告訴我是否有問題?我很感謝你的幫助。 – Aborted

+4

對不起,我不想浪費時間通過代碼試圖找到大海撈針。這就是運行測試的目的。 ;) –

2

我強烈建議在Paypal開發人員網站中使用IPN simulator。它可以爲你建立一個IPN請求,並將它發送給你的服務器並報告它得到的結果。

1

查看您的PayPal帳戶下的日誌,它會顯示發送的IPN請求列表以及結果。如果您遇到一些主要問題,可以使用提供的GET字符串來測試用例。

6

對於調試部分

您也可以檢查您的貝寶IPN的狀態。

我的賬戶>歷史> IPN歷史。

它將列出發送到您的服務器的所有IPN。你會看到他們每個人的狀態。它可能有幫助。但正如安德魯安傑爾所說,看看你的日誌。

對於PHP的一部分

貝寶提供了一個地段善良的東西在他們的Github。你應該仔細看看。

他們有a dead simple IPNLister sample,你應該使用(而不是自定義的 - 即使它看起來不錯)。它使用Paypal自身的內置功能。我個人也使用它。 你不應該重新發明輪子 :)

<?php 
require_once('../PPBootStrap.php'); 
// first param takes ipn data to be validated. if null, raw POST data is read from input stream 
$ipnMessage = new PPIPNMessage(null, Configuration::getConfig()); 
foreach($ipnMessage->getRawData() as $key => $value) { 
    error_log("IPN: $key => $value"); 
} 

if($ipnMessage->validate()) { 
    error_log("Success: Got valid IPN data");  
} else { 
    error_log("Error: Got invalid IPN data"); 
} 

正如你所看到的,這很簡單。

我用它在略有不同的方式:

$rawData = file_get_contents('php://input'); 
$ipnMessage = new PPIPNMessage($rawData); 

$this->forward404If(!$ipnMessage->validate(), 'IPN not valid.'); 

$ipnListener = new IPNListener($rawData); 
$ipnListener->process(); 

IPNListener類是定製的對我說:它不處理做什麼用的IPN。它解析響應,並根據國家做動作:

function __construct($rawData) 
{ 
    $rawPostArray = explode('&', $rawData); 
    foreach ($rawPostArray as $keyValue) 
    { 
    $keyValue = explode ('=', $keyValue); 
    if (count($keyValue) == 2) 
    { 
     $this->ipnData[$keyValue[0]] = urldecode($keyValue[1]); 
    } 
    } 

    // log a new IPN and save in case of error in the next process 
    $this->ipn = new LogIpn(); 
    $this->ipn->setContent($rawData); 
    $this->ipn->setType(isset($this->ipnData['txn_type']) ? $this->ipnData['txn_type'] : 'Not defined'); 
    $this->ipn->save(); 
} 

/** 
* Process a new valid IPN 
* 
*/ 
public function process() 
{ 
    if (null === $this->ipnData) 
    { 
    throw new Exception('ipnData is empty !'); 
    } 

    if (!isset($this->ipnData['txn_type'])) 
    { 
    $this->ipn->setSeemsWrong('No txn_type.'); 
    $this->ipn->save(); 

    return; 
    } 

    switch ($this->ipnData['txn_type']) 
    { 
    // handle statues 
    } 
}