PayPal WebHook 事件异步通知

另一个文章 https://www.bahjeez.com/validating-paypal-webhooks-offline-almost/ ,阐述了如何实现

PayPal WebHook

即 paypal 异步通知功能,订阅了相应的事件后,当发生了订阅的事件,paypal 会 post 一个 json 格式的数据到所配置的地址上。

配置WebHook地址和订阅事件

https://developer.paypal.com/developer/applications

选择查看应用详情后拉到最下面的Webhook配置,输入通知地址和订阅事件,点击保存后,会生成一个Webhook ID(在验证Webhook信息的时候用到)。

checkout order相关的事件:https://developer.paypal.com/docs/checkout/apm/reference/subscribe-to-webhooks/

所有事件:https://developer.paypal.com/api/rest/webhooks/event-names/

需要响应200状态码表示处理成功,否则会在3天内最多发送10次。

Webhook事件

接收并验证Webhook消息

为了保证webhook消息的真实性,需要对消息进行验证,避免处理假消息。参考文档地址:https://developer.paypal.com/api/rest/webhooks/

需要用到的header:

验证方式一:调接口获取webhook详情

通过Webhook的消息ID调用接口去获取webhook消息详情

GET https://api.paypal.com/v1/notifications/webhooks-events/{EVENT_ID}

验证方式二:调用验签API

调用paypal的验签API

POST https://api.paypal.com/v1/notifications/verify-webhook-signature

参数

{
/**
* 签名方式,可从header信息的PAYPAL-AUTH-ALGO获取
*/
"auth_algo": string;
/**
* 公钥地址,可从header信息的PAYPAL-CERT-URL获取
*/
"cert_url": string;
/**
* HTTP传输ID,可从header信息的PAYPAL-TRANSMISSION-ID获取
*/
"transmission_id": string;
/**
* 签名,可从header信息的PAYPAL-TRANSMISSION-SIG获取
*/
"transmission_sig": string;
/**
* 时间,可从header信息的PAYPAL-TRANSMISSION-TIME获取
*/
"transmission_time": string;
/**
* Webhook ID,即webhook配置的ID
*/
"webhook_id": string;
/**
* webhook事件
*/
"webhook_event": string;
}

响应

{
/**
* 验证结果
* SUCCESS - 成功
* FAILURE - 失败
*/
"verification_status": string;
}

验证方式三:自己手动验证

验证签名,输入的字符串为:

 
<transmissionId>|<timeStamp>|<webhookId>|<crc32>
 
FieldDescription
 
|transmissionId|The unique ID of the HTTP transmission from the PAYPAL-TRANSMISSION-ID header.|
|timeStamp|The date and time when the HTTP message was transmitted from the PAYPAL-TRANSMISSION-TIME header.|
|webhookId|The ID of the webhook resource for the destination URL to which PayPal delivers the event notification.|
|crc32|The [Cyclic Redundancy Check (CRC32)](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) checksum for the body of the HTTP payload.|
 
使用crc32算法对post的数据进行多项式计算。
<?php
$dataUrlString = file_get_contents('php://input');
$headers = self::getAllHeaders();// 获取所有header
$paypalTransmissionId = $headers['PAYPAL-TRANSMISSION-ID'] ?? '';
$timeStamp = $headers['PAYPAL-TRANSMISSION-TIME'] ?? '';
$webhookId = 'WEBHOOK ID';
$crc32 = crc32($dataUrlString);
 
if (!$paypalTransmissionId || !$timeStamp || !$webhookId || !$crc32) {
return false;
}
 
$sigString = "{$paypalTransmissionId}|{$timeStamp}|{$webhookId}|{$crc32}";
 
// 通过PAYPAL-CERT-URL头信息去拿公钥
$publicKey = openssl_pkey_get_public(self::httpCurl($headers['PAYPAL-CERT-URL']));
$details = openssl_pkey_get_details($publicKey);
$verifyResult = openssl_verify($sigString, base64_decode($headers['PAYPAL-TRANSMISSION-SIG']), $details['key'], 'SHA256');
if ($verifyResult === 1) {
// 验证成功
} else {
// 验证失败
}
<?php
public static function getAllHeaders(): array
{
$headers = [];
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') {
$headers[str_replace(' ', '-', str_replace('_', ' ', substr($name, 5)))] = $value;
}
}
return $headers;
}
<?php
public static function httpCurl($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 
$output = curl_exec($ch);
curl_close($ch);
return $output;
}

其他说明