Paypal沙盒IPN始终返回INVALID。

11

正如下面一个答案中的评论所提到的,我尝试按照这篇教程操作。现在我有了以下内容:


ipn.php文件:

<?php

    $ipn_post_data = $_POST;

    $url = 'https://www.sandbox.paypal.com/cgi-bin/webscr';

    // Set up request to PayPal
    $request = curl_init();
    curl_setopt_array($request, array
    (
        CURLOPT_URL => $url,
        CURLOPT_POST => TRUE,
        CURLOPT_POSTFIELDS => http_build_query(array('cmd' => '_notify-validate') + $ipn_post_data),
        CURLOPT_RETURNTRANSFER => TRUE,
        CURLOPT_HEADER => FALSE,
        CURLOPT_SSL_VERIFYPEER => TRUE,
        CURLOPT_CAINFO => 'cacert.pem',
    ));

    // Execute request and get response and status code
    $response = curl_exec($request);
    $status   = curl_getinfo($request, CURLINFO_HTTP_CODE);

    // Close connection
    curl_close($request);

    if($status == 200 && $response == 'VERIFIED')
    {
        $subject = "valid";
        $message = "good";
    }
    else
    {
        $subject = "invalid";
        $message = "bad";
    }

    $to = "oshirowanen@mail.com";
    $from = "me@desktop.com";

    $header  = 'MIME-Version: 1.0' . "\r\n";
    $header .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
    $header .= 'To: Oshirowanen <oshirowanen@mail.com>' . "\r\n";
    $header .= 'From: Me <me@desktop.com>' . "\r\n";

    mail($to,$subject,$message,$header);

?>

收到的电子邮件:

Subject "invalid"
Message "bad"

更新的问题展示了通过 $_POST 接收到的值,其中包含一个数组内部的数组,正如 @Enzino 所期望的那样。 - oshirowanen
我仍然无法让它正常工作。因此,我将奖励那个包含一个ipn.php文件的答案,并通过沙盒账户发送给我一个valid电子邮件的回答者。 - oshirowanen
根据下面一个答案的评论提到的教程http://www.geekality.net/2011/05/28/php-tutorial-paypal-instant-payment-notification-ipn/,更新问题。 - oshirowanen
9个回答

14

编辑:

现在我可以看到您输出的数组,请尝试替换以下内容以消除PHP数组错误:

foreach ($_POST as $key => $value) {
    if (!is_array($value)) {
        $value = urlencode(stripslashes($value));
        $req .= "&$key=$value";
    }
    else if (is_array($value)) {
        $paymentArray = explode(' ', $value[0]);
        $paymentCurrency = urlencode(stripslashes($paymentArray[0]));
        $paymentGross = urlencode(stripslashes($paymentArray[1]));
        $req .= '&mc_currency=' . $paymentCurrency . '&mc_gross=' . $paymentGross;
    }
}

以下是完整的编辑代码:

// read the post from PayPal system and add 'cmd'
$req = 'cmd=' . urlencode('_notify-validate');

foreach ($_POST as $key => $value) {
    if (!is_array($value)) {
        $value = urlencode(stripslashes($value));
        $req .= "&$key=$value";
    }
    else if (is_array($value)) {
        $paymentArray = explode(' ', $value[0]);
        $paymentCurrency = urlencode(stripslashes($paymentArray[0]);
        $paymentGross = urlencode(stripslashes($paymentArray[1]);
        $req .= '&mc_currency=' . $paymentCurrency . '&mc_gross=' . $paymentGross;
    }
}

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://www.paypal.com/cgi-bin/webscr');
curl_setopt($ch, CURLOPT_HEADER, 0);
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_HTTPHEADER, array('Host: www.paypal.com'));
$res = curl_exec($ch);
curl_close($ch);


// assign posted variables 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'];


if (strcmp ($res, "VERIFIED") == 0) {
    // check 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 ($res, "INVALID") == 0) {
    // log for manual investigation
}

点这里查看!

编辑:查看PayPal故障排除提示:

https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_admin_IPNTesting


@oshirowanen 看起来 PayPal 沙盒非常脆弱,我经常看到有人遇到同样的问题。尝试一件事是打印 $req 值并查看是否已添加所有 POST。阅读此内容:(https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_admin_IPNTesting)[https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_admin_IPNTesting] - Stegrex
@oshirowanen 在我回复之前,Enzino已经添加了一些解释。请查看他们下面的答案。我同意cURL是更好的解决方案,而Enzino在将主机放入头部以确保没有错误方面是正确的。我会为您展示PayPal代码示例中的cURL解决方案。 - Stegrex
我认为这段代码仍然缺少对HTTP响应状态码的检查。请参考此答案以获取更多详细信息。 - user1419445
@Stegrex,我根据cURL方法进行了最新尝试,并更新了问题。 - oshirowanen
我仍然无法让它正常工作。因此,我将奖励那个包含一个有效的ipn.php文件的答案,并通过沙盒账户发送电子邮件给我。 - oshirowanen
显示剩余8条评论

6
问题在于您没有检查HTTP响应代码,因此将“无效的主机头”解释为PayPal响应,而实际上它是Web服务器响应(状态码为400)。 如果您查看PayPal文档,会发现其中有一个PHP示例与您的代码非常相似,因为它使用“fsockopen”,“fputs”和“fgets”函数与PayPal服务器通信。 但是,如果您仔细查看“fsockopen”调用后的备注,就可以阅读到:
// Process validation from PayPal 
// TODO: This sample does not test the HTTP response code. All 
// HTTP response codes must be handled or you should use an HTTP 
// library, such as cUrl

这正是您的问题所在:在解析响应主体之前,您没有检查HTTP响应代码是否为200(OK)。
此外,使用“strtolower”函数是不正确的,因为来自PayPal服务器的真实响应始终是大写字母,就像上面引用的示例一样。
即使PayPal示例使用“fsockopen”方法,我认为最好使用PHP cURL库来实现您的IPN监听器。
还可以参考以下答案:
然而,如果你真的想使用"fsockopen"函数,你应该始终在POST请求中指定"主机"头字段,如下面代码片段所示(摘自PHP手册):
<?php
$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
if (!$fp) {
    echo "$errstr ($errno)<br />\n";
} else {
    $out = "GET / HTTP/1.1\r\n";
    $out .= "Host: www.example.com\r\n";
    $out .= "Connection: Close\r\n\r\n";
    fwrite($fp, $out);
    while (!feof($fp)) {
        echo fgets($fp, 128);
    }
    fclose($fp);
}
?>

更新

这是一个用于递归执行stripslashes/urlencoding的简单函数:

<html>
<body>
<pre>
<?

$post = Array (
  "transaction" => Array("USD 20.00"),
  "payment_request_date" => "Sun Aug '05 08:49:20 PDT 2012",
  "return_url" => "http://000.000.000.000/success.php"
);

echo "before myUrlencode...\n";
print_r($post);

function myUrlencode($post) {
  foreach ($post as $key => $val) {
    if (is_array($val)) {
      $post[$key] = myUrlencode($val);
    } else {
      $post[$key] = urlencode(stripslashes($val));
    }
  }
  return($post);
}

echo "\nafter myUrlencode...\n";
print_r(myUrlencode($post));

?>
</pre>
</body>
</html>

如我在之前的评论中所说,您还应该添加对HTTP状态码的检查,因为如果PayPal服务器不以200(OK)代码回复您的请求,则$res变量可能为空(或FALSE),并且您将不会收到任何邮件,因为两个 if (strcmp($res, ... 中的任何一个都不会返回零值。 - user1419445
1
stripslashes()不是递归的。如果要将此函数应用于多维数组,则需要使用递归函数。在PHP手册中,可以找到如何执行此操作的示例(搜索“stripslashes_deep”函数)。 请注意,您在“urlencode”函数上也会遇到相同的问题。有关数组的URL编码示例,请参见此链接(搜索“urlencode_array”)。 - user1419445
我仍然无法让它正常工作。因此,我将奖励那个包含一个ipn.php文件的答案,并通过沙盒账户发送给我一个valid电子邮件的回答者。 - oshirowanen
这个问题是否与我的问题末尾的“关于上述教程的混淆”部分有关?我根本没有在代码中包含那一部分。 - oshirowanen
你检查了你的PayPal沙盒账户是否是“未经验证”的吗?请查看此帖以获取更多细节。另请查看此帖以获取INVALID响应可能原因的列表,即使我不认为它真的有什么帮助。 - user1419445
显示剩余17条评论

2
  1. 使用基本示例代码 4b,使其正常工作。

  2. 从基本示例代码中清除$ipnNotificationUrl = "";,因为我已经自己添加了一个值。

  3. 创建了一个卖家账户,而不是沙盒中的商业专业账户。

  4. 设置卖家账户以启用ipn url。

  5. 使用以下PHP 5.2示例代码作为ipn监听器。

  6. 按照此处的说明,在监听器中添加了两行代码,这两行代码如下所示:

  7. 此处下载了cacert.pem证书到我的服务器,并将其放在与ipn监听器相同的目录中:

第6点中提到的2行代码:

CURLOPT_SSL_VERIFYPEER => TRUE,
CURLOPT_CAINFO => 'cacert.pem',

我不知道为什么沙盒业务专业账户不允许我设置ipn url,但卖家账户可以。


我能看你的完整代码吗?我也卡在创建PayPal IPN上了。 - Sam San
@IvorySantos,我认为我已经通过链接在我的答案中包含了所有的代码。如果我漏掉了什么,请告诉我。 - oshirowanen
PHP示例代码(上面的第5步)使用curl非常出色!感谢您的提示!已点赞! - Johncl

1

我现在不确定你的代码出了什么问题,但我之前也遇到过同样的问题,我的解决方法是在头部添加HOST,HOST必须是www.paypal.com。我使用了fsockopen方法,现在可以正常工作。

在Curl中,我之前遇到过SSL问题。解决方法是添加以下几行:

curl_setopt($curl, CURLOPT_COOKIEJAR, dirname(__FILE__) . "/cookies.txt");
curl_setopt($curl, CURLOPT_COOKIEFILE, dirname(__FILE__) . "/cookies.txt");

当然,文件 cookies.txt 必须存在。 而且,我必须运行一个连接到页面来获取会话数据,然后发送 POST 数据。

以下是使用 fsockopen 方法运行良好的标头

$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";

1

这是一个与“+”字符有关的问题,它经常被错误地提取,因此我制定了这个解决方法,并且对我有效。

payment_data = 2016年6月4日星期六15:11:16 GMT+0200(中欧夏令时)

foreach ($_POST as $key => $value) {
if($key !== "payment_date"){
    $req .= '&' . $key . '=' . rawurlencode(html_entity_decode($value, ENT_QUOTES, 'UTF-8'));
}else{
    $req .= '&' . $key . '=' . rawurlencode(str_replace(array('GMT '),array('GMT+'),$value));
}}


0

我终于找到了一个更新的(2016年8月5日)可用的答案来解决这个问题。您可以将此代码用作您的Sandbox或Live的最终IPN。需要注意以下几点:

  1. 确保将您的IPN监听器放置在“我的销售工具” ->“即时付款通知”部分。
  2. 不要在沙盒中使用IPN模拟器,它总是会返回INVALID。
  3. 创建并使用实际的Sandbox按钮,但不要将您的IPN监听器放置在“当客户完成结账时,将其带到此URL”的RETURN PAGE上。

就是这些。我希望这会有所帮助。

以下是可用的代码:

<?php
$post_data = file_get_contents('php://input');
$post_array = explode('&', $post_data);
$dataFromPayPal = array();
foreach ($post_array as $keyval) {
    $keyval = explode ('=', $keyval);
    if (count($keyval) == 2)
        $dataFromPayPal[$keyval[0]] = urldecode($keyval[1]);
}

$req = 'cmd=_notify-validate';
if(function_exists('get_magic_quotes_gpc')) {
    $get_magic_quotes_exists = true;
}
foreach ($dataFromPayPal 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";
}

$ch = curl_init('https://www.sandbox.paypal.com/cgi-bin/webscr');
//use https://www.sandbox.paypal.com/cgi-bin/webscr in case you are testing this on a PayPal Sanbox environment
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_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));

if( !($res = curl_exec($ch)) ) {
    curl_close($ch);
    exit;
}
curl_close($ch);



if (strcmp ($res, "INVALID") == 0) {
        echo "INVALID";
}
else if (strcmp ($res, "VERIFIED") == 0) {
        echo "VALID";
}

?>

0

以下是避免这些错误的方法...

foreach ($_POST as $key => $value) {
     if ($key=='transaction')
          foreach ($value as $key2=>$value2) {
               $value['transaction'][$key2] = urlencode(stripslashes($value2));
     }
     else {
          $value = urlencode(stripslashes($value));
     }
     $req .= "&$key=$value";
 }

好的,这样可以从数组中获取数组,但是我仍然从$res得到一个无效的值。 - oshirowanen
我仍然无法让它正常工作。因此,我将奖励那个包含一个有效的ipn.php文件的答案,并通过沙盒账户发送电子邮件给我。 - oshirowanen

0

在看到Izudin的答案之前,我已经抓狂了好几个小时。他是对的...日期中的+号没有被传输。为了测试,我从模拟器中预填的字段中删除了它,最终获得了已验证


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接