PHP 验证 Paypal webhook 签名

Posted

技术标签:

【中文标题】PHP 验证 Paypal webhook 签名【英文标题】:PHP verify Paypal webhook signature 【发布时间】:2020-07-17 08:22:41 【问题描述】:

我在尝试使用 php 验证 paypal webhook 签名时遇到问题。使用新的 V2 贝宝 API,我在我的页面上收到了贝宝 webhook。 但是我似乎无法成功验证签名。

从链接HERE 我从paypal 获得了一些示例webhook 验证PHP 代码。

我无法让它工作,我不知道我应该从 paypal 代码中的哪里获取 bootstrap.php。贝宝信息似乎不完整或半生不熟。与 Stripe 相比,Paypal 的设置似乎很糟糕。

有没有人在使用 V2 的 paypal API 时使用 PHP 验证 paypal webhook 签名的经验?

【问题讨论】:

向我们展示您的代码,到目前为止您尝试过什么? @Grumpy 我在上面的链接中使用了 Paypal 提供的确切代码 你可以在这里找到 bootstrap.php 信息github.com/paypal/PayPal-PHP-SDK/blob/master/sample/… @Grumpy 文档似乎都是关于贝宝 API 的 V1 的。我正在使用 V2 对于最近试图解决这个问题的任何人,可以 1.) 尝试使用他们的旧代码示例,2.) 使用他们的验证 url 和带有身份验证令牌的新文档,或者 3.) 手动核实。我个人发现 (2) 他们的系统没有及时响应资源 ID,因此订单在随后的 webhook 调用中被延迟。这是php(3)的最佳解决方案:bahjeez.com/validating-paypal-webhooks-offline-almost 【参考方案1】:

好吧,我得出的结论是,Paypal 开发人员信息相当差,它散布在各个地方,在多个不同的页面和站点上。他们在贝宝开发者网站HERE 上提供的示例并不是验证 webhook 签名所需内容的完整图片。 Stripe 开发人员文档的格式和简洁性要好得多。

安装 Paypal Checkout V2 SDK 并没有为您提供验证 paypal webhook 签名所需的开发工具,即您可以处理付款和接收 webhook,但您无法验证 webhook 签名......我知道这很愚蠢。提示不要直接下载 SDK,因为您不会包含所需的 autoload.php 文件。使用 composer 安装 Paypal Checkout V2 SDK,以便您获得 autoload.php 文件。

一旦您可以处理付款并从 paypal 接收 webhook,您需要安装另一个名为 Paypal Rest API SDK 的 SKD。再次使用 composer 安装 SDK,这样你就可以得到一个你需要的 autoload.php 文件。

当您惊人地安装 Paypal Rest API SDK 时,您仍然会丢失验证 payapl webhook 签名所需的文件。我在贝宝开发者网站上的任何地方都找不到这些内容。

bootstrap.php & common.php

感谢@Grumpy,我在 github HERE 上获得了一些示例

请注意,您可能需要稍微修改示例以使它们与您的网站一起使用。提示将记录器设置为 false,如果您没有必要的写入权限,可以省去一些麻烦。

一旦您创建了 bootstrap.php 和 common.php 文件,您就可以为您的 webhook 端点页面(即 paypal 将 webhook 发送到的页面)编写代码。我在下面包含了我的 PHP 代码,以了解如何验证然后处理 paypal webhook。在下面的代码中提示您需要指定 webhook ID,您在 paypal 中创建的每个 webhook 都有一个唯一的 ID。此外,当您进行测试时,您不能使用 webhook 模拟器,因为这将无法通过验证,您可以使用您的沙盒帐户详细信息手动付款,这将触发 webhook 付款事件。

Paypal 确实不容易,与 Stripe 相比,他们的文档无处不在。 Paypal webhook 有时在付款后可能需要几分钟才能到达,这在尝试调试时非常令人沮丧。而且他们在paypal开发者网站上有一个不能用来验证签名的webhook模拟器有点可笑……如果stripe可以做到,为什么paypal不能。

<?php





//get the webhook payload

$requestBody = file_get_contents('php://input');

//check if webhook payload has data
if($requestBody) 
//request body is set
 else 
//request body is not set
exit(); 





use \PayPal\Api\VerifyWebhookSignature;
use \PayPal\Api\WebhookEvent;

$apiContext = require __DIR__ . '/bootstrap.php';




//Receive HTTP headers that you received from PayPal webhook.

$headers = getallheaders();


//need header keys to be UPPERCASE

$headers = array_change_key_case($headers, CASE_UPPER);


/*

example header paypal signature content for webhook, these values are recieved as an array, we then need to use this data to verify the payload


CONTENT-LENGTH : 1376

CORRELATION-ID : 6db85170269e7

USER-AGENT : PayPal/AUHD-214.0-54377828

CONTENT-TYPE: application/json

PAYPAL-AUTH-ALGO : SHA256withRSA

PAYPAL-CERT-URL : https://api.paypal.com/v1/notifications/certs/CERT-360caa42-fca2a784-5edc0ebc

PAYPAL-AUTH-VERSION : v2

PAYPAL-TRANSMISSION-SIG : Hc2lsDedYdSjOM4/t3T/ioAVQqFPNVB/AY/EyPNlavXk5WYUfnAmt9dyEP6neAPOjFHiVkXMK+JlLODbr6dalw6i26aFQdsPXqGl38Mafuu9elPE74qgsqNferUFgHi9QFXL+UZCNYcb4mvlDePXZIIAPbB0gOuFGOdEv2uqNwTCSAa/D8aguv1/51FWb3RkytFuVwXK/XNfIEy2oJCpDs8dgtYAZeojH8qO6IAwchdSpttMods5YfNBzT7oCoxO80hncVorBtjj1zQrkoynEB9WNNN9ytepNCkT8l29fQ4Sx/WRndm/PESCqxqmRoYJoiSosxYU3bZP7QTtILDykQ==

PAYPAL-TRANSMISSION-TIME : 2020-04-05T14:40:43Z

PAYPAL-TRANSMISSION-ID : 6dec99b0-774b-11ea-b306-c3ed128f0c4b


*/


//if any of the relevant paypal signature headers are not set exit()

if(
(!array_key_exists('PAYPAL-AUTH-ALGO', $headers)) ||
(!array_key_exists('PAYPAL-TRANSMISSION-ID', $headers)) ||
(!array_key_exists('PAYPAL-CERT-URL', $headers)) ||
(!array_key_exists('PAYPAL-TRANSMISSION-SIG', $headers)) ||
(!array_key_exists('PAYPAL-TRANSMISSION-TIME', $headers)) 
)


exit();     


//specify the ID for the webhook that you have set up on the paypal developer website, each web hook that you create has a unique ID


$webhookID = "ENTER_YOUR_WEBHOOK_ID_HERE";




//start paypal webhook signature validation 

$signatureVerification = new VerifyWebhookSignature();
$signatureVerification->setAuthAlgo($headers['PAYPAL-AUTH-ALGO']);
$signatureVerification->setTransmissionId($headers['PAYPAL-TRANSMISSION-ID']);
$signatureVerification->setCertUrl($headers['PAYPAL-CERT-URL']);
$signatureVerification->setWebhookId($webhookID); 
$signatureVerification->setTransmissionSig($headers['PAYPAL-TRANSMISSION-SIG']);
$signatureVerification->setTransmissionTime($headers['PAYPAL-TRANSMISSION-TIME']);

$signatureVerification->setRequestBody($requestBody);
$request = clone $signatureVerification;

try 

$output = $signatureVerification->post($apiContext);

 catch (Exception $ex) 

//error during signature validation, capture error and exit

ResultPrinter::printError("Validate Received Webhook Event", "WebhookEvent", null, $request->toJSON(), $ex);
exit(1);




$sigVerificationResult = $output->getVerificationStatus();

// $sigVerificationResult is a string and will either be "SUCCESS" or "FAILURE"


//if not webhook signature failed validation exit
if($sigVerificationResult != "SUCCESS")

exit(); 

else if($sigVerificationResult == "SUCCESS")

//paypay webhook signature is valid

//proceed to process webhook payload


//decode raw request body

$requestBodyDecode = json_decode($requestBody);


//pull whatever info required from decoded request body, some examples below


$paymentSystemID = $requestBodyDecode->id;


$eventType = $requestBodyDecode->event_type;


//do something with info captured from the webhook payload


 

【讨论】:

您能否在本地验证这一点?而不是将其发送到 Paypal 服务器? 根据 Paypal 开发文档“PHP 目前不支持证书链验证,这是直接验证 webhook 所必需的,从接收到的数据要解决这个问题,我们需要使用替代方法,它调用 PayPal 的验证 Webhook 签名 API。” paypal.github.io/PayPal-PHP-SDK/sample/doc/notifications/…

以上是关于PHP 验证 Paypal webhook 签名的主要内容,如果未能解决你的问题,请参考以下文章

如何验证 PayPal Webhook 签名?

验证模拟 PayPal webhook 的签名总是返回“FAILURE”

PayPal + RESTful API + WebHooks + 自签名证书

node.js 中 PayPal webhook 的签名是啥?

通过 Braintree + Braintree Webhooks 从 PayPal Payments Pro(w/h 定期计费)+ IPN 迁移到 PayPal Payments

无法执行成功的 Paypal webhook 验证