如何为 Xero Payment API 设置分页?
结论
GET Payments 通常只支持通过 page 参数翻页,不能指定每页返回多少条数据。请求需要从第 1 页开始,逐页获取,直到接口返回空数组或不足一页的数据。
Payment 本身不包含完整的 LineItems,通常只会提供关联发票等对象的引用。要获取明细行,可以根据 Payment 中的 InvoiceID 调用 GET Invoices/{InvoiceID};也可以先分页同步发票,再通过 InvoiceID 关联两类数据。
分页获取全部 Payments
Accounting API 的请求形式如下:
GET https://api.xero.com/api.xro/2.0/Payments?page=1
GET https://api.xero.com/api.xro/2.0/Payments?page=2
GET https://api.xero.com/api.xro/2.0/Payments?page=3
由于不能设置每页数量,不要依赖自定义 pageSize。可以持续请求下一页,直到:
Payments为空;或- 返回数量少于接口固定的单页上限。
固定上限可能发生变化,因此以返回空页作为最终停止条件更稳妥。
async function getAllPayments(accessToken, tenantId) {
const payments = [];
for (let page = 1; ; page += 1) {
const response = await fetch(
`https://api.xero.com/api.xro/2.0/Payments?page=${page}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Xero-tenant-id": tenantId,
Accept: "application/json"
}
}
);
if (!response.ok) {
throw new Error(
`GET Payments failed: ${response.status} ${await response.text()}`
);
}
const data = await response.json();
const currentPage = data.Payments ?? [];
if (currentPage.length === 0) {
break;
}
payments.push(...currentPage);
}
return payments;
}
获取关联发票的 LineItems
Payment 返回的 Invoice 通常只是关联对象的摘要,不能假定其中包含完整的 LineItems。可以提取并去重 InvoiceID,再逐一读取发票详情:
async function getInvoice(invoiceId, accessToken, tenantId) {
const response = await fetch(
`https://api.xero.com/api.xro/2.0/Invoices/${encodeURIComponent(invoiceId)}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Xero-tenant-id": tenantId,
Accept: "application/json"
}
}
);
if (!response.ok) {
throw new Error(
`GET Invoice failed: ${response.status} ${await response.text()}`
);
}
const data = await response.json();
return data.Invoices?.[0] ?? null;
}
async function getPaymentsWithInvoiceLines(accessToken, tenantId) {
const payments = await getAllPayments(accessToken, tenantId);
const invoiceIds = [
...new Set(
payments
.map(payment => payment.Invoice?.InvoiceID)
.filter(Boolean)
)
];
const invoicesById = new Map();
// 使用串行请求便于控制速率;生产环境也可以采用有限并发。
for (const invoiceId of invoiceIds) {
const invoice = await getInvoice(invoiceId, accessToken, tenantId);
if (invoice) {
invoicesById.set(invoiceId, invoice);
}
}
return payments.map(payment => {
const invoiceId = payment.Invoice?.InvoiceID;
return {
...payment,
InvoiceDetails: invoiceId
? invoicesById.get(invoiceId) ?? null
: null
};
});
}
发票行可以从以下位置读取:
result[0].InvoiceDetails?.LineItems
数据量较大时的做法
如果逐笔读取发票,API 请求数量可能会很大。数据量较多时,可以采用下面的同步流程:
- 分页拉取 Payments。
- 分页拉取 Invoices,并保存完整的发票数据。
- 通过
InvoiceID在本地关联两类数据。 - 后续使用
If-Modified-Since等增量机制更新,避免每次全量读取。
筛选参数、分页大小参数和增量请求头是否可用,应以当前使用的 Xero Accounting API 版本及 SDK 文档为准。其他 endpoint 支持的参数不一定适用于 GET Payments。
注意事项
- Payment 可能关联 Invoice、Credit Note、Prepayment 或 Overpayment,不能假定每条记录都有
Payment.Invoice.InvoiceID。 - 同一张发票可能对应多笔 Payment,因此要先对
InvoiceID去重。 - 需要处理
401、429和5xx响应。遇到限流时,应按照响应头指定的等待时间重试。 - 分页过程中,数据可能发生变化。需要稳定同步时,可以配合时间范围或增量条件,并按
PaymentID去重。 - 不要根据 Payment 的总金额推导各个
LineItem的实际付款分摊。Payment 通常针对整张发票,支付金额与发票明细行之间未必存在一一对应关系。
备注:内容仅供参考。