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

Next.js中PayPal订单捕获问题:无法获取orderId与token

PayPal支付集成问题:创建订单后无法获取orderId与token导致捕获API调用失败

我正在为网站集成PayPal支付功能,使用react-paypal-js的PayPalButtons组件和PayPal API v2。目前遇到的问题是:创建订单后,无法正确获取刚生成的orderId与token,导致调用捕获API完成支付时失败。

相关实现代码如下:

import React, { useEffect, useState } from "react";
import { PayPalButtons } from "@paypal/react-paypal-js";
import useBoundStore from "@/store/store";

type onApproveData = {
    token: string,
    orderId: string
}
export const PayPalButton = () => {
    const layout: "vertical" | "horizontal" = "vertical";
    const style = { layout };
    const base = process.env.NEXT_PUBLIC_PAYPAL_API_URL || 'https://api-m.sandbox.paypal.com'
    const ordersPaypalApi = {
        test: "https://api-m.sandbox.paypal.com/v2/checkout/orders",
        prod: "https://api-m.paypal.com/v2/checkout/orders"
    };
    const totalPrice = useBoundStore((state) => state.calculateRoomPrice?.reservations_amount_price)
    const [token, setToken] = useState("")
    const [orderId, setOrderId] = useState("")
    const [dataApprove, setDataApprove] = useState<onApproveData>({token:"", orderId:""})
    useEffect(()=>{
        setDataApprove({token:token, orderId:orderId})
    },[token, orderId])
    async function generateAccessToken() {
        const auth = Buffer.from(process.env.NEXT_PUBLIC_PAYPAL_CLIENT_ID + ':' + process.env.NEXT_PUBLIC_PAYPAL_CLIENT_SECRET).toString(
            'base64'
        )
        const response = await fetch(`${base}/v1/oauth2/token`, {
            method: 'POST',
            body: 'grant_type=client_credentials',
            headers: {
                Authorization: `Basic ${auth}`,
            },
        })
        return response.json()
    }
    const createOrder =async () => {
        const res = await generateAccessToken()
        const accessToken = res.access_token
        localStorage.setItem(accessToken,"PAYPAL_TOKEN")
        setToken(accessToken)
        const url = process.env.NEXT_ENV !== "production" ? ordersPaypalApi.test : ordersPaypalApi.prod
        const response = await fetch(url, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                Authorization: `Bearer ${accessToken}`,

            },
            body: JSON.stringify({
                intent: 'CAPTURE',
                purchase_units: [
                    {
                        amount: {
                            currency_code: 'USD',
                            value: Math.round(totalPrice),
                        },
                    },
                ],
            }),
        }
        )
        const responseData = await response.json();
        const id = responseData.id;
        setOrderId(id);
        return id;
    }
    const onApprove = async ()=>{
        const url = process.env.NEXT_ENV !== "production" ? ordersPaypalApi.test : ordersPaypalApi.prod + `/${orderId}/capture`
        const response = await fetch(url, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                Authorization: `Bearer ${token}`,
            },
        }
        );
        const capture = await response.json();
        console.log("capture", capture);
    }
    return (
        <PayPalButtons
            style={style}
            createOrder={createOrder}
            onApprove={onApprove}
        />
    );
};

截图显示订单已创建但捕获操作失败:
订单已创建但捕获操作失败


问题分析与修复方案

1. 改用onApprove回调参数获取订单数据

onApprove本身会接收PayPal返回的订单信息,不需要依赖组件state存储orderId和token——state异步更新的特性会导致获取到空值或旧数据。直接用回调参数里的即时数据:

const onApprove = async (data: any) => {
  // 直接从参数拿orderID
  const orderId = data.orderID;
  // 重新获取最新accessToken,避免过期
  const tokenRes = await generateAccessToken();
  const accessToken = tokenRes.access_token;

  const url = `${process.env.NEXT_ENV !== "production" ? ordersPaypalApi.test : ordersPaypalApi.prod}/${orderId}/capture`;
  const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
  });
  const capture = await response.json();
  console.log("capture", capture);
};

2. 把敏感操作移到后端,避免密钥泄露

当前代码在前端直接暴露NEXT_PUBLIC_PAYPAL_CLIENT_SECRET,属于严重安全问题。必须将获取accessToken、创建订单、捕获订单的逻辑全部移到后端接口,前端只负责调用后端接口:

后端示例(Node.js/Express)

// 生成accessToken接口
app.post('/api/paypal/get-token', async (req, res) => {
  const auth = Buffer.from(process.env.PAYPAL_CLIENT_ID + ':' + process.env.PAYPAL_CLIENT_SECRET).toString('base64');
  const response = await fetch('https://api-m.sandbox.paypal.com/v1/oauth2/token', {
    method: 'POST',
    body: 'grant_type=client_credentials',
    headers: { Authorization: `Basic ${auth}` },
  });
  const data = await response.json();
  res.json(data);
});

// 创建订单接口
app.post('/api/paypal/create-order', async (req, res) => {
  const { totalPrice } = req.body;
  const tokenRes = await fetch('http://localhost:3000/api/paypal/get-token');
  const { access_token } = await tokenRes.json();
  const response = await fetch('https://api-m.sandbox.paypal.com/v2/checkout/orders', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${access_token}`,
    },
    body: JSON.stringify({
      intent: 'CAPTURE',
      purchase_units: [{ amount: { currency_code: 'USD', value: Math.round(totalPrice) } }],
    }),
  });
  const data = await response.json();
  res.json(data);
});

// 捕获订单接口
app.post('/api/paypal/capture-order', async (req, res) => {
  const { orderId } = req.body;
  const tokenRes = await fetch('http://localhost:3000/api/paypal/get-token');
  const { access_token } = await tokenRes.json();
  const response = await fetch(`https://api-m.sandbox.paypal.com/v2/checkout/orders/${orderId}/capture`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${access_token}`,
    },
  });
  const data = await response.json();
  res.json(data);
});

前端修改为调用后端接口

// 创建订单
const createOrder = async () => {
  const response = await fetch('/api/paypal/create-order', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ totalPrice: Math.round(totalPrice) }),
  });
  const responseData = await response.json();
  return responseData.id;
};

// 捕获订单
const onApprove = async (data: any) => {
  const response = await fetch('/api/paypal/capture-order', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ orderId: data.orderID }),
  });
  const capture = await response.json();
  console.log("capture", capture);
};

3. 移除不必要的state同步逻辑

原代码里用useEffect同步dataApprove的操作完全多余,state异步更新的特性会导致数据不同步,直接删除相关代码即可。

4. 修正localStorage的错误用法

原代码中localStorage.setItem(accessToken,"PAYPAL_TOKEN")的参数顺序反了,正确写法是localStorage.setItem("PAYPAL_TOKEN", accessToken),但更建议完全移除前端存储token的逻辑——token存前端有被盗用风险,交给后端处理更安全。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 17:22:49