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

PayPal点击「继续查看订单」后立即跳转,未等待支付完成问题咨询

PayPal集成跳转问题修复方案

问题概述

站点集成PayPal后,核心功能正常,能接收所有webhook,且在CHECKOUT.ORDER.APPROVED事件中可给用户添加tokens。但有个问题:用户在PayPal页面点「继续查看订单」后会立刻跳回站点,不等支付实际完成(最终支付是能成功的),怀疑是webhook端缺少状态调用。

当前控制器代码

exports.createPayment = async (req, res) => {
  const { userId, amountCNY, selectedTokens } = req.body;

  // 按当前汇率将人民币转美元
  const amountInUsd = convertRmbToUsd(amountCNY);

  // 拼接userId和selectedTokens作为custom_id
  const customId = `${userId}:${selectedTokens}`;

  const request = new paypal.orders.OrdersCreateRequest();
  request.prefer('return=representation');
  request.requestBody({
    intent: 'CAPTURE',
    purchase_units: [
      {
        amount: {
          currency_code: 'USD',
          value: amountInUsd.toFixed(2).toString(),
        },
        custom_id: customId,
      },
    ],
    application_context: {
      return_url: 'example.com',
      cancel_url: 'example.com',
    },
  });

  try {
    const response = await client().execute(request);
    if (response.statusCode === 201) {
      const approvalUrl = response.result.links.find((link) => link.rel === 'approve').href;
      res.json({ id: response.result.id, approvalUrl: approvalUrl });
    }
  } catch (error) {
    res.status(500).send('Error creating PayPal payment');
  }
};

exports.executePayment = async (req, res) => {
  const { orderId } = req.body;

  const request = new paypal.orders.OrdersCaptureRequest(orderId);
  request.requestBody({});

  try {
    const capture = await client().execute(request);
    res.status(200).json(capture.result);
  } catch (error) {
    res.status(500).send('Error capturing PayPal payment');
  }
};

exports.handleWebhook = async (req, res) => {
  const event = req.body;

  if (event.event_type === 'CHECKOUT.ORDER.APPROVED') {
    const orderId = event.resource.id;

    try {
      const request = new paypal.orders.OrdersCaptureRequest(orderId);
      request.requestBody({});
      const capture = await client().execute(request);

      const purchaseUnit = capture.result.purchase_units[0];
      const customId = purchaseUnit.custom_id;
      const [userId, selectedTokens] = customId.split(':');

      const newBalance = await addTokensByUserId(userId, parseInt(selectedTokens, 10));
      res.status(200).send('Success! Tokens added.');
    } catch (error) {
      res.status(500).send({ error: `Error processing payment: ${error.message}` });
    }
  } else if (event.event_type === 'PAYMENT.CAPTURE.COMPLETED') {
    res.status(200).send('Payment capture completed.');
  } else {
    res.status(200).send('Event received, but not handled.');
  }
};

问题原因及修复方案

核心问题

  1. 跳转逻辑触发过早:return_url会在用户点击「继续查看订单」时直接跳转,此时只是用户授权了支付,资金还没实际完成捕获
  2. token发放时机错误:当前在CHECKOUT.ORDER.APPROVED事件就发token,这个事件仅代表用户同意付款,不代表钱已经到账

修复步骤

1. 调整webhook事件处理逻辑

把token发放推迟到PAYMENT.CAPTURE.COMPLETED事件(资金实际到账的通知),CHECKOUT.ORDER.APPROVED只用来触发支付捕获请求:

exports.handleWebhook = async (req, res) => {
  const event = req.body;

  if (event.event_type === 'CHECKOUT.ORDER.APPROVED') {
    const orderId = event.resource.id;
    try {
      // 仅启动支付捕获流程,不处理token
      const request = new paypal.orders.OrdersCaptureRequest(orderId);
      request.requestBody({});
      await client().execute(request);
      res.status(200).send('Capture initiated successfully');
    } catch (error) {
      res.status(500).send({ error: `Error initiating capture: ${error.message}` });
    }
  } else if (event.event_type === 'PAYMENT.CAPTURE.COMPLETED') {
    try {
      const capture = event.resource;
      const orderId = capture.parent_payment;
      // 获取订单详情里的custom_id
      const orderRequest = new paypal.orders.OrdersGetRequest(orderId);
      const orderResponse = await client().execute(orderRequest);
      const customId = orderResponse.result.purchase_units[0].custom_id;
      const [userId, selectedTokens] = customId.split(':');

      // 确认资金到账后再添加tokens
      const newBalance = await addTokensByUserId(userId, parseInt(selectedTokens, 10));
      res.status(200).send('Success! Tokens added after payment confirmed.');
    } catch (error) {
      res.status(500).send({ error: `Error processing completed payment: ${error.message}` });
    }
  } else {
    res.status(200).send('Event received, but not handled.');
  }
};

2. 优化前端跳转后的体验

用户从return_url跳回站点后,前端不要直接显示支付成功,而是展示「支付处理中」的状态,通过轮询订单API或者等待webhook推送的状态更新,直到收到PAYMENT.CAPTURE.COMPLETED的通知后,再更新UI为支付成功并显示token到账信息。

3. 补充配置检查

  • 确认PayPal后台已正确订阅PAYMENT.CAPTURE.COMPLETED事件
  • return_url建议携带订单ID参数(比如example.com/order?orderId=XXX),方便前端快速定位对应订单
  • 给token添加逻辑加上幂等校验,避免重复发放

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 17:22:04