我正在尝试为我的 Xero 组织帐户设置自定义集成,并且我正忙于与演示公司一起构建它。我们已经为实际组织购买了订阅,将在代码投入生产时使用。
我正在尝试通过 xero-node SDK 设置我的第一个 API 函数。
我使用我的客户端 ID 和密钥正确进行身份验证,这会返回访问令牌。然后,我使用此访问令牌发出 API 请求,并在代码中将访问令牌设置为我的 xero 客户端,以便我可以通过 SDK 发出请求。
当我尝试调用 xero.accountingAPI.getContacts 时,出现 403 错误。 我已经仔细检查了我的范围,它们都很好,我的访问令牌解码包括accounting.contacts范围,所以这不应该是问题。
我已经阅读了很多有关 xero-tenant-id 标头的内容,但是当我运行 xero.updateTenants 然后打印出租户列表时,列表中没有租户,它是空的。不过我有点困惑,我的理解是不需要租户 ID,因为它是自定义集成,专门针对我自己的组织,所以不需要它。
如果需要租户 ID,任何人都可以指出我在哪里获取租户 ID 以及如何在代码中的 xero 客户端中设置它的正确方向吗?
如果不需要,您能否提供一些指导来说明我的设置还有哪些问题?
我在下面包含了我的代码,该代码在我调用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": {}
}
}