Paypal ipn 未验证:无效
Posted
技术标签:
【中文标题】Paypal ipn 未验证:无效【英文标题】:Paypal ipn not verifying: INVALID 【发布时间】:2016-06-26 23:54:55 【问题描述】:我已经设置了 paypal ipn,握手有效:我可以去使用 paypal 支付。但是,奇怪的是,它不会返回验证并返回无效。
付款也完成了吗?
paypal按钮代码:
<form action="https://www.paypal.com/cgi-bin/webscr" name="gateway_form" method="post">
<input type="hidden" value="2" name="rm">
<input type="hidden" value="_xclick" name="cmd">
<input type="hidden" value="<?=base_url()?>stationery/paypal_accepted" name="return">
<input type="hidden" value="<?=base_url()?>stationery/paypal_cancelled" name="cancel_return">
<input type="hidden" value="<?=base_url()?>stationery/paypal_ipn" name="notify_url">
<input type="hidden" value="vish90@hotmail.com" name="business">
<input type="hidden" value="GBP" name="currency_code">
<input type="hidden" value="<?=$basket_total?>" name="amount">
<input type="hidden" value="John Browns Stationery Order" name="item_name">
<input type="hidden" value="<?=$random_invoice_no?>" name="invoice">
<input type="hidden" value="<?=$user_id?>" name="custom">
<input class="btn btn-default" type="submit" value="Checkout by Paypal">
</form>
ipn监听代码:
<?php
/**
* 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) 2012 - Micah Carrick
* @version 2.1.0
*/
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 = false;
/**
* 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';
const SANDBOX_HOST = 'www.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()
*
* 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)
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
*
* 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)
$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("No POST data found.");
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("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.");
/**
* Require Post Method
*
* Throws an exception and sets a HTTP 405 response header if the request
* method was not POST.
*/
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.");
以及 ipn 处理程序代码:
function paypal_ipn()
/**
* PHP-PayPal-IPN Example
*
* This shows a basic example of how to use the IpnListener() PHP class to
* implement a PayPal Instant Payment Notification (IPN) listener script.
*
* For a more in depth tutorial, see my blog post:
* http://www.micahcarrick.com/paypal-ipn-with-php.html
*
* This code is available at github:
* https://github.com/Quixotix/PHP-PayPal-IPN
*
* @package PHP-PayPal-IPN
* @author Micah Carrick
* @copyright (c) 2011 - Micah Carrick
* @license http://opensource.org/licenses/gpl-3.0.html
*/
/*
Since this script is executed on the back end between the PayPal server and this
script, you will want to log errors to a file or email. Do not try to use echo
or print--it will not work!
Here I am turning on PHP error logging to a file called "ipn_errors.log". Make
sure your web server has permissions to write to that file. In a production
environment it is better to have that log file outside of the web root.
*/
ini_set('log_errors', true);
ini_set('error_log', base_url() . 'add_ons/ipn/ipn_errors.log');
// instantiate the IpnListener class
include('./add_ons/ipn/ipnlistener.php');
$listener = new IpnListener();
/*
When you are testing your IPN script you should be using a PayPal "Sandbox"
account: https://developer.paypal.com
When you are ready to go live change use_sandbox to false.
*/
$listener->use_sandbox = false;
/*
By default the IpnListener object is going going to post the data back to PayPal
using cURL over a secure SSL connection. This is the recommended way to post
the data back, however, some people may have connections problems using this
method.
To post over standard HTTP connection, use:
$listener->use_ssl = false;
To post using the fsockopen() function rather than cURL, use:
$listener->use_curl = false;
*/
/*
The processIpn() method will encode the POST variables sent by PayPal and then
POST them back to the PayPal server. An exception will be thrown if there is
a fatal error (cannot connect, your server is not configured properly, etc.).
Use a try/catch block to catch these fatal errors and log to the ipn_errors.log
file we setup at the top of this file.
The processIpn() method will send the raw data on 'php://input' to PayPal. You
can optionally pass the data to processIpn() yourself:
$verified = $listener->processIpn($my_post_data);
*/
try
$listener->requirePostMethod();
$verified = $listener->processIpn();
mail('vish90@hotmail.com', 'what is the verified value?', $verified);
catch (Exception $e)
mail('vish90@hotmail.com', 'error', $e->getMessage());
error_log($e->getMessage());
exit(0);
/*
The processIpn() method returned true if the IPN was "VERIFIED" and false if it
was "INVALID".
*/
if ($verified)
/*
Once you have a verified IPN you need to do a few more checks on the POST
fields--typically against data you stored in your database during when the
end user made a purchase (such as in the "success" page on a web payments
standard button). The fields PayPal recommends checking are:
1. Check the $_POST['payment_status'] is "Completed"
2. Check that $_POST['txn_id'] has not been previously processed
3. Check that $_POST['receiver_email'] is your Primary PayPal email
4. Check that $_POST['payment_amount'] and $_POST['payment_currency']
are correct
Since implementations on this varies, I will leave these checks out of this
example and just send an email using the getTextReport() method to get all
of the details about the IPN.
*/
foreach($this->input->post() as $key => $value)
$data[$key] = $value;
$this->ipn_verified($data);
mail('vish90@hotmail.com', 'Verified IPN', $listener->getTextReport());
else
/*
An Invalid IPN *may* be caused by a fraudulent transaction attempt. It's
a good idea to have a developer or sys admin manually investigate any
invalid IPN.
*/
mail('vish90@hotmail.com', 'IPN is invalid', $listener->getTextReport());
更新:
我创建了一个文本报告:
HTTP/1.1 200 OK
Server: Apache
X-Frame-Options: SAMEORIGIN
Paypal-Debug-Id: f12b436ba34fc
Cache-Control: max-age=0, no-cache, no-store, must-revalidate
Pragma: no-cache
Content-Type: text/html; charset=UTF-8
DC: dcg12-origin-www-2.paypal.com
Date: Tue, 15 Mar 2016 11:35:50 GMT
Content-Length: 7
Connection: close
Set-Cookie: cwrClyrK4LoCV1fydGbAxiNL6iG=AJGAy4asx-VKRgNsi6tOcBzPvUpJE9YTQbT7LjGvLd-hcG94uHT60TPzfOxn7psnIrKUTpm7Uhx1EuFm_dN5UmgmCIBhkbYG3UFQLQ0kAyJNq-4F2YHhtPqLlJJ-nAPKKNHp4-9na-9KedRj8tKnSadxd-rkQffpEdJ3a-olWxDgCXbiCS3m8GUxs9whYlM4j9fC44XtdKa6tVAEyxnlXvTZT_tPjsmRusPIwe508NVz8mOiTYDIjWWgYTcLgb6N6__WHncNHihk4vD-VA3PVWhBIH4W0Zku-29-r2vcHZILfLlowv-b3s6jqu4HHdsTV1vkBTXcRFHQKeHl6INVuZ8AUa10N30il814CCVMQrMMf9oHtoQ2HLDls2g24vFuiQa1w5U-R-v_MhPsJj5Sgdtz8t5K2Dl3Wtp_-h8b2MEI8Sn-YzrdtpfYzy8; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: cookie_check=yes; expires=Fri, 13-Mar-2026 11:35:49 GMT; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: navcmd=_notify-validate; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: navlns=0.0; expires=Thu, 15-Mar-2018 11:35:49 GMT; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: X-PP-SILOVER=name%3DLIVE11.WEB.1%26silo_version%3D880%26app%3Dappdispatcher%26TIME%3D2515789654; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: X-PP-SILOVER=; Expires=Thu, 01 Jan 1970 00:00:01 GMT
Set-Cookie: AKDC=dcg12-origin-www-2.paypal.com; expires=Tue, 15-Mar-2016 12:05:50 GMT; path=/; secure
Strict-Transport-Security: max-age=63072000
INVALID
--------------------------------------------------------------------------------
mc_gross 30.60
invoice pDz0Nun6H29xdGuI
protection_eligibility Ineligible
address_status confirmed
payer_id 9SU8DB98AZDKU
tax 0.00
address_street 1 Main Terrace
payment_date 04:35:46 Mar 15, 2016 PDT
payment_status Pending
charset windows-1252
address_zip W12 4LQ
first_name Vish
address_country_code GB
address_name Vish Patel's Test Store
notify_version 3.8
custom 501
payer_status verified
address_country United Kingdom
address_city Wolverhampton
quantity 1
verify_sign AzFmRN2alSXsVcRQ-bkJDjsW0iFTAt2a-oBGJoFYjl.qfx0QiRc9VT6j
payer_email testee@test.com
txn_id 37U96690JH140125B
payment_type instant
payer_business_name Vish Patel's Test Store
last_name Patel
address_state West Midlands
receiver_email TEST@test.com
pending_reason unilateral
txn_type web_accept
item_name ipn test Order
mc_currency GBP
item_number
residence_country GB
test_ipn 1
handling_amount 0.00
transaction_subject
payment_gross
shipping 0.00
ipn_track_id d243782521d47
另外,查看ipn历史日志,令人担忧的是没有http状态码,它的空白和状态显示禁用。
【问题讨论】:
付款没问题。 IPN 无效,或者至少没有通过 PayPal 的服务器正确验证。看起来你正在使用 fsock。请改用 cURL。还要确保您使用 https:// 和 PayPal 的回调 URL。 @AndrewAngell 很抱歉回复晚了,但碰巧知道为什么 ipn 一直返回无效,仅在上个月左右之前使用还可以并返回验证? 可能与 $this->use_ssl() 有关。 PayPal 将不再接受 POST 回他们的 http:// 地址,因此取决于您是访问沙箱(已经调整)还是实时服务器(稍后将调整),您可能会遇到现在。 @AndrewAngell 我的印象是,http 仍然会受到支持,我已经尝试过沙箱并进行直播。另外,我收到 http ok 消息,所以它正在被发送和接收:只是没有经过验证返回 @AndrewAngell 我一直在深入研究这个问题,我在沙盒测试中得到了 AzFmRN2alSXsVcRQ-bkJDjsW0iFTAt2a-oBGJoFYjl.qfx0QiRc9VT6j 的验证信号。另外,查看 ipn 历史日志,令人担忧的是没有 http 状态代码,它的空白和状态显示 disabled 可能是什么原因造成的? 【参考方案1】:商家帐户意外关闭了接收 ipn 消息。因此,登录此帐户并启用此选项即可正常工作。
【讨论】:
以上是关于Paypal ipn 未验证:无效的主要内容,如果未能解决你的问题,请参考以下文章
Paypal IPN 获得空白确认(应为“已验证”或“无效”)
PHP/CodeIgniter PayPal IPN 无效验证