使用 HTTPS 的 Paypal IPN 验证回传

Paypal IPN Verification Postback with HTTPS

根据新的安全要求(2016、2017 和 2018),在 "IPN" 期间,服务器和 Paypal 之间的交换似乎需要 HTTPS。 This question is linked to this subject and also .

我们应该如何修改这个 PHP IPN 代码?

$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Host: www.paypal.com:80\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";

$fp = fsockopen ('www.paypal.com', 80, $errno, $errstr, 30);

$req = 'cmd=_notify-validate';

...

fputs ($fp, $header . $req);

www.paypal.com 的两次出现替换为 https://www.paypal.com 就足够了吗?

另外,我的店铺网站不是HTTPS是不是有问题,这个连接会被拒绝吗?


以下是从 Paypal 收到的部分电子邮件:


编辑(2018/06/22),这是应用接受的答案代码后的实际 IPN 代码。奇怪的是,我仍然得到:"IPN Verification postback to HTTPS. Update needed: YES"。所以这意味着下面的代码仍然不是 100% 兼容 HTTPS。为什么?

<?php
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value) {
  $value = trim(urlencode(stripslashes($value)));
  $req .= "&$key=$value";
}

$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Host: www.paypal.com\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen('tls://www.paypal.com', 443, $errno, $errstr, 30);

// variables
$item_name = $_POST['item_name'];
$business = $_POST['business'];
$item_number = $_POST['item_number'];
$payment_status = $_POST['payment_status'];
// and many more

if (!$fp)
{
  // HTTP ERROR
} else
{
  fputs ($fp, $header . $req);
  while (!feof($fp))
  {
    $res = fgets ($fp, 1024);
    if (strcmp ($res, "VERIFIED") == 0)
    {
        // send email to customer, etc.
    }
  }
  fclose ($fp);
}
?>

hostname

If OpenSSL support is installed, you may prefix the hostname with either ssl:// or tls:// to use an SSL or TLS client connection over TCP/IP to connect to the remote host.

http://www.php.net/fsockopen

端口也需要更改为 443。所以:

$header .= "Host: www.paypal.com\r\n";
...
$fp = fsockopen('ssl://www.paypal.com', 443, $errno, $errstr, 30);
...
fputs ($fp, $header . $req);

https:// 将不起作用,因为您打开了一个 套接字 ,这是一种低级传输。 HTTP 是其之上的应用程序级协议,套接字不知道或不关心它。在套接字级别,它是一个 TLS 连接。

Also, is the fact my shop website is not HTTPS a problem, will this connection be refused?

浏览器与您的服务器有什么样的连接是无关紧要的,没有人知道。您正在打开一个从 PHP 程序到 Paypal 的套接字,您也可以直接从命令行执行此操作,根本不涉及任何“HTTP”连接。

除了 deceze 的回答,PayPal 的更新现在要求您使用 ipnpb.paypal.com

尝试改用此代码。

$fp = fsockopen('ssl://ipnpb.paypal.com', "443", $err_num, $err_str, 60);

我建议您放弃 fsocket,转而使用 curl。它更可靠(我认为)。我刚刚更新了 ipn.php 中的 curl 组件,以符合 HTTP1/1 和 TLS1.2 (sslversion=6) 的 Paypals 要求 密码是

//NOW SEND IT ALL BACK TO PAYPAL AS CONFIRMATION//
// USING CURL AS PHP FSOCKOPEN IS LESS RELIABLE //
$curl_result=$curl_err='';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$paypal_url);
curl_setopt($ch, CURLOPT_CAINFO, "cacert.pem");
curl_setopt($ch, CURLOPT_SSLVERSION, 6);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/x-www-form-urlencoded", "Content-Length: " . strlen($req)));
curl_setopt($ch, CURLOPT_HEADER, 0);   
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$curl_result = @curl_exec($ch);
$curl_err = curl_error($ch);
curl_close($ch);

效果很好

除了我对 Curl 的评论之外,Paypal 开发人员还为 IPN 提供了几套源代码。他们提供 C++、Python、ASP、PHP 以及更多其他语言。您会在 github 上找到它们。所有 PHP 解决方案都使用 Curl。尽管有些上传是几年前的事,但他们一定知道 Paypal 会将他们的完整通信(用于 IPN)更新为 HTTPS。 即使使用一些较旧的软件包,只需添加 1 行代码即可将 SSL 版本设置为 6,旧代码也可以满足 Paypals 的新要求

https://github.com/paypal/ipn-code-samples 中的 link 是 Python、Ruby、Perl、Asp 和其他一些代码的汇编,包括 [=16] =]. PHP 组件的直接 link 位于 https://github.com/paypal/ipn-code-samples/tree/master/php 这是我使用的,因为它已经 HTTP1/1 和 TLS1.2 "ready",因此符合 Paypal 的新安全合规性。 其他 github 个样本很少,有些已经超过 6 年了,但这是最新的

您可能还应该根据 PayPal 的最新要求,现在将 HTTP/1.0 更改为 HTTP/1.1。那就是:

$header .= "POST /cgi-bin/webscr HTTP/1.1\r\n";