Xero Contacts API仅更新Bills付款条款,忽略Sales条款且无报错
我正在开发Salesforce到Xero的集成,通过Xero Contacts API(POST /api.xro/2.0/Contacts)更新联系人付款条款。发送的请求Payload同时包含Bills和Sales付款条款,请求返回HTTP 200成功,联系人的采购默认值(Bills)在Xero中更新正确,但销售默认值(Sales)未发生变化,且响应中无任何错误或验证提示。请问是否有人成功通过该接口同时更新联系人的Bills和Sales付款条款?
请求Payload示例
{ "Contacts": [ { "ContactID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "PaymentTerms": { "Bills": { "Day": 7, "Type": "DAYSAFTERBILLDATE" }, "Sales": { "Day": 7, "Type": "DAYSAFTERBILLDATE" } } } ] }
完整Apex代码
public class XeroAPIHandler { public class AccountInput { @InvocableVariable(required=true) public Id accountId; } @InvocableMethod(label='Update Xero Payment Terms' description='Updates Xero contact with payment terms based on Account field') public static void updatePaymentTermsFromFlow(List<AccountInput> inputList) { for (AccountInput input : inputList) { List<Account> acc = [SELECT Id, Name, Xero_Account_Number__c, Payment_Terms__c, Account_State__c FROM Account WHERE Id = :input.accountId LIMIT 1]; if (acc.size() > 0) { String tenantId = tenantIdForState(acc[0].Account_State__c); String namedCredentialName = namedCredForState(acc[0].Account_State__c); updatePaymentTerms(acc[0].Xero_Account_Number__c, acc[0].Payment_Terms__c, tenantId, namedCredentialName); } } } public static String tenantIdForState(String state) { if (String.isBlank(state)) return null; switch on state { when 'ABC' { return Label.Xero_Tenant_Id_ABC; } when 'DEF' { return Label.Xero_Tenant_Id_DEF; } when 'GHI' { return Label.Xero_Tenant_Id_GHI; } when 'JK' { return Label.Xero_Tenant_Id_JK; } when else { return null; } } } public static String namedCredForState(String state) { if (String.isBlank(state)) return null; switch on state { when 'ABC' { return 'Xero_ABC'; } when 'DEF' { return 'Xero_DEF'; } when 'GHI' { return 'Xero_GHI'; } when 'JK' { return 'Xero_JK'; } when else { return null; } } } @future(callout=true) public static void updatePaymentTerms(String AccountNumber, String term, String tenantId, String namedCredentialName) { String xeroContactId = AccountNumber; if (String.isBlank(xeroContactId)) return; XeroPaymentTermMapper.XeroPaymentTerm terms = XeroPaymentTermMapper.mapPaymentTerm(term); if (terms == null) return; Map<String, Object> payload = new Map<String, Object>{ 'Contacts' => new List<Object>{ new Map<String, Object>{ 'ContactID' => xeroContactId, 'PaymentTerms' => new Map<String, Object>{ 'Bills' => new Map<String, Object>{ 'Day' => terms.Day, 'Type' => terms.Type }, 'Sales' => new Map<String, Object>{ 'Day' => terms.Day, 'Type' => terms.Type } } } } }; HttpRequest req = new HttpRequest(); req.setEndpoint('callout:' + namedCredentialName + '/api.xro/2.0/Contacts'); req.setHeader('xero-tenant-id', tenantId); req.setMethod('POST'); req.setHeader('Content-Type', 'application/json'); req.setBody(JSON.serialize(payload)); Http http = new Http(); try { HttpResponse res = http.send(req); System.debug('Xero response: ' + res.getBody()); } catch (Exception e) { System.debug('Xero callout failed: ' + e.getMessage()); } } }
可能的解决方案
1. 确认Xero API权限范围
确保连接Xero的应用已获得contacts的读写权限,部分权限配置可能仅允许更新采购相关字段,需检查应用权限设置中是否包含完整的联系人字段修改权限。
2. 验证字段命名的准确性
Xero API对字段大小写敏感,确认Sales字段的拼写和嵌套结构完全符合官方文档要求。可以尝试将Sales替换为SalesTerms(部分旧版API或特定场景下的字段名差异),或者直接核对Xero Contacts API的最新字段定义。
3. 检查联系人类型与默认设置
- 如果该联系人在Xero中被标记为仅供应商,则销售相关字段(包括Sales付款条款)可能无法被更新,需确认联系人的类型设置为“客户/供应商”或“客户”。
- 检查Xero中该联系人是否存在销售默认值的全局覆盖设置,部分租户级配置可能会限制联系人级别的销售条款修改。
4. 调整请求方法或端点
虽然POST方法用于创建/更新联系人,但部分场景下使用PUT /api.xro/2.0/Contacts/{ContactID}进行单联系人更新可能更可靠,尝试切换为PUT请求并简化Payload,仅包含需要更新的Sales付款条款字段,测试是否生效。
5. 深入调试响应内容
当前代码仅打印响应Body,建议添加对响应Header的调试,同时检查返回的Contacts对象中Sales付款条款是否被正确返回:
// 修改调试代码 System.debug('Xero response status: ' + res.getStatusCode()); System.debug('Xero response headers: ' + res.getHeaders()); System.debug('Xero response body: ' + res.getBody()); // 解析响应中的Contact对象,查看Sales字段是否被正确更新 Map<String, Object> responseMap = (Map<String, Object>)JSON.deserializeUntyped(res.getBody()); List<Object> contacts = (List<Object>)responseMap.get('Contacts'); if(contacts != null && contacts.size() > 0){ Map<String, Object> contact = (Map<String, Object>)contacts[0]; Map<String, Object> paymentTerms = (Map<String, Object>)contact.get('PaymentTerms'); if(paymentTerms != null){ System.debug('Response Sales Terms: ' + paymentTerms.get('Sales')); } }
如果响应中Sales字段未被返回或值不正确,说明Xero API未处理该字段的更新请求,需进一步排查字段权限或API版本问题。
6. 确认API版本兼容性
当前使用的是Xero API 2.0,检查是否存在版本差异导致Sales字段不被支持,尝试升级到最新的API版本(如2.1),或查阅Xero API的版本变更日志,确认PaymentTerms.Sales字段的支持情况。
内容的提问来源于stack exchange,提问作者Ob1Newb

