You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PayPal结账时未向我的Webhook发送PAYMENT.SALE.COMPLETED事件

PayPal Webhook 问题排查与修复

核心问题分析

你的接口存在两个关键问题:

  1. 原始Body处理错误:Express的express.json()中间件会自动解析请求体为JSON对象,你用JSON.stringify(req.body)重新序列化后的内容,和PayPal发送的原始请求体(比如空格、键的顺序)不一致,导致签名验证失败,真实交易的请求被直接拒绝返回403。
  2. 真实交易无请求:大概率是Webhook配置错误、接口公网不可访问,或者签名验证逻辑错误导致PayPal无法成功回调。

修复方案

1. 正确处理原始请求体并使用官方SDK验证签名

不要自己实现HMAC验证,PayPal的签名机制包含证书验证等额外逻辑,官方SDK能确保验证准确性。同时必须捕获原始请求体,避免解析后序列化导致的内容不一致。

// 先添加中间件捕获原始请求体,放在express.json()之前
app.use('/paypal-webhook', (req, res, next) => {
  let rawBody = '';
  req.on('data', chunk => {
    rawBody += chunk.toString();
  });
  req.on('end', () => {
    req.rawBody = rawBody;
    next();
  });
});

// 启用JSON解析中间件
app.use(express.json());

app.post('/paypal-webhook', async (req, res) => {
  try {
    console.log('收到Webhook请求');
    const rawBody = req.rawBody;
    // 获取PayPal请求头中的验证参数
    const signature = req.headers['paypal-transmission-sig'];
    const transmissionId = req.headers['paypal-transmission-id'];
    const transmissionTime = req.headers['paypal-transmission-time'];
    const certUrl = req.headers['paypal-cert-url'];
    const webhookId = req.headers['paypal-webhook-id'];

    // 初始化PayPal SDK客户端
    const paypal = require('@paypal/checkout-server-sdk');
    const environment = process.env.NODE_ENV === 'production' 
      ? new paypal.core.LiveEnvironment(process.env.PAYPAL_CLIENT_ID, process.env.PAYPAL_CLIENT_SECRET)
      : new paypal.core.SandboxEnvironment(process.env.PAYPAL_CLIENT_ID, process.env.PAYPAL_CLIENT_SECRET);
    const client = new paypal.core.PayPalHttpClient(environment);

    // 创建验证请求并执行
    const verificationRequest = new paypal.notifications.WebhookVerificationRequest(
      transmissionId,
      transmissionTime,
      rawBody,
      webhookId,
      certUrl,
      signature
    );
    await client.execute(verificationRequest);

    // 验证成功,解析事件数据
    const event = JSON.parse(rawBody);
    if (event.event_type === 'PAYMENT.SALE.COMPLETED') {
      // 提取买家邮箱
      const buyerEmail = event.resource.payer.payer_info.email;
      console.log('买家邮箱:', buyerEmail);
      // 这里处理你的业务逻辑(建议异步处理,避免超时)
    }

    // 立即返回200,告诉PayPal已收到
    res.status(200).send('Success');
  } catch (err) {
    console.error('Webhook验证失败:', err.message);
    res.status(403).send('Unauthorized');
  }
});

2. 排查真实交易无请求的问题

  • 确认接口公网可访问:如果是本地开发,用端口穿透工具确保PayPal能访问你的Webhook URL;生产环境检查服务器防火墙/安全组,允许外部请求访问该接口。
  • 检查PayPal Webhook配置:
    • 确认URL完全匹配(必须是HTTPS,生产环境不能用HTTP)
    • 确认订阅了PAYMENT.SALE.COMPLETED事件(在PayPal开发者后台的Webhook配置中勾选)
    • 确认Webhook状态为"Active"
  • 检查PayPal事件日志:在PayPal开发者后台的Webhook页面,查看"Event History",如果有失败记录,根据错误信息排查(比如URL不可达、验证失败)。

3. 修复模拟器重复触发的问题

模拟器重复发送请求是因为接口响应超时或返回错误状态码:

  • 确保接口在5秒内返回200,PayPal会重试最多8次如果请求失败
  • 验证成功后立即返回200,耗时的业务逻辑(比如发送邮件、更新数据库)放到异步队列中处理,不要阻塞响应。

内容的提问来源于stack exchange,提问作者karlo292

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 13:12:28