How to Integrate Salesforce with Xero

0
1384

Hi #Trailblazers,

Welcome to my blog. In this blog post, we will learn how to integrate Salesforce with Xero.

Whenever we talk about integration, the very important thing is how securely connect to the third party system and then make the communication secure.

You can use below link to know all about Xero Authentication. Note: – Xero Uses OAuth 2.0 and OAuth 1.0 for authentication and we will use OAuth 2.0

https://developer.xero.com/documentation/sdks-and-tools/tools/postman/#steps-to-get-up-and-running

Step1 – Create Connected Application in Xero

Navigate to This Link and then click on New App

Go to the Xero developer portal and create an OAuth2 app.

If you haven’t already signed up for a xero account you can do so here.

Use the following values:

  • App Name – your choice, but can’t contain the word ‘Xero’
  • Company or application URL – this needs to be an https address, but isn’t used
  • OAuth 2.0 redirect URI – also needs to be https but won’t be used in salesforce. We will change this URL in later steps

Then:

  1. Click Create App
  2. Click Generate a secret
  3. Keep the page open
Image with a page of inputs where you enter your app details

Step2 – Create Auth Provider in Salesforce

Once you have created the Connected Application in Xero, now the time is to Create the Auth. Provider in Salesforce

  1. Login to Salesforce Org
  2. Navigate to Setup -> Identity -> Auth. Providers -> New
  3. For Provider Type Select Open ID Connect
  4. For Consumer Key, provide the Client Id you have noted down
  5. For Consumer Secret, provide the Client Secret you have noted down
  6. For Authorize Endpoint URL provide use “https://login.xero.com/identity/connect/authorize
  7. For Token Endpoint URL use “https://identity.xero.com/connect/token
  8. For User Info Endpoint URL use “https://identity.xero.com/connect/userinfo
  9. For Token Issuer use “https://identity.xero.com
  10. For Default Scopes use “offline_access openid profile email accounting.transactions accounting.transactions.read accounting.contacts”
  11. Go ahead and Click on Save
  12. Now from the Detail Page Copy “Callback URL”

Step3 – Change the Callback URL in Xero Application

  1. Get back to the Xero Connected Application Detail Page
  2. Click on Configuration Tab from Left
  3. Click on “Add Another URI Button”
  4. Paste the URL that you have copied and then save it

Step4 – Create Named Credentials in Salesforce

Once you have created the Auth. Provider and Updated the callback URL in Xero Connected Application. Now, let’s created the Named Credentials as this will be used to Authenticate Xero with Salesforce

Make sure your named credentials looks like below image.

For Scope use below values

offline_access accounting.settings openid profile email accounting.transactions accounting.transactions.read accounting.contacts

Step5 – Test the Integration

To test the integration and make sure that this is working fine. Run the below code from the developer console

HttpRequest request = new HttpRequest();
request.setMethod('GET');
request.setEndpoint( 'callout:Xero/'+'connections' );

request.setHeader('Accept', 'application/json');
request.setHeader('xero-tenant-id', '');
HttpResponse response = new HttpResponse();
response = new Http().send(request);

System.debug(' \n '+ response.getStatusCode() );
System.debug(' \n '+ response.getBody() );

After executing the code you must see a 200 as status code. See the image below

Below is the complete code for your reference

public class XeroAccount {
public String Id;
public String Status;
public String ProviderName;
public String DateTimeUTC;
public Accounts[] Accounts;
public class Accounts {
public String AccountID;
public String Code;
public String Name;
public String Status;
public String TypeX;
public String TaxType;
public String ClassX;
public boolean EnablePaymentsToAccount;
public boolean ShowInExpenseClaims;
public String BankAccountNumber;
public String BankAccountType;
public String CurrencyCode;
public String ReportingCode;
public String ReportingCodeName;
public boolean HasAttachments;
public String UpdatedDateUTC;
public boolean AddToWatchlist;
}
}
public class XeroAPICalloutService {
public static HttpResponse executeCallout (String method, String resourceName) {
return executeCallout(method, resourceName, null, null);
}
public static HttpResponse executeCallout (String method, String resourceName, String requestBody) {
return executeCallout(method, resourceName, requestBody, null);
}
public static HttpResponse executeCallout (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', XeroUtilApi.getXeroTenantId());
}
request.setTimeout(120000);
if (String.isNotBlank(requestBody)) {
request.setBody(requestBody);
request.setHeader('Content-Type', 'application/json');
}
if (headers != null && !headers.isEmpty()) {
for (String headerKey :headers.keySet()) {
request.setHeader(headerKey, headers.get(headerKey));
}
}
HttpResponse response = new HttpResponse();
try{
response = new Http().send(request);
}catch(System.Exception ex){
if(String.valueOf(ex.getMessage()).startsWith('Unauthorized endpoint')){
errorMessage = 'Unauthorize endpoint: An Administer must go to Setup -> Administer -> Security Control ->'
+' Remote Site Setting and add '+' '+ request.getEndpoint() +' Endpoint';
}else{
errorMessage = 'Unexpected Error while communicating with API. '
+'Status '+response.getStatus()+' and Status Code '+response.getStatuscode()+
response.getBody();
}
}
return response;
}
}
public class XeroContact {
public String Id;
public String Status;
public String ProviderName;
public String DateTimeUTC;
public Contacts[] Contacts;
public class Contacts {
public String ContactID;
public String ContactNumber;
public String ContactStatus;
public String Name;
public String FirstName;
public String LastName;
public String EmailAddress;
public Addresses[] Addresses;
public Phones[] Phones;
public String UpdatedDateUTC;
public ContactGroups[] ContactGroups;
public boolean IsSupplier;
public boolean IsCustomer;
public Balances Balances;
public ContactPersons[] ContactPersons;
public boolean HasAttachments;
public boolean HasValidationErrors;
}
public class Addresses {
public String AddressType;
}
public class Phones {
public String PhoneType;
}
public class ContactGroups {
}
public class Balances {
public AccountsReceivable AccountsReceivable;
public AccountsPayable AccountsPayable;
}
public class AccountsReceivable {
public Integer Outstanding;
public Integer Overdue;
}
public class AccountsPayable {
public Integer Outstanding;
public Integer Overdue;
}
public class ContactPersons {
}
}
public class XeroInvoice {
public String Id;
public String Status;
public String ProviderName;
public String DateTimeUTC;
public Invoices[] Invoices;
public class Invoices {
public String TypeX;
public String InvoiceID;
public String InvoiceNumber;
public String Reference;
public Attachments[] Attachments;
public Payments[] Payments;
public CreditNotes[] CreditNotes;
public Prepayments[] Prepayments;
public Overpayments[] Overpayments;
public Integer AmountDue;
public Integer AmountPaid;
public Integer AmountCredited;
public Integer CurrencyRate;
public boolean IsDiscounted;
public boolean HasAttachments;
public boolean HasErrors;
public XeroContact Contact;
public String DateString;
public String DateX;
public String DueDateString;
public String DueDate;
public String BrandingThemeID;
public String Status;
public String LineAmountTypes;
public LineItems[] LineItems;
public Double SubTotal;
public Double TotalTax;
public Integer Total;
public String UpdatedDateUTC;
public String CurrencyCode;
}
public class Payments {
public String PaymentID;
public String DateX;
public Decimal Amount;
public Decimal CurrencyRate;
public boolean HasAccount;
public boolean HasValidationErrors;
}
public class CreditNotes {
}
public class Prepayments {
}
public class Overpayments {
}
public class Attachments{
public String FileName;
public String Url;
public String MimeType;
public Integer ContentLength;
public boolean IncludeOnline;
}
public class LineItems {
public String Description;
public Decimal UnitAmount;
public String TaxType;
public Decimal TaxAmount;
public Decimal LineAmount;
public String AccountCode;
public Decimal Quantity;
public String LineItemID;
}
}
global with sharing class XeroUtilApi {
global static void getXeroInvoces(){
HttpResponse response = XeroAPICalloutService.executeCallout('GET', 'Invoices');
String responseBody = response.getBody();
responseBody = response.getBody().replaceAll('Type','TypeX').replaceAll('Date','DateX');
XeroInvoice invoice = (XeroInvoice)System.JSON.deserialize( responseBody, XeroInvoice.class);
System.debug(' invoice \n '+ JSON.serialize( invoice ) );
}
global static void getXeroAccounts(){
HttpResponse response = XeroAPICalloutService.executeCallout('GET', 'Accounts');
String responseBody = response.getBody();
responseBody = response.getBody().replaceAll('Type','TypeX').replaceAll('Class','ClassX');
XeroAccount account = (XeroAccount)System.JSON.deserialize( responseBody, XeroAccount.class);
System.debug(' accounts \n '+ JSON.serialize(account) );
}
global static void getXeroContacts(){
HttpResponse response = XeroAPICalloutService.executeCallout('GET', 'Contacts');
String responseBody = response.getBody();
XeroContact contact = (XeroContact)System.JSON.deserialize( responseBody, XeroContact.class);
System.debug(' contact '+ response.getBody());
}
global static String getXeroTenantId () {
HttpResponse response = XeroAPICalloutService.executeCallout('GET', 'connections');
if (response.getStatusCode() < 300) {
List<XeroConnection> xeroConnections = (List<XeroConnection>) JSON.deserialize(response.getBody(), List<XeroConnection>.class);
return xeroConnections[0].tenantId;
}
else {
throw new XeroApiException(
'Error retrieving connections: ' + response.getStatusCode() + ' - ' + response.getStatus() + '\n\n' +
response.getBody()
);
}
}
}

Note: – XeroUtilApi class is the main Class which is using XeroAPICalloutService class to make the callouts to Xero Org.

Here are some examples that are already implemented

  1. XeroUtilApi.getXeroInvoces(); – To fetch All the Invoices
  2. XeroUtilApi.getXeroAccounts(); – To fetch All the Accounts
  3. XeroUtilApi.getXeroContacts(); – To fetch All the Contacts

Thanks for reading 🙂 Happy Learning

LEAVE A REPLY

Please enter your comment!
Please enter your name here