PHP验证Paypal Webhook签名

时间:2020-04-05 10:22:42

标签: php validation paypal paypal-sandbox

我在尝试用PHP验证Paypal Webhook签名时遇到麻烦。使用新的V2贝宝API,我在页面上收到了贝宝Webhook。 但是我似乎无法成功验证签名。

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

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

在使用V2的Paypal API时,有没有人有使用PHP验证Paypal Webhook签名的经验?

1 个答案:

答案 0 :(得分:8)

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

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

一旦您可以处理付款并通过Paypal接收Webhooks,您需要安装另一个名为Paypal Rest API SDK的SKD。再次使用composer安装SDK,以便获得所需的autoload.php文件。

当您惊人地安装Paypal Rest API SDK时,您仍然会缺少验证payapl Webhook签名所需的文件。我在Paypal开发者网站上的任何地方都找不到任何提及。

bootstrap.php和common.php

感谢@Grumpy,我在github HERE

上提供了一些示例

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

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

贝宝(Paypal)肯定不容易,与Stripe相比,他们的文档无处不在。付款后,Paypal Webhooks有时可能需要几分钟才能到达,在尝试调试时非常沮丧。另外,他们在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


}