我在尝试用PHP验证Paypal Webhook签名时遇到麻烦。使用新的V2贝宝API,我在页面上收到了贝宝Webhook。 但是我似乎无法成功验证签名。
从链接HERE中,我从paypal获得了一些示例Webhook验证PHP代码。
我无法正常工作,我不知道应该在贝宝代码中从哪里获取bootstrap.php。贝宝信息似乎不完整或半熟。与Stripe相比,贝宝的设置似乎很糟糕。
在使用V2的Paypal API时,有没有人有使用PHP验证Paypal Webhook签名的经验?
答案 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
}