验证IPN调用是否来自PayPal?

8

如何验证PayPal IPN POST请求是否确实来自于PayPal发送到我指定的notifyURL?

我的意思不是将数据与之前发送的内容进行比较,而是如何验证此PayPal请求所来自的服务器/IP地址是否确实有效?


这里列出的验证IPN回传来源的方法并不是绝对可靠的,也不能让你比现在更安全。请按照PayPal推荐的最佳实践来实施。如果它们不安全,考虑到PayPal的整个品牌都建立在用户的信任之上,那么PayPal将会面临更大的问题。 - Brad
6个回答

16
IPN协议由三个步骤组成:
  1. PayPal向您的IPN监听器发送消息,通知您发生了事件
  2. 您的监听器将完整、未经改动的消息发送回PayPal;该消息必须包含与原始消息相同的字段、相同的顺序,并采用与原始消息相同的编码方式
  3. PayPal会回复一个单词,如果消息来自PayPal,则是"VERIFIED",如果有任何差异,则为"INVALID"
https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_admin_IPNIntro

你需要这样做的原因是什么呢?毕竟,上述三步骤的整个重点在于,其他人无法调用您的IPN监听器并获得已验证的响应... - Amber
更多或少相同的主题:https://dev59.com/6k3Sa4cB1Zd3GeqPytuA - Sami Koivu
是的,这样做有一个很好的理由,当使用多种付款方式时,我想知道是谁进行了哪个调用,以便进行验证。 - Phil Jackson
@siliconpi 我同意。他们的做法很愚蠢。IPN 应该通过 HTTPS 传递,并使用客户端证书。 - user207421
这是不正确的:你将完全相同的消息发送回PayPal,除了在末尾添加“&cmd=_notify-validate”。 - IrishChieftain
显示剩余3条评论

12

这是我发现的最简单的方法,也是PayPal建议的方法。我使用http_build_query()从PayPal发送到站点的POST构造URL。PayPal文档指出,您应该将此发送回进行验证,这就是我们使用file_get_contents的原因。您会注意到我使用strstr检查是否存在“VERIFIED”一词,如果不存在,则在函数中返回false...

$verify_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_notify-validate&' . http_build_query( $_POST );   

if( !strstr( file_get_contents( $verify_url ), 'VERIFIED' ) ) return false;

1
实际上,PayPal 建议您将请求正文 POST 回给他们:https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNIntro/ - acidtv
现在需要HTTP头中的User-Agent! - Sergey Shuchkin

3

https://gist.github.com/mrded/a596b0d005e84bc27bad

function paypal_is_transaction_valid($data) {
  $context = stream_context_create(array(
    'http' => array(
      'header'  => "Content-type: application/x-www-form-urlencoded\r\n",
      'method'  => 'POST',
      'content' => http_build_query($data),
    ),
  ));
  $content = file_get_contents('https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_notify-validate', false, $context);

  return (bool) strstr($content, 'VERIFIED');
}

3

现在需要HTTP头中的User-Agent!

$vrf = file_get_contents('https://www.paypal.com/cgi-bin/webscr?cmd=_notify-validate', false, stream_context_create(array(
    'http' => array(
        'header'  => "Content-type: application/x-www-form-urlencoded\r\nUser-Agent: MyAPP 1.0\r\n",
        'method'  => 'POST',
        'content' => http_build_query($_POST)
    )
)));

if ( $vrf == 'VERIFIED' ) {
    // 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
}

如果我正在使用沙盒进行测试,那么用于身份验证的URL是www.sandbox.paypal.com还是www.paypal.com? - Volomike

1

如果我没记错的话,PayPal在其IPN调用中使用静态IP。

因此,检查正确的IP应该可以解决问题。

或者,您可以使用gethostbyaddrgethostbyname


+1 提醒我可能是静态的。我已经好几年没有做过IPN编程了。 - jcomeau_ictx

-1

这是我使用的东西

if (preg_match('~^(?:.+[.])?paypal[.]com$~', gethostbyaddr($_SERVER['REMOTE_ADDR'])) > 0)
{
    // came from paypal.com (unless your server got r00ted)
}

如果我购买了域名blablablarandomstuffpaypal.com会怎样?它不会符合这个正则表达式吧?在“paypal”之前加上 [.] 应该可以解决这个问题。 - aularon
@aularon:它不会匹配,因为有 ^ - Alix Axel
可以很容易地被欺骗。请参阅https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNIntro/。 - Luke A. Leber

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