如何验证 Paypal webhook 通知 DIY 样式(不使用 Paypal SDK)

集成 Paypal 的智能按钮后,我无法验证 Paypal 发送的 webhook 通知。我发现的示例要么已过时,要么不起作用。

有没有办法验证 webhook 通知,最好是 DIY 方式(即无需使用庞大而复杂的 Paypal API)?

stack overflow How to verify a Paypal webhook notification DIY style (without using Paypal SDK)
原文答案

答案:

作者头像

为nodejs回答这个问题,因为有微妙的安全问题,有些在原始答案(但非常有用)中缺少逻辑。该答案解决了以下问题:

1.有人放置自己的URL,从而获得自己的请求的身份验证

  1. CRC需要是一个未签名的整数,而不是签名的整数。
  2. nodejs <17.0缺少X509功能内置的一些。
    4.理想情况下,应该用内置的证书链验证签名证书,但nodejs <17.0不能轻松地做到这一点。信托模型依赖于TLS和nodejs信托链的cert fetch url,而不是CERT URL返回的证书,这可能足够好。

    
    const forge = require('node-forge');
    const crypto = require('crypto')
    const CRC32 = require('crc-32');
    const axios = require('axios');
    
    const transmissionId = paypalSubsEvent.headers['PAYPAL-TRANSMISSION-ID'];
    const transmissionTime = paypalSubsEvent.headers['PAYPAL-TRANSMISSION-TIME'];
    const signature = paypalSubsEvent.headers['PAYPAL-TRANSMISSION-SIG'];
    const webhookId = '<your webhook ID from your paypal dev. account>';
    const url = paypalSubsEvent.headers['PAYPAL-CERT-URL'];
    const bodyCrc32 = CRC32.str(paypalSubsEvent.body);  
    const unsigned_crc = bodyCrc32 >>> 0;     // found by trial and error
    
    // verify domain is actually paypal.com, or else someone
    // could spoof in their own cert
    const urlObj = new URL(url);
    if (!urlObj.hostname.endsWith('.paypal.com')) {
    throw new Error(
      `URL ${certUrl} is not in the domain paypal.com, refusing to fetch cert for security reasons`);
    }
    const validationString =
    transmissionId + '|'
    + transmissionTime + '|'
    + webhookId + '|'
    + unsigned_crc;
    
    const certResult = await axios.get(url);   // Trust TLS to check the URL is really from *.paypal.com
    const cert = forge.pki.certificateFromPem(certResult.data);
    const publicKey = forge.pki.publicKeyToPem(cert.publicKey)
    const verifier = crypto.createVerify('RSA-SHA256');
    verifier.update(validationString);
    verifier.end();
    const result = verifier.verify(publicKey, signature, 'base64');
    console.log(result);