Xero 自定义集成应用程序 - 节点 sdk 请求失败并显示 403

问题描述 投票:0回答:1

我正在尝试为我的 Xero 组织帐户设置自定义集成,并且我正忙于与演示公司一起构建它。我们已经为实际组织购买了订阅,将在代码投入生产时使用。

我正在尝试通过 xero-node SDK 设置我的第一个 API 函数。

我使用我的客户端 ID 和密钥正确进行身份验证,这会返回访问令牌。然后,我使用此访问令牌发出 API 请求,并在代码中将访问令牌设置为我的 xero 客户端,以便我可以通过 SDK 发出请求。

当我尝试调用 xero.accountingAPI.getContacts 时,出现 403 错误。 我已经仔细检查了我的范围,它们都很好,我的访问令牌解码包括accounting.contacts范围,所以这不应该是问题。

我已经阅读了很多有关 xero-tenant-id 标头的内容,但是当我运行 xero.updateTenants 然后打印出租户列表时,列表中没有租户,它是空的。不过我有点困惑,我的理解是不需要租户 ID,因为它是自定义集成,专门针对我自己的组织,所以不需要它。

  1. 如果需要租户 ID,任何人都可以指出我在哪里获取租户 ID 以及如何在代码中的 xero 客户端中设置它的正确方向吗?

  2. 如果不需要,您能否提供一些指导来说明我的设置还有哪些问题?

我在下面包含了我的代码,该代码在我调用generateClientInXero函数时启动,以及下面的错误代码。

注释 - console.log(xero.tenants) 打印一个空白数组 ([])

谢谢!


const xero = new XeroClient({
  clientId: process.env.XERO_CLIENT_ID,
  clientSecret: process.env.XERO_CLIENT_SECRET,
  grantType: "client_credentials",
  scopes: "accounting.transactions accounting.settings accounting.contacts accounting.settings.read".split(" "),
});

const generateClientInXero = async (company) => {
  try {
    // await generateConsentUrl();

    // Obtain access token
    const tokenSet = await getAccessToken();
    console.log("back with access token", tokenSet);
    // const xero_scopes = ;

    // Set the token in Xero client
    xero.setTokenSet(tokenSet);
    xero.updateTenants();
    console.log(xero.tenants);
    // // Setup client and contacts in Xero
    const clientContactID = await setupClientInXero(company);
  } catch (err) {
    console.error(err);
  }
};

const getClientCredentialsToken = async () => {
  const url = "https://identity.xero.com/connect/token";
  const client_id = process.env.XERO_CLIENT_ID;
  const client_secret = process.env.XERO_CLIENT_SECRET;
  const xero_scopes = "accounting.transactions accounting.settings accounting.contacts accounting.settings.read"; // Assuming xero_scopes is defined somewhere

  // Encode client_id and client_secret to Base64
  const credentials = Buffer.from(`${client_id}:${client_secret}`).toString("base64");

  // Request body parameters
  const data = new URLSearchParams();
  data.append("grant_type", "client_credentials");
  data.append("scope", "accounting.contacts accounting.transactions accounting.settings accounting.settings.read");

  try {
    const response = await axios.post(url, data, {
      headers: {
        Authorization: `Basic ${credentials}`,
        "Content-Type": "application/x-www-form-urlencoded",
      },
    });
    return response.data;
  } catch (error) {
    console.error("Error obtaining access token:", error.response ? error.response.data : error.message);
    throw new Error("Failed to obtain access token");
  }
};
const storeAccessToken = async (accessToken) => {
  const tokenDocRef = firestore.collection("xeroTokens").doc("tokenSet");
  await tokenDocRef.set({
    ...accessToken,
    updatedAt: DateTime.now().toISO(),
  });
};

const loadAccessToken = async () => {
  const tokenDocRef = firestore.collection("xeroTokens").doc("tokenSet");
  const tokenDoc = await tokenDocRef.get();

  if (tokenDoc.exists) {
    const tokenData = tokenDoc.data();
    console.log("Token exists");

    // const currentTimestamp = new Date().getTime(); // Current time in milliseconds
    // const expiresAt = tokenData.expiresAt || 0; // Ensure expiresAt is defined

    // if (expiresAt < currentTimestamp) {
    //   console.log("TOKEN EXPIRED - should get new one now");
    //   throw new Error("Xero access token has expired. Please re-authenticate.");
    // }

    return tokenData;
  } else {
    throw new Error("Xero access token not found. Please authenticate first.");
  }
};

const getAccessToken = async () => {
  let accessToken;
  try {
    accessToken = await loadAccessToken();
  } catch (error) {
    console.log("ERROR IN GET ACCESS TOKEN: ", error);
    accessToken = await getClientCredentialsToken();
    await storeAccessToken(accessToken);
  }
  return accessToken;
};

错误:

{
  "response": {
    "statusCode": 403,
    "body": {
      "Type": null,
      "Title": "Forbidden",
      "Status": 403,
      "Detail": "AuthenticationUnsuccessful",
      "Instance": "43e09fc9-ea0f-4679-b1d5-c907777bacf0",
      "Extensions": {}
    },
    "headers": {
      "content-type": "application/json",
      "content-length": "150",
      "server": "nginx",
      "xero-correlation-id": "43e01324-ea0f-4679-b1d5-c912313132",
      "x-appminlimit-remaining": "9998",
      "expires": "Fri, 28 Jun 2024 13:33:19 GMT",
      "cache-control": "max-age=0, no-cache, no-store",
      "pragma": "no-cache",
      "date": "Fri, 28 Jun 2024 13:33:19 GMT",
      "connection": "close",
      "x-client-tls-ver": "tls1.3",
      "set-cookie": "ak_bmsc=xxxxxxxxxx....; Domain=.xero.com; Path=/; Expires=Fri, 28 Jun 2024 15:33:19 GMT; Max-Age=7200"
    },
    "request": {
      "url": { "protocol": "https:", "port": 443, "host": "api.xero.com", "path": "/api.xro/2.0/Contacts" },
      "headers": {
        "accept": "application/json",
        "content-type": "application/json",
        "user-agent": "xero-node-7.0.0",
        "xero-tenant-id": "[object Object]",
        "authorization": "Bearer xxxxxxxxxxxxxx....",
        "content-length": "2",
        "accept-encoding": "gzip, compress, deflate, br",
        "host": "api.xero.com"
      },
      "method": "GET"
    }
  },
  "body": {
    "Type": null,
    "Title": "Forbidden",
    "Status": 403,
    "Detail": "AuthenticationUnsuccessful",
    "Instance": "43e09fc9-ea0f-4679-b1d5-c912313132",
    "Extensions": {}
  }
}

javascript node.js xero-api xero
1个回答
0
投票

虽然不需要租户 ID,但自定义连接 SDK 是根据与标准代码流相同的开放 API 规范生成的,因此需要租户 ID 标头。

您可以将租户 ID 作为空字符串传递。

请参阅此链接以获取说明

© www.soinside.com 2019 - 2024. All rights reserved.