在对接Xero会计与薪酬系统时,不少技术团队需要把员工工资单从Xero同步到内部HR或财务系统。Xero提供了基于REST风格的Payroll API,其中的Payslip资源记录了每一次发薪周期内每位员工的税后收入、扣款与净支付金额。不同于普通的发票或账单接口,Payslip数据受到更严格的权限控制,并且必须依附于某个具体的PayRun(发薪批次)。理解这套层级关系,是后续稳定获取列表的前提。

认证方式选择与AccessToken获取
Xero开放平台目前主推OAuth2授权码模式,旧版的私钥证书方式虽仍可用,但新建应用已不再支持。OAuth2要求开发者在Xero Developer Portal中配置回调地址,并为应用申请payroll.employees、payroll.payruns以及payroll.payslips等作用域。授权完成后,系统会返回access_token与refresh_token,其中access_token有效期通常为30分钟,需要利用refresh_token定期轮换。
下面是一段使用Python requests库完成授权码交换的示例。代码中将client_id与client_secret放在请求体里,并指定grant_type为authorization_code。实际生产环境应把这些敏感信息存入环境变量或密钥管理服务,避免硬编码带来的泄露风险。
import requests
token_url = 'https://identity.xero.com/connect/token'
client_id = 'YOUR_CLIENT_ID'
client_secret = 'YOUR_CLIENT_SECRET'
redirect_uri = 'https://ipipp.com/callback'
auth_code = 'RECEIVED_AUTH_CODE'
resp = requests.post(token_url, data={
'grant_type': 'authorization_code',
'code': auth_code,
'redirect_uri': redirect_uri,
'client_id': client_id,
'client_secret': client_secret
})
token_data = resp.json()
print(token_data.get('access_token'))
print(token_data.get('refresh_token'))
如果团队仍维护老系统,可能会遇到使用RSA私钥签名请求的方式。这种方式不需要用户交互授权,但Xero官方已明确逐渐废弃,且调试难度高。从长期维护成本看,OAuth2虽然多一步前端跳转,却更利于审计与权限回收。因此在获取Payslip列表前,优先确认当前租户使用的是哪种认证,再决定调用基类。
调用Payslip列表接口与分页处理
Xero的Payslip列表接口路径通常为/api.xro/2.0/Payroll/Payslips,需要带上租户标识头Xero-Tenant-Id。该接口支持QueryString参数如Page与PageSize,默认每页返回一百条。当企业员工规模较大、历史发薪批次多时,必须循环翻页才能拿全数据。接口本身不提供游标,只能依靠页码递增,直到返回空数组为止。
另一个常见需求是按PayRun过滤。虽然接口没有直接暴露payrunId参数,但可以在拿到列表后依据响应体里的PayRunID字段做内存筛选,或者先调用PayRuns接口拿到目标批次,再逐个员工关联。下面的代码展示了带分页逻辑的拉取过程,并把结果存入本地列表以便后续解析。
import requests
base_url = 'https://api.xero.com/api.xro/2.0/Payroll/Payslips'
headers = {
'Authorization': 'Bearer ' + access_token,
'Xero-Tenant-Id': tenant_id,
'Accept': 'application/json'
}
all_payslips = []
page = 1
while True:
resp = requests.get(base_url, headers=headers, params={'Page': page, 'PageSize': 100})
data = resp.json()
batch = data.get('Payslips', [])
if not batch:
break
all_payslips.extend(batch)
page += 1
print('Total payslips fetched:', len(all_payslips))
需要注意的是,Xero对API调用频率有限流,免费租户每分钟约六十次请求。若员工基数过万,串行翻页可能触发限流错误,此时应引入指数退避重试,或采用多线程分批拉取不同页码。同时,响应中的日期字段如PaymentDate采用UTC时间,前端展示时要转换成本地时区,否则会出现差一天的对账异常。
响应字段解析与数据落地建议
每一条Payslip记录包含员工标识、发薪周期、总收入、税额、扣除项与净发金额。其中EmployeeID可与HR系统的工号映射,GrossEarnings与Tax是财务对账核心。部分字段为只读,例如CreatedDateUTC,在写入自有库时应原样保留以方便增量比对。
为了避免重复同步,建议在本地表建立联合唯一索引,例如(tenant_id, payslip_id)。每次拉取后先做upsert,再统计异常记录。以下片段演示了如何用简单结构打印关键字段,实际项目中可替换为数据库批量插入。
for slip in all_payslips:
emp_id = slip.get('EmployeeID')
gross = slip.get('GrossEarnings')
tax = slip.get('Tax')
net = slip.get('NetPay')
pay_run = slip.get('PayRunID')
print(emp_id, pay_run, gross, tax, net)
当工资单涉及多个薪资组件时,Xero会在EarningsLines与DeductionLines中给出明细。如果内部系统需要展示工资条,应把这些子数组展开成行项目,而非仅存总额。这样在员工质疑某笔扣款时,能够快速定位到具体规则。此外,Payslip一旦随PayRun冻结便不可修改,因此同步逻辑不必处理更新冲突,只需关注新增与首次拉取的补录即可。