XERO is a cloud-based accounting software platform that allows businesses to manage their finances in real-time.

“Here, we have integrated Xero with Salesforce.

First, you need to create a Xero account. Inside the account, create an app and generate the client ID and client secret for authorization and authentication. You can refer to this website for your API reference and documents as well.”

1. Create Xero Account

In order to use Xero, you need to register yourself on Xero.

2.Get Client id and Client secret

Go to this link, create an app, and obtain the client ID and client secret for the Configurations tab.

3.Create Auth providers in salesforce .

Create Auth providers in Salesforce and fill in all the information in the Auth Provider. After that, provide the callback URL from Salesforce to register the callback URL. Prompt the authentication, allow, and accept the terms and conditions. for more details click the link.1 , link2

4.Account Sync Process.

we are using the account record to sync as a contact from Salesforce to Xero.

I have created a separate Visualforce page for this purpose and a controller to pass the data at runtime via an action button. The customer ID is also stored as a response. In order to sync the invoice , you have to pass the customer ID and then synchronize the invoice for that particular Contact.

public class XeroCustomerController {
	
   public  XeroCustomerController(){
        
    }
    
    public static Id accId{get;set;}
    public Account acc {get;set;}
    
    public XeroCustomerController(ApexPages.StandardController controller){
         accId = controller.getRecord().Id;
         Account  acc = XeroCustomerHelper.getAccount(accId);
        System.debug('acc is '+acc);
      
        
    }
    
    
    @future(callout = true)
    public static void createCustomer(Id accId){
       Account  acc = XeroCustomerHelper.getAccount(accId); 
         XeroContact xeroContact = new XeroContact(acc);
        try {
            XeroContact responseContact = XeroAPI.sendContact(xeroContact);
            System.debug('Contact sent successfully to Xero. Xero ID: ' + responseContact.ContactID);
            update new Account (id=accId,Xero_Customer_ID__c=responseContact.ContactID);
        } catch (XeroApiException e) {
            System.debug('Error sending contact to Xero: ' + e.getMessage());
        }
        
    }
       
    public Pagereference syncXero(){
        XeroCustomerController.createCustomer(accId);
        return new PageReference('/'+accId);
        
         
    }
     
}
public class XeroCustomerHelper {
	
     public static Account getAccount(Id accId){
        return [SELECT Id,
                      Name, 
                      ShippingCity,
                      ShippingStreet,
                      ShippingState,
                      ShippingCountry,
                      ShippingPostalCode,
                      phone,
                	  fax,
                     Email__c,
                      BillingCity,
                      BillingStreet,
                      BillingCountry,
                      BillingPostalCode,
                      BillingState,
                      BillingLatitude,
                      BillingLongitude
              FROM Account 
              WHERE Id =: accId];
    }
}
public class XeroAPICall {
	
	public static XeroContact sendContact (XeroContact xeroContact) {
        String resourceName = 'Contacts';
        HttpResponse response = XeroServiceClass.getCallout('POST', resourceName, xeroContact.serialize());
        Boolean isSuccess = response.getStatusCode() < 300;
		String jsonSerialized = XeroUtils.generateJsonStringForParsing(response.getBody(), isSuccess ? resourceName : 'Elements');
		List<XeroContact> xeroContacts = (List<XeroContact>) JSON.deserialize(jsonSerialized, List<XeroContact>.class);
		if (isSuccess) {
			return xeroContacts[0];
		}
    }
	
	
	public static XeroInvoice sendInvoice (XeroInvoice xeroInvoice) {
        String resourceName = 'Invoices';
        HttpResponse response = XeroServiceClass.getCallout('POST', resourceName, xeroInvoice.serialize());
        Boolean isSuccess = response.getStatusCode() < 300;
            String jsonSerialized = XeroUtils.generateJsonStringForParsing(response.getBody(), isSuccess ? resourceName : 'Elements');
            List<XeroInvoice> xeroInvoices = (List<XeroInvoice>) JSON.deserialize(jsonSerialized, List<XeroInvoice>.class);
            if (isSuccess) {

                return xeroInvoices[0];
            }
            
        
    }

}
public class XeroServiceClass {
    
    public static HttpResponse getCallout (String method, String resourceName) {
        return executeCallout(method, resourceName, null, null);
    }
    
    public static HttpResponse getCallout (String method, String resourceName, String requestBody) {
        return executeCallout(method, resourceName, requestBody, null);
    }
    
    public static HttpResponse getCallout (String method, String resourceName, String requestBody, Map<String, String> headers) {
        
        String errorMessage = '';
        
        HttpRequest request = new HttpRequest();
        request.setMethod(method);
        request.setEndpoint( 'callout:Xero/' + (resourceName.equalsIgnoreCase('connections') ? 'connections': 'api.xro/2.0/' + resourceName) );
        
        request.setHeader('Accept', 'application/json');
        
        if(resourceName.equalsIgnoreCase('connections')){
            request.setHeader('xero-tenant-id', '');
        }else{
            request.setHeader('xero-tenant-id', XeroTenant .getXeroTenantId());
        }
            request.setTimeout(120000);
            request.setBody(requestBody);
            request.setHeader('Content-Type', 'application/json');
            for (String headerKey :headers.keySet()) {
                request.setHeader(headerKey, headers.get(headerKey));
            }
        
        HttpResponse response = new HttpResponse();
        response = new Http().send(request);
        System.debug('respons'+response);
        return response;
    }  
}
global class XeroTenant {
	  global static String getXeroTenantId () {
        HttpResponse response = XeroServiceClass.getCallout('GET', 'connections'); 
        if (response.getStatusCode() < 300) {
            List<XeroConnection> xeroConnections = (List<XeroConnection>) JSON.deserialize(response.getBody(), List<XeroConnection>.class);
            return xeroConnections[0].tenantId;
        }
        else {
            System.debug('error occurred');
        }
    }
}
public with sharing class XeroConnection {
    
    public String id;
    public String tenantId;	
    public String tenantType;
    public String createdDateUtc;
    public String updatedDateUtc;
}

5. Account setup in Salesforce

After completing these steps, create a button on the Account record page and call the Visualforce page, then save it. Add the button to the page layout and save the changes.

After clicking the button, your account data, including name, Phone, billing address,shipping address will sync with the Xero contact. You can add more fields as needed based on your business requirements. Now, check your xero dashboard and click on “contacts”; you will see the synced contact.

6. Opportunity (Product) sync as invoice

Afterward, create an opportunity related to the account, add the amount, price, description, currency, product list of product and save it.

Now, I have created a separate class and method to send the opportunity product as an invoice from Salesforce. Create a wrapper class for this to send JSON serialized data.

public class XeroInvoice {
    public XeroInvoice(){
        
    }
    public String Type;	//ACCREC
    public String InvoiceID;	//94b8a8d6-31fd-4d8c-8791-d1a2a7dfe898
    public String InvoiceNumber;	//asdsdasd 
    public String Reference;	//ABC123
    public XeroContact Contact;
    public String Date_x;	
    public String DueDate;	

    public Boolean SentToContact;
    public Decimal AmountDue;	//115
    public Decimal AmountPaid;	//0
    public Decimal AmountCredited;	//0
    public Decimal CurrencyRate;	//1
    public Boolean IsDiscounted;
    public Boolean HasAttachments;
    public Boolean HasErrors;
    
    public String Status;	//AUTHORISED
    public String LineAmountTypes;
    public Decimal SubTotal;	//100
    public Decimal TotalTax;	//15
    public Decimal Total;	//115
    public String UpdatedDateUTC;	
    public String CurrencyCode;	
    public String Url;
    
    public List<LineItem> LineItems;
    public List<Payment> Payments;
    public List<CreditNote> CreditNotes;
    public List<PrePayment> Prepayments;
    public List<OverPayment> Overpayments;

    public List<XeroValidationError> ValidationErrors;

    public class Payment {}

    public class CreditNote {}

    public class PrePayment {}

    public class OverPayment {}

    public class LineItem {

        public String LineItemID;	//006fc261-ee0b-4f4c-bb55-c96ca97cdb53
        public String Description;	//
        public String AccountCode;	//200
        public String TaxType;	//OUTPUT2
        public Decimal UnitAmount;	//100
        public Decimal TaxAmount;	//15
        public Decimal LineAmount;	//100
        public Decimal Quantity;	//1

    }


    public XeroInvoice(String xeroContactId) {

       
        this.Type = 'ACCREC';

      
        this.Contact = new XeroContact(xeroContactId);
    }

    public String serialize() {
        
       
        String serialized = JSON.serialize(this, true);

      
        serialized = serialized.replace('"Date_x"', '"Date"');

        return serialized;
    }
}

public class XeroOpportunityController {
    
    
    public XeroOpportunityController(){
        
    }
    public static Id oppId{get;set;}
    public Opportunity  opp {get;set;}
    public  List<OpportunityLineItem> opplineItem {get;set;}
    public XeroOpportunityController(ApexPages.StandardController controller){
        oppId = controller.getRecord().Id;
        List <OpportunityLineItem> lstOpportunityLineItem = XeroOpportunityHelper.getOpportunityProductLineItem(oppId);
        System.debug('lstOpportunityLineItem is =======================================>'+lstOpportunityLineItem); 
        
        Set<String> customerIdSet = new Set<String>();
        List<XeroInvoice.LineItem> lstInvoiceItem = new List<XeroInvoice.LineItem>();
        for(OpportunityLineItem opplist :lstOpportunityLineItem){
            customerIdSet.add(opplist.Opportunity.Account.Xero_Customer_ID__c);
            
        }
        System.debug('customerIdSet is =======================================>'+customerIdSet);
        
        for(OpportunityLineItem opplist :lstOpportunityLineItem){
            
             XeroInvoice.LineItem lineItem = new XeroInvoice.LineItem();
            lineItem.Description = opplist.Product2.Name;
            lineItem.Quantity = opplist.Quantity;
            lineItem.UnitAmount = opplist.UnitPrice;
           // lineItem.TaxAmount = 10.00;
            lineItem.LineAmount = opplist.TotalPrice;
            
            lstInvoiceItem.add(lineItem);
        }
        //-------------------------------
        System.debug('lstOpportunityLineItem[0].Opportunity.Account.Xero_Customer_ID__c-------======'+lstOpportunityLineItem[0].Opportunity.Account.Xero_Customer_ID__c);
        XeroInvoice newInvoice = new XeroInvoice(lstOpportunityLineItem[0].Opportunity.Account.Xero_Customer_ID__c);
         newInvoice.Date_x = String.valueof(Date.today());
        newInvoice.DueDate = String.valueof(Date.today()+15);
        newInvoice.Reference = 'Website Design';
         newInvoice.LineItems = lstInvoiceItem;
        
        //newInvoice.SubTotal = 100.00;
        //newInvoice.TotalTax = 15.00;
        //newInvoice.Total = 115.00;
        try {
            XeroInvoice resultInvoice = XeroAPICall.sendInvoice(newInvoice);
            System.debug('Invoice successfully sent to Xero. InvoiceID: ' + resultInvoice.InvoiceID);
        } catch (Exception e) {
            System.debug('Error sending invoice to Xero: ' + e.getMessage());
        }
	 //-------------------------------
    }
    
    public Pagereference syncXero(){
        
        
        //return to the same page 
        return new PageReference('/'+oppId);
        
    }
    
}
public class XeroOpportunityHelper {
    
    
    public static List<OpportunityLineItem>  getOpportunityProductLineItem(Id oppId){
        return [select id,
                TotalPrice,
                UnitPrice,
                Name,
                OpportunityId,
                Opportunity.Account.Name,
                Opportunity.Amount,
                Opportunity.Account.Xero_Customer_ID__c ,
                Quantity, 
                ListPrice,
                Product2.Name,
                product2.Id from
                OpportunityLineItem 
                where OpportunityId=: oppId];
    }
    
}
<apex:page standardController="Opportunity" extensions="XeroOpportunityController" action="{!syncXero}">
</apex:page>
<apex:page standardController="Account" extensions="XeroCustomerController" action="{!syncXero}">
</apex:page>

Map all these classes and methods, and also add the button for the Opportunity custom button to sync the opportunity. You can also incorporate additional logic or conditions based on your business requirements.

After completing these steps, click the “Sync Invoice” button. You will find the invoice items inside the Xero contact.

“Thank you for taking the time to read our blog! We appreciate your interest and hope you found valuable insights within these articles. If you have any questions, comments, or suggestions, we’d love to hear from you. Your feedback is invaluable as we strive to provide content that matters to you. Stay tuned for more enriching articles, and once again, thank you for being part of our reading community!”