Xero API 创建发票时能否自定义发票编号?
结论
可以通过 Xero Accounting API 创建发票,并在请求中使用 InvoiceNumber 传入自定义发票编号,例如 WBS-6YCP2ZF。
使用时需注意:
- Xero 发票的标准字段和明细列不能随意改成预订系统的字段结构。
Booking ID、Guest Name、Tour Name等信息通常需要组合写入明细的Description,也可以根据业务需要使用Reference、Tracking Categories 等标准字段。- 自定义发票编号必须保持唯一,同时符合 Xero 当前的格式和长度限制。
DPS过去通常指DPS Payment Express,该服务后来更名为Windcave。Xero 中实际显示的名称和接入方式可能因组织所在地区及界面版本而异。
1. 创建发票应使用哪个 API?
创建发票需要调用 Xero Accounting API 的 Invoices 接口:
POST https://api.xero.com/api.xro/2.0/Invoices
调用前需要完成以下配置:
- 在 Xero Developer Portal 创建 OAuth 2.0 应用。
- 完成用户授权。
- 获取
access_token。 - 获取并保存目标组织的
tenantId。 - 申请包含发票写入权限的 scope,例如:
accounting.transactions
请求通常需要携带以下 HTTP 头:
Authorization: Bearer ACCESS_TOKEN
Xero-tenant-id: TENANT_ID
Accept: application/json
Content-Type: application/json
Laravel 项目既可以直接使用 HTTP Client 调用接口,也可以使用 Xero 官方或社区维护的 PHP SDK。无论选择哪种方式,都需要正确处理 OAuth token 刷新和 tenantId 管理。
2. 能否自定义 Xero 发票字段?
不能像修改自己的数据库表那样,在 Xero 发票中随意增加 Booking ID、Guest Name、Tour Name 等自定义列。通过 API 创建发票时,数据必须符合 Xero 定义的发票模型。
常用的发票字段有:
TypeContactDateDueDateInvoiceNumberReferenceLineItemsStatus
常用的明细字段有:
DescriptionQuantityUnitAmountAccountCodeTaxTypeTracking
旅游预订系统可以参考下面的映射方式:
| 预订系统字段 | 建议写入 Xero 的位置 |
|---|---|
| Date of Sale | 发票的 Date,或者写入 Description |
| Booking ID | Reference 或 Description |
| Guest Name | Description |
| Tour Name | Description |
| 数量 | Quantity |
| 单价 | UnitAmount |
例如,一条预订可以转换为一条 Xero 发票明细:
{
"Description": "Booking: 6YCP2ZF | Guest: Zhang San | Tour: Great Ocean Road",
"Quantity": 2,
"UnitAmount": 150.00,
"AccountCode": "200"
}
如果一张代理商发票包含多个预订,可以让每个预订对应一条 LineItem。如果每个预订还有多个收费项目,也可以继续拆分,但要避免生成过于庞大的发票。
Reference 属于发票级字段,适合保存代理商批次号、主预订号或外部系统的发票 ID。如果一张发票中有多个 Booking ID,就不适合全部放在 Reference 中。
Tracking Categories 可用于保存需要在 Xero 报表中分类统计的信息,例如:
- Tour Type
- Sales Channel
- Branch
Tracking Categories 并不是可以任意扩展的字段。使用前必须在 Xero 中配置对应的 Tracking Category 和 Option。
3. 是否可以传入自定义发票编号?
可以,在创建发票的请求中设置 InvoiceNumber:
{
"Type": "ACCREC",
"InvoiceNumber": "WBS-6YCP2ZF"
}
其中:
ACCREC表示销售发票,也就是应收账款发票。ACCPAY表示供应商账单,也就是应付账款发票。
完整请求示例如下:
{
"Invoices": [
{
"Type": "ACCREC",
"Contact": {
"ContactID": "00000000-0000-0000-0000-000000000000"
},
"Date": "2026-08-29",
"DueDate": "2026-09-12",
"InvoiceNumber": "WBS-6YCP2ZF",
"Reference": "Agency batch 2026-08-29",
"Status": "DRAFT",
"LineItems": [
{
"Description": "Booking: 6YCP2ZF | Guest: Zhang San | Tour: Island Day Tour",
"Quantity": 2,
"UnitAmount": 150.00,
"AccountCode": "200"
},
{
"Description": "Booking: 6YCP31A | Guest: Li Si | Tour: City Walking Tour",
"Quantity": 1,
"UnitAmount": 90.00,
"AccountCode": "200"
}
]
}
]
}
AccountCode 必须是目标 Xero 组织中真实存在且适用于该交易的科目代码,不能直接照搬示例值。
Laravel 调用示例
下面使用 Laravel HTTP Client 发送请求:
<?php
use Illuminate\Support\Facades\Http;
$accessToken = $xeroAccessToken;
$tenantId = $xeroTenantId;
$payload = [
'Invoices' => [
[
'Type' => 'ACCREC',
'Contact' => [
'ContactID' => $xeroContactId,
],
'Date' => now()->toDateString(),
'DueDate' => now()->addDays(14)->toDateString(),
'InvoiceNumber' => 'WBS-6YCP2ZF',
'Reference' => 'Booking batch 2026-08-29',
'Status' => 'DRAFT',
'LineItems' => [
[
'Description' => implode(' | ', [
'Booking: 6YCP2ZF',
'Guest: Zhang San',
'Tour: Island Day Tour',
]),
'Quantity' => 2,
'UnitAmount' => 150.00,
'AccountCode' => '200',
],
],
],
],
];
$response = Http::withToken($accessToken)
->withHeaders([
'Xero-tenant-id' => $tenantId,
'Accept' => 'application/json',
])
->post(
'https://api.xero.com/api.xro/2.0/Invoices',
$payload
);
if ($response->failed()) {
throw new RuntimeException(
'Unable to create Xero invoice: ' . $response->body()
);
}
$xeroInvoice = $response->json('Invoices.0');
在生产环境中,不能只依赖 $response->failed() 判断请求是否成功。Xero 可能返回字段级验证错误,因此还需要记录并处理响应中的验证信息。
自定义编号的注意事项
保证编号唯一
同一个 Xero 组织内不应重复使用发票编号。建议在本地数据库中同时保存:
local_invoice_id
xero_invoice_id
invoice_number
xero_status
synced_at
发票创建成功后,应保存 Xero 返回的 InvoiceID。后续查询、更新或同步状态时,优先使用 InvoiceID,不要只依赖可读的发票编号。
防止重复创建
HTTP 请求超时并不代表 Xero 没有创建发票。请求可能已经在 Xero 端成功,只是响应未能返回本地系统。
比较安全的处理流程是:
- 在本地生成唯一且稳定的
InvoiceNumber。 - 调用 Xero API。
- 创建成功后保存返回的
InvoiceID。 - 如果请求超时,先按发票编号查询 Xero。
- 确认发票不存在后再重试创建。
如果超时后直接重试,可能会生成重复发票。
先创建草稿
刚开始集成时,建议使用:
"Status": "DRAFT"
确认联系人、税率、科目、金额和明细映射都正确后,再决定是否直接创建为:
"Status": "AUTHORISED"
直接授权可能影响正式账务记录,是否使用该状态应结合企业的审核流程决定。
编号长度和字符限制
WBS-6YCP2ZF、AATK-1007、MMDR-1008 这类由字母、数字和连字符组成的编号,通常适合作为发票编号。不过,Xero 的字段限制可能随着 API 版本或产品规则调整。上线前应以当前 Accounting API 文档对 InvoiceNumber 的限制为准,并在本地增加长度、字符和唯一性校验。
4. DPS 是否代表 Payment Express?
是的,Xero 中过去使用的 DPS 通常指 DPS Payment Express。该支付服务后来更名为 Windcave。
Xero 当前显示的支付服务可能受以下因素影响:
- Xero 组织所在的国家或地区
- 组织使用的 Xero 产品版本
- 支付服务商当前使用的品牌名称
- Xero 和支付服务商当前是否仍支持新连接
如果设置页面仍然显示 DPS,通常可以将其理解为 Payment Express 的相关集成。正式配置前,还应核对页面跳转后的服务商域名、商户协议和 Xero 当前帮助文档,尤其不要只根据名称提交生产环境的支付凭据。
备注:内容仅供参考。
