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

PayPal沙箱环境Client Authentication失败问题求助

PayPal认证失败(invalid_client)排查方案

我从GitHub下载的示例代码替换Client ID和App Secret后能正常运行,但集成到自己项目时出现错误:

{"error":"invalid_client","error_description":"Client Authentication failed"}

可能的原因及解决办法

  • 环境变量未正确加载
    检查项目根目录的.env文件,确认CLIENT_ID和APP_SECRET拼写无误,无多余空格或引号。同时确保require('dotenv').config()在所有读取环境变量的代码之前执行(比如server.js开头就加载dotenv)。可以在server.js开头添加日志验证:

    console.log("Loaded Client ID:", process.env.CLIENT_ID);
    console.log("Loaded App Secret:", process.env.APP_SECRET);
    

    启动服务后如果输出undefined,说明环境变量未加载成功,需要检查.env文件路径或dotenv配置。

  • 沙箱/生产环境不匹配
    示例代码使用沙箱环境(base = "https://api-m.sandbox.paypal.com"),确认你使用的Client ID和App Secret是沙箱环境生成的。如果是生产环境密钥,需将paypal-api.js中的base URL改为https://api-m.paypal.com。

  • 密钥复制不完整
    检查复制的Client ID和App Secret是否完整,PayPal的密钥是长字符串,复制时避免漏字符或多复制空格。可以直接在PayPal开发者后台重新复制一次密钥。

  • OAuth请求验证
    用Postman直接测试PayPal的OAuth接口,确认密钥有效性:

    1. 将CLIENT_ID:APP_SECRET转换为Base64编码(可通过命令行echo -n "CLIENT_ID:APP_SECRET" | base64生成)
    2. 发送POST请求到https://api-m.sandbox.paypal.com/v1/oauth2/token,请求头添加Authorization: Basic 转换后的Base64字符串,请求体为grant_type=client_credentials(格式选x-www-form-urlencoded)
      如果Postman返回同样的invalid_client错误,说明密钥本身有问题;如果请求成功,说明项目代码中存在密钥加载或转换错误。
  • Base64转换异常
    检查paypal-api.js中generateAccessToken函数的Base64转换逻辑:

    const auth = Buffer.from(CLIENT_ID + ":" + APP_SECRET).toString("base64");
    

    打印转换后的auth字符串,和Postman中使用的Base64编码对比,若不一致则说明密钥拼接时出现问题(比如环境变量为空导致拼接错误)。


相关代码

payment.html

<head>
  <script src="https://www.paypal.com/sdk/js?client-id=Abcbu93zfm5Je0H7he6WL2FB4VqnH9SRSf5SvtDOYnJwFBwJl8M4umFYZVzpbW1oDH3AR9XsbtgRwTEl&currency=USD"></script>
  <link rel="stylesheet" type="text/css" href="https://www.paypalobjects.com/webstatic/en_US/developer/docs/css/cardfields.css" />
</head>

<body>
  <div id="paypal-button-container" class="paypal-button-container"></div>
  <div class="card_container">
    <form id="card-form">
      ......
    </form>
  </div>

  <script src="../Javascript/payment.js"></script>
</body>

payment.js

paypal.Buttons({
    createOrder: function() {
      return fetch("http://localhost:5555/api/orders", {
          method: "post",
          body: JSON.stringify({
            cart: [{
              sku: "<YOUR_PRODUCT_STOCK_KEEPING_UNIT>",
              quantity: "<YOUR_PRODUCT_QUANTITY>",
            }, ],
          }),
        })
        .then((response) => response.json())
        .then((order) => order.id);
    },
    onApprove: function(data) {
      return fetch(`http://localhost:5555/api/orders/${data.orderID}/capture`, {
          method: "post",
        })
        .then((response) => response.json())
        .then((orderData) => {
          console.log("Capture result", orderData, JSON.stringify(orderData, null, 2));
          const transaction = orderData.purchase_units[0].payments.captures[0];
          alert(`Transaction ${transaction.status}: ${transaction.id}\n\nSee console for all available details`);
        });
    },
  })
  .render("#paypal-button-container");

server.js

const paypal = require("./paypal-api");
const express = require('express')
const app = express()
const { port = 5555 } = process.env;
const cors = require('cors');
require('dotenv').config();

app.use(cors());

// payment page
app.get("/payment", async(req, res) => {
  const clientId = process.env.CLIENT_ID;
  try {
    const clientToken = await paypal.generateClientToken();
    res.render("checkout", { clientId, clientToken });
  } catch (err) {
    res.status(500).send(err.message);
    console.log(err);
  }
});

// create order
app.post("/api/orders", async(req, res) => {
  try {
    const order = await paypal.createOrder();
    res.json(order);
  } catch (err) {
    res.status(500).send(err.message);
  }
});

// capture payment
app.post("/api/orders/:orderID/capture", async(req, res) => {
  const { orderID } = req.params;
  try {
    const captureData = await paypal.capturePayment(orderID);
    res.json(captureData);
  } catch (err) {
    res.status(500).send(err.message);
  }
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});

paypal-api.js

const fetch = (...args) =>
  import('node-fetch').then(({ default: fetch }) => fetch(...args));
const { CLIENT_ID, APP_SECRET } = process.env;
const base = "https://api-m.sandbox.paypal.com";

// create order
async function createOrder() {
  const purchaseAmount = "100.00";
  const accessToken = await generateAccessToken();
  const url = `${base}/v2/checkout/orders`;
  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: purchaseAmount }
      }]
    }),
  });
  return handleResponse(response);
}

// capture payment
async function capturePayment(orderId) {
  const accessToken = await generateAccessToken();
  const url = `${base}/v2/checkout/orders/${orderId}/capture`;
  const response = await fetch(url, {
    method: "post",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
  });
  return handleResponse(response);
}

// generate access token
async function generateAccessToken() {
  const auth = Buffer.from(`${CLIENT_ID}:${APP_SECRET}`).toString("base64");
  const response = await fetch(`${base}/v1/oauth2/token`, {
    method: "post",
    body: "grant_type=client_credentials",
    headers: { Authorization: `Basic ${auth}` },
  });
  const jsonData = await handleResponse(response);
  return jsonData.access_token;
}

// generate client token
async function generateClientToken() {
  const accessToken = await generateAccessToken();
  const response = await fetch(`${base}/v1/identity/generate-token`, {
    method: "post",
    headers: {
      Authorization: `Bearer ${accessToken}`,
      "Accept-Language": "en_US",
      "Content-Type": "application/json",
    },
  });
  console.log('response status', response.status);
  const jsonData = await handleResponse(response);
  return jsonData.client_token;
}

async function handleResponse(response) {
  if (response.status === 200 || response.status === 201) {
    return response.json();
  }
  const errorMessage = await response.text();
  throw new Error(errorMessage);
}

module.exports = { createOrder, capturePayment, generateAccessToken, generateClientToken };

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 06:42:08