PAYATHON 2026

XERO API 更新 payment amount 时出现 validation error

支付小周

结论

Xero API 不支持将已有 payment 的 Amount 更新为 0.00。Payment 创建后,不能通过修改金额把它当作 invoice note 使用。对于已有 payment,通常只能将其标记为 DELETED。是否可以删除,还要看付款状态、会计期间及其他业务限制。

如果要给 invoice 添加备注,应使用 invoice 的 History & Notes 接口。如果确实需要修改付款金额,应先删除原 payment,再用正确的金额创建新的 payment。

为什么会出现 validation error

下面的请求尝试修改一笔已有付款的金额:

api.Payments.Update(new Payment
{
    Id = payment.Id,
    Amount = 0
});

这个请求存在两个问题:

  • 已创建 payment 的金额通常不能直接修改。
  • 0.00 不是有效的实际付款金额。Payment 代表一笔真实的付款分配,不适合作为备注或业务状态标记。

因此,SDK 即使只返回笼统的 validation error,也不表示请求缺少某个更新参数。问题在于该操作本身不符合 Payment 的更新规则。

将 payment 标记为删除则是受支持的操作:

api.Payments.Update(new Payment
{
    Id = payment.Id,
    Status = PaymentStatus.Deleted
});

为 invoice 添加 note

Xero Accounting API 提供了 invoice 的 History & Notes 相关接口。对应的 REST 请求通常为:

PUT /api.xro/2.0/Invoices/{InvoiceID}/History

请求体需要包含一条 HistoryRecord:

{
  "HistoryRecords": [
    {
      "Details": "Payment was reviewed and requires manual follow-up."
    }
  ]
}

下面是使用 HttpClient 调用该接口的示例:

using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;

var invoiceId = "INVOICE_ID";

var json = """
{
  "HistoryRecords": [
    {
      "Details": "Payment was reviewed and requires manual follow-up."
    }
  ]
}
""";

using var request = new HttpRequestMessage(
    HttpMethod.Put,
    $"https://api.xero.com/api.xro/2.0/Invoices/{invoiceId}/History");

request.Headers.Authorization =
    new AuthenticationHeaderValue("Bearer", accessToken);

request.Headers.Add("xero-tenant-id", tenantId);
request.Content = new StringContent(json, Encoding.UTF8, "application/json");

using var response = await httpClient.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();

if (!response.IsSuccessStatusCode)
{
    throw new InvalidOperationException(
        $"Xero request failed: {(int)response.StatusCode} {responseBody}");
}

Xero SDK 版本不同,具体方法名也可能不同,例如 CreateInvoiceHistory、CreateInvoiceHistoryAsync 或类似名称。如果旧版 SDK 没有提供相应方法,可以升级 SDK,也可以直接调用对应的 REST endpoint。

如果需要修改 payment 金额

不要尝试把金额更新为零。可以按以下步骤处理:

  1. 确认原 payment 是否可以删除。
  2. 将原 payment 的状态更新为 DELETED。
  3. 使用正确的金额创建新的 payment。
  4. 如果需要记录修改原因,再向 invoice 添加一条 history note。

示例代码如下:

if (payment != null && payment.Status != PaymentStatus.Deleted)
{
    api.Payments.Update(new Payment
    {
        Id = payment.Id,
        Status = PaymentStatus.Deleted
    });

    api.Payments.Create(new Payment
    {
        Invoice = new Invoice
        {
            Id = invoice.Id
        },
        Account = new Account
        {
            Id = account.Id
        },
        Amount = correctedAmount,
        Date = paymentDate
    });
}

代码中的属性名称取决于所使用的 Xero .NET SDK 版本,请以当前 SDK 的模型定义为准。

注意事项

  • 已完成银行对账、处于锁定会计期间或受其他会计规则限制的 payment,可能无法直接删除。
  • History note 仅用于说明和审计记录,不会改变 invoice 余额、付款状态或会计分录。
  • 不要创建零金额 payment 来充当备注、占位符或状态标记。
  • 捕获异常时,应记录 Xero 返回的完整响应体。详细的 ValidationErrors 通常位于响应中的元素级错误信息内,SDK 抛出的顶层异常消息可能不会完整显示这些内容。
  • 如果内部备注不应出现在 Xero 的历史记录中,可以将其保存在本地数据库,并通过 InvoiceID 或业务引用与 Xero invoice 关联。

备注:内容仅供参考。