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 详情

文档地址:https://developer.paypal.com/docs/api/webhooks/v1/#webhooks-events_get

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

验证方式二:调用验签 API

调用 paypal 的验签 API,文档地址:https://developer.paypal.com/docs/api/webhooks/v1/#verify-webhook-signature_post

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

payload

{
/**
* 签名方式,可从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;
}

response

{
/**
* 验证结果
* 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 = 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(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 {
// 验证失败
}
 
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;
}
 
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;
}

其他说明