Xero Contacts API only updates “Bills” payment terms but ignores “Sales” payment terms
07:28 03 Nov 2025

Title:

Xero Contacts API only updates “Bills” payment terms but ignores “Sales” payment terms (no error returned)

Body:

I’m working on a Salesforce → Xero integration that updates contact payment terms using the Xero Contacts API (POST /api.xro/2.0/Contacts).

The payload I’m sending includes both Bills and Sales payment terms like this:

{
  "Contacts": [
    {
      "ContactID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
      "PaymentTerms": {
        "Bills": { "Day": 7, "Type": "DAYSAFTERBILLDATE" },
        "Sales": { "Day": 7, "Type": "DAYSAFTERBILLDATE" }
      }
    }
  ]
}

Full Code:

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 inputList) {
        for (AccountInput input : inputList) {
            List 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 payload = new Map{
            'Contacts' => new List{
                new Map{
                    'ContactID' => xeroContactId,
                    'PaymentTerms' => new Map{
                        'Bills' => new Map{
                            'Day' => terms.Day,
                            'Type' => terms.Type
                        },
                        'Sales' => new Map{
                            '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());
        }
    }
}

The Problem:

The request succeeds with HTTP 200, and the contact’s Purchase defaults (Bills) are updated correctly in Xero. However, the Sales defaults are not being updated — they remain unchanged, and there’s no error or validation message in the response.

Has anyone successfully updated both Bills and Sales payment terms for a contact via the /api.xro/2.0/Contacts endpoint?

salesforce integration apex xero