PayPal IPN 更新以适应 HTTP1.1 - 对等方重置连接

Posted

技术标签:

【中文标题】PayPal IPN 更新以适应 HTTP1.1 - 对等方重置连接【英文标题】:PayPal IPN Updates to Accommodate HTTP1.1 - Connection reset by peer 【发布时间】:2013-01-08 12:42:45 【问题描述】:

我们一直在努力根据 seom 最近的变化更新我们的 PayPal IPN 脚本。以下是 PayPal 的部分说明...

您需要在 2013 年 2 月 1 日之前采取行动 您需要更新 您的 IPN 和/或 PDT 脚本使用 HTTP 1.1,并包括“主机: www.paypal.com”和“连接:关闭”IPN 中的 HTTP 标头和 PDT 脚本。

我们这样做了,但 IPN 失败了。 PayPal 商家技术服务要求我们转移到 SLL 连接。以前我们与 PayPal 的连接是基于...

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

现在是……

fsockopen ('ssl://ipnpb.paypal.com', 443, $errno, $errstr, 30)

我们必须克服主机配置的 SSL 问题才能使其正常工作,但它现在可以建立连接。然而,IPN 要求我们发回 PayPal 以接收“已验证”通知,此时脚本可以根据确认的付款进行本地处理。

这就是失败的地方。

示例 PayPal 脚本的行是:

if (!$fp) 
    // HTTP ERROR

 else 
    fputs ($fp, $header . $req);
    while (!feof($fp)) 
        $res = fgets ($fp, 1024);

        if (strcmp (trim($res), "VERIFIED") == 0)   ... rest of script

但响应返回空白。我插入了一个诊断程序,通过电子邮件向我发送每个阶段 $res 的值。如果我使用旧脚本执行此操作,它会返回一个“已验证”值就好了。几个网站上的旧脚本多年来一直很稳定。我现在已经在两个不同服务器上的两个不同站点上尝试了这些新更新,结果是一样的。

查看 Apache 日志时,我看到此错误:

php 警告:fgets() [function.fgets]: SSL: Connection reset by peer in /home/[sitename]/public_html/[IPN scriptname].php on line 145

因此,PayPal 似乎在未发送已验证响应的情况下关闭了连接。然而,PayPal 支持否认这是一个问题,因为连接首先是打开的。

我花了几天时间试图解决这个问题。我还有 2 月 1 日的最后期限,在那之后 PayPal 说我的旧脚本可能无法正常工作,但新脚本也不能。

其他人有这方面的经验吗?

【问题讨论】:

PayPal 是否告诉您切换到 HTTPS?您是否尝试仅使用 PayPal 实际所说的两个更改来运行旧脚本? 使用 HTTP 1.1包括“主机:www.paypal.com”和“连接:关闭”HTTP 标头。更改为 HTTP 1.1 本身并不需要更改为 HTTPS。 我看到了相同的回复,我也放置了代码以通过电子邮件将回复的内容发送给我。我期待“已验证”或“无效”,但没有返回任何内容)。我正在使用 cURL。我们有同样的问题,如果我第一次来,我会分享解决方案。 对此有任何更新吗? 2014 年同样的问题。 同样的问题,有谁知道是什么原因造成的? 【参考方案1】:

终于找到答案了! Paypal IPN Script, issue with feof and fgets

删除 while(!feof($fp) ... 并将其更改为 $res = stream_get_contents($fp, 1024); 并添加 $header .= "Conection: Close" 以确保贝宝关闭连接。

【讨论】:

【参考方案2】:

如果其他人有同样的问题,CURL 似乎是 PayPal 的 IPN 推荐选择。

在 Github 上查看代码示例:https://github.com/paypal/ipn-code-samples/blob/master/paypal_ipn.php

【讨论】:

+1 我在这里尝试了其他答案,不再出现“连接重置”错误,但代码没有正确解析响应,直到我使用 PayPal 的 Github 中的 CURL 切换到代码。 【参考方案3】:

在这个问题上挣扎了几个小时。我没看错。

首先,标题应该是正确的。

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

其次,缓冲区应该足够大以接收来自 Paypal 的整个消息。原始示例代码使用 1024,但结果被截断。

$res = stream_get_contents($fp, 2048);

两者之后,结果返回良好。

【讨论】:

【参考方案4】:

更新日期:2016 年 1 月 7 日 我注意到以下内容非常适用于 tls://www.sandbox.paypal.com 但是当你上线 tls://www.paypal.com 连接被拒绝! (它不起作用)我发现问题出在标题中,沙箱和生产级别的 live paypal 需要不同的标题!这是一个错误,但为了让您在生产级别和沙箱中都能正常工作,请分别使用这些标头:

生产级别(实时 PayPal 标头):

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

沙盒 PayPal 标头:

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

* 下面的代码也是一样的,效果很好,只需分别替换header即可。* 这是其余的代码(和答案):

我在这里和那里检查了所有答案,以使其在 2016 年 1 月 5 日生效。 它们都有优点,但并不适用于整个画面。

首先,这里是实际工作完整代码,然后我将通过它:

<?php

// =========================================================================
// PayPal Official PHP Codes and Tutorial:
// https://developer.paypal.com/webapps/developer/docs/classic/ipn/gs_IPN/
// =========================================================================

// Send an empty HTTP 200 OK response to acknowledge receipt of the notification 
header('HTTP/1.1 200 OK');

// Assign payment notification values to local variables
$item_name        = $_POST['item_name'];
$item_number      = $_POST['item_number'];
$payment_status   = $_POST['payment_status'];
$payment_amount   = $_POST['mc_gross'];
$payment_currency = $_POST['mc_currency'];
$txn_id           = $_POST['txn_id'];
$receiver_email   = $_POST['receiver_email'];
$payer_email      = $_POST['payer_email'];

// Build the required acknowledgement message out of the notification just received
$req = 'cmd=_notify-validate';               // Add 'cmd=_notify-validate' to beginning of the acknowledgement

foreach ($_POST as $key => $value)          
    // Loop through the notification NV pairs
    $value = urlencode(stripslashes($value));  // Encode these values
    $req  .= "&$key=$value";                   // Add the NV pairs to the acknowledgement



// Set up the acknowledgement request headers
// HTTP POST request
$header  = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$header .= "Host: www.sanbox.paypal.com\r\n";
$header .= "Accept: */*\r\n";
$header .= "Connection: Close\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "\r\n";

// Open a socket for the acknowledgement request
$fp = fsockopen('tls://www.sandbox.paypal.com', 443, $errno, $errstr, 30);

// Send the HTTP POST request back to PayPal for validation
fputs($fp, $header . $req);

// Log the transaction:
file_put_contents("paypal_post.txt",  date('Y-m-d H:i:s')."\n".file_get_contents("php://input")."\n\n", FILE_APPEND);

// While not EOF
while (!feof($fp)) 
    // Get the acknowledgement response
    // $res = fgets($fp, 1024);  
    $res = stream_get_contents($fp, 1024);
    $responses = explode("\r\n", $res);
    foreach ($responses as $response) 
        if (strcmp ($response, "VERIFIED") == 0) 
            // Response contains VERIFIED - process notification

            // Authentication protocol is complete - OK to process notification contents

            // Possible processing steps for a payment include the following:

            // Check that the payment_status is Completed
            // Check that txn_id has not been previously processed
            // Check that receiver_email is your Primary PayPal email
            // Check that payment_amount/payment_currency are correct
            // Process payment

         else if (strcmp ($response, "INVALID") == 0) 
            // Response contains INVALID - reject notification
            // Authentication protocol is complete - begin error handling

        
    

// Close the file
fclose($fp);
?>

好的,现在你有了代码侦听PayPal IPN,重新编译并将其发送回PayPal验证,接收标头返回,处理标头逐行查找VERIFIED验证。

这应该由 PayPal 一体化软件包在 工作条件 中提供,但他们的教程缺少非常关键的部分,并且对我和此线程中的许多人都不起作用。 p>

那么现在使它工作的关键部分是什么: 首先,paypal 提供的 headers 不起作用,我发现 @jp_eagle 的 headers 可以正常工作。

paypal 的错误是 $res = fgets($fp, 1024);还有……

但是你不需要 $res = stream_get_contents($fp, 2048);正如@jp_eagle 建议的那样, $res = stream_get_contents($fp, 1024);刚刚好。

while (!feof($fp)) 循环应该留在那里以使其工作!是的,即使从 fgets() 切换到 stream_get_contents() 它也应该留在那里!

@richbai90 删除它的建议是错误的。 换个方法试试,不行……

获得 VALIDATION 的最关键部分是这个循环:

    $res = stream_get_contents($fp, 1024);
    $responses = explode("\r\n", $res);
    foreach ($responses as $response) 

现在您可以在此处的每一行中查找 VALIDATION:

if (strcmp ($response, "VERIFIED") == 0)

还在其他任何事情之前记录了初始完整的传入 POST 请求表单 paypal:

// 记录事务: file_put_contents("paypal_post.txt", date('Y-m-d H:i:s')."\n".file_get_contents("php://input")."\n\n", FILE_APPEND);

就是这样。 现在去这里并使用您的贝宝登录,看看它是否有效: https://developer.paypal.com/developer/ipnSimulator/

例如,给您的网址选择“购物车结帐”并发送测试 IPN。 如果一切正常,请继续从上面的 paypal URL 中删除 .sandbox,从 www.sandbox.paypal.com 到 www.paypal.com,您就可以上线了。

用您的数据库操作填写 if-else 语句中的 VERIFIED 和 INVALID 部分,然后就完成了。

【讨论】:

以上是关于PayPal IPN 更新以适应 HTTP1.1 - 对等方重置连接的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 IPN 使用 Paypal 自适应支付?

Paypal 自适应支付 IPN 回调:如何返回完整未更改的 ipn 消息

Paypal 自适应支付 IPN 费用信息

paypal自适应支付IPN中的自定义字段

Paypal 自适应支付 - IPN 和成功的返回响应

POODLE 更新后,PayPal IPN 停止使用 Tomcat/Open SSL