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

Next.js+Vercel环境PayPal沙箱OAuth凭证401错误求助

PayPal REST API集成问题:预发布环境401未授权

本地开发环境PayPal支付功能正常,但Vercel预发布(staging/review)环境中获取Access Token时返回401状态码,请求格式符合要求但认证失败。

相关日志

info: Request body {"type":"form","content":[{"key":"grant_type","value":"client_credentials"}]}
info: Response 401 77 application/json
info: Response headers {"connection":"keep-alive","content-length":"77","access-control-expose-headers":"**Redacted**","server-timing":"**Redacted**","server":"nginx","paypal-debug-id":"**Redacted**","x-paypal-token-service":"**Redacted**","content-type":"application/json","traceparent":"**Redacted**","strict-transport-security":"**Redacted**","cache-control":"max-age=0, no-cache, no-store, must-revalidate","http_x_pp_az_locator":"**Redacted**","pragma":"no-cache","x-backend-info":"**Redacted**","edge-control":"**Redacted**","accept-ranges":"**Redacted**","date":"Sat, 14 Dec 2024 01:56:00 GMT","via":"1.1 varnish","x-served-by":"**Redacted**","x-cache":"**Redacted**","x-cache-hits":"**Redacted**","x-timer":"**Redacted**","vary":"Accept-Encoding"}
Failed to create order: Error
    at v (/var/task/.next/server/app/api/orders/route.js:1:1699)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async g (/var/task/.next/server/app/api/orders/route.js:1:1884)
    at async /var/task/node_modules/next/dist/compiled/next-server/app-route.runtime.prod.js:6:36957
    at async eC.execute (/var/task/node_modules/next/dist/compiled/next-server/app-route.runtime.prod.js:6:27552)
    at async eC.handle (/var/task/node_modules/next/dist/compiled/next-server/app-route.runtime.prod.js:6:38291)
    at async es (/var/task/node_modules/next/dist/compiled/next-server/server.runtime.prod.js:16:25262)
    at async en.responseCache.get.routeKind (/var/task/node_modules/next/dist/compiled/next-server/server.runtime.prod.js:17:1026)
    at async r6.renderToResponseWithComponentsImpl (/var/task/node_modules/next/dist/compiled/next-server/server.runtime.prod.js:17:508)
    at async r6.renderPageComponent (/var/task/node_modules/next/dist/compiled/next-server/server.runtime.prod.js:17:5085)

路由处理代码

import { NextRequest, NextResponse } from 'next/server';
import { Client, Environment, LogLevel, OrdersController, ApiError, CheckoutPaymentIntent } from '@paypal/paypal-server-sdk';

const PAYPAL_CLIENT_ID = process.env.PAYPAL_CLIENT_ID || '';
const PAYPAL_CLIENT_SECRET = process.env.PAYPAL_CLIENT_SECRET || '';

console.info("PayPal Client ID:", process.env.PAYPAL_CLIENT_ID);
console.info("PayPal Client Secret:", process.env.PAYPAL_CLIENT_SECRET);

const client = new Client({
    clientCredentialsAuthCredentials: {
        oAuthClientId: PAYPAL_CLIENT_ID,
        oAuthClientSecret: PAYPAL_CLIENT_SECRET,
    },
    environment: Environment.Sandbox,
    logging: {
        logLevel: LogLevel.Info,
        logRequest: { logBody: true },
        logResponse: { logHeaders: true },
    },
});

const ordersController = new OrdersController(client);

/* Create payment order through PayPal SDK */
const createOrder = async (totalCost: string) => {
  const payload = {
    body: {
      intent: CheckoutPaymentIntent.CAPTURE,
      purchaseUnits: [
        {
          amount: {
            currencyCode: 'USD',
            value: totalCost,
          },
        },
      ],
    },
    prefer: 'return=minimal',
  };

  try {
    const { body, ...httpResponse } = await ordersController.ordersCreate(payload);
    const jsonResponse = JSON.parse(body.toString());

    return {
      jsonResponse,
      httpStatusCode: httpResponse.statusCode,
    };
  } catch (error) {
    if (error instanceof ApiError) {
      throw new Error(error.message);
    }
    return {
      jsonResponse: { error: 'Unknown error occurred' },
      httpStatusCode: 500,
    };
  }
};


export async function POST(req: NextRequest) {
    const {totalCost} = await req.json();
    try {
        const { jsonResponse, httpStatusCode } = await createOrder(totalCost);
        return NextResponse.json(jsonResponse, { status: httpStatusCode });
    } catch (error) {
        console.error('Failed to create order:', error);
        return NextResponse.json({ error: 'Failed to create order.' }, { status: 500 });
    }
}

排查步骤及解决方案

  • 验证Vercel预发布环境的环境变量
    本地环境正常说明凭证本身有效,但Vercel的review/staging环境可能未正确配置:

    1. 进入Vercel项目的Settings → Environment Variables,确认PAYPAL_CLIENT_ID和PAYPAL_CLIENT_SECRET已添加到所有环境(包括Preview/Staging)
    2. 检查变量值是否存在空格、换行或复制错误,尤其是Secret末尾的特殊字符
    3. 重新部署预发布环境,确保新的环境变量生效(Vercel有时需要手动触发部署才能加载更新的变量)
  • 检查环境变量的暴露范围
    PayPal的Secret必须仅在服务器端使用,确认变量未被标记为Expose to Browser,否则可能导致凭证泄露或SDK无法正确读取服务器端变量。

  • 确认PayPal环境匹配
    代码中使用Environment.Sandbox,需确保预发布环境使用的是沙箱凭证,而非生产环境凭证——误将生产凭证用于沙箱环境会直接返回401。

  • 查看完整的PayPal错误响应体
    当前日志仅显示响应状态码和头部,修改错误捕获逻辑打印完整响应体:

    catch (error) {
      if (error instanceof ApiError) {
        console.error('PayPal API Error:', error.statusCode, error.body);
        throw new Error(error.message);
      }
      // 其他逻辑
    }
    

    PayPal的401响应体通常会包含具体原因(如invalid_client、invalid_grant),能直接定位问题。

  • 检查Vercel部署的构建缓存
    如果之前部署时环境变量未正确配置,Next.js的构建缓存可能保留了旧的空值。在Vercel部署时勾选Disable Build Cache,强制重新构建项目。

  • 添加变量校验逻辑
    在代码启动时检查变量是否存在,避免空值传入SDK:

    if (!PAYPAL_CLIENT_ID || !PAYPAL_CLIENT_SECRET) {
      throw new Error('PayPal client credentials are not configured');
    }
    

    这样能在部署日志中直接看到是否是变量缺失问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 15:14:51