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
相关产品推荐
相关产品推荐

