Series (1/4)
A Comprehensive Guide to Automating Statistics and Business Processes through QuickBooks-Salesforce Integration”
Unlock the potential of seamless automation for your business statistics and processes with the strategic
integration of QuickBooks and Salesforce. In this blog, we offer invaluable insights and guidance
to empower you in automating crucial aspects of your operations, ensuring efficiency and accuracy
through the synergy of these powerful platforms.
Essential Steps to Follow for Seamless Integration
Getting Started: Signing Up for QuickBooks and Salesforce Accounts
The first step to begin your journey with QuickBooks and Salesforce is to sign up for an account on both platforms. Follow these easy steps to get started:
- QuickBooks Account Signup: Visit the QuickBooks website and look for the “Sign Up” or “Create Account” option. Fill in the required information, such as your business details and contact information. Once completed, you’ll have your own QuickBooks account ready to use.
- Salesforce Account Signup: Head over to the Salesforce website and locate the “Sign Up” or “click this link https://developer.salesforce.com/signup” . Provide the necessary details, including your name, email, and organization information. After completing the signup process, you’ll have access to your new Salesforce account.
Creating Your App in QuickBooks: A Step-by-Step Guide
- Log In to QuickBooks: Start by logging in to your QuickBooks account. Enter your credentials to access the dashboard.
- Navigate to the Dashboard: Once logged in, locate and click on the “Dashboard” option. This is usually the central hub where you can access various tools and features.
- Access the App Creation Section: Look for the “Apps” or “App Management” section in the dashboard. Depending on your QuickBooks version, this may be found in the main menu or a designated area within the dashboard.
- Initiate App Creation: Within the “Apps” section, you should find an option to create a new app. Click on this option to begin the app creation process.
- Fill in App Details: Provide necessary details about your app, such as its name, description, and purpose. This information helps QuickBooks understand the function and scope of your app.
- refer to this Link https://developer.intuit.com/app/developer/qbo/docs/get-started you will understand everything.
- After creating your QuickBooks app, find the Client ID and Client Secret, crucial for secure integration. Retrieve additional credentials like Company ID and Callback URL, and follow the authentication process to establish a secure link between your app and QuickBooks.
Creating Your Site and Remote Site Settings in Salesforce :
(1). Create your site and add inside for the webhooks
(2). Create Remote site settings and add information
Url :https://oauth.platform.intuit.com
Active -true
Create an apex class
- The webhook class is designed to capture and store Item information of Quickbooks within the Salesforce platform during creation.
- Its purpose is to handle webhook events, ensuring the seamless storage of details related to the products in the system
/**
* Salesforce REST Resource class for handling QuickBooks webhooks to create or update products.
*/
@RestResource(urlMapping='/api/Webhooks/pushDemo/*')
global class CreateWebHookQuickBooks {
/**
* REST method to handle the creation or update of products in Salesforce triggered by QuickBooks webhooks.
*/
@HttpPost
global static void createProductFromQB() {
RestRequest request = RestContext.request;
RestResponse response = RestContext.response;
// Deserialize the incoming webhook payload
RespHookClass resp = (RespHookClass) system.JSON.deserialize(RestContext.request.requestBody.toString(), RespHookClass.class);
try {
/*
* Create or update a product in Salesforce from Quickbooks products (item) if created or updated.
*/
if(!resp.eventNotifications.isEmpty() && resp.eventNotifications.size()>0){
if(resp.eventNotifications[0].dataChangeEvent.entities[0].Name == 'Item'){
AUthCallout call = new AUthCallout ();
String responseStr = call.getItemById('' + resp.eventNotifications[0].dataChangeEvent.entities[0].Id);
ItemWrapper wrapper = (ItemWrapper) system.JSON.deserialize(responseStr, ItemWrapper.class);
// Prepare Product2 object for insertion or update
Product2 ProductToInsert = new Product2(
Name = wrapper.Item.Name,
Description = wrapper.Item.Description,
Rate__c = wrapper.Item.UnitPrice,
IsActive = true,
QB_Id__c = wrapper.Item.Id,
QBO_SyncToken__c = wrapper.Item.SyncToken
);
// Upsert the product based on operation type (Create or Update)
if (resp.eventNotifications[0].dataChangeEvent.entities[0].operation == 'Create' || resp.eventNotifications[0].dataChangeEvent.entities[0].operation == 'Update') {
List<Product2> existingProduct = [SELECT Id FROM Product2 WHERE QB_Id__c = :wrapper.Item.Id LIMIT 1];
ProductToInsert.Id = (!existingProduct.isEmpty() && existingProduct[0] != null) ? existingProduct[0].id : null;
if(ProductToInsert != null){
upsert ProductToInsert;
}
}
// Create Pricebook entries for the product
if(resp.eventNotifications[0].dataChangeEvent.entities[0].operation == 'Create'){
PriceBook2 pb2Standard = [select Id from Pricebook2 where isStandard=true];
PricebookEntry objPBEntry = new PricebookEntry(
Pricebook2Id = pb2Standard.Id,
Product2Id = ProductToInsert.Id,
UnitPrice = wrapper.Item.UnitPrice,
IsActive = true
);
insert objPBEntry;
PriceBook2 pb2Custom = [select Id from Pricebook2 where isStandard=false];
PricebookEntry pbe = new PricebookEntry(
Pricebook2Id = pb2Custom.Id,
Product2Id = ProductToInsert.Id,
UnitPrice = wrapper.Item.UnitPrice,
IsActive = true
);
insert pbe;
}
// Log integration success
Integration_Log__c inglog = new Integration_Log__c();
inglog.Name = resp.eventNotifications[0].dataChangeEvent.entities[0].Name;
inglog.Event_Date_And_Time__c = System.Now();
inglog.Request_Body__c = JSON.serializePretty(resp);
inglog.Event_Status__c = 'Success';
insert inglog;
}
}
} catch(Exception e) {
// Log integration error
System.debug('error occurred:'+e.getMessage()+' '+e.getLineNumber());
Integration_Log__c inglog = new Integration_Log__c();
inglog.Name = resp.eventNotifications[0].dataChangeEvent.entities[0].Name;
inglog.Event_Date_And_Time__c = System.Now();
inglog.Request_Body__c = JSON.serializePretty(resp);
inglog.Error_Message__c = e.getMessage() + ' ' + e.getLineNumber() + ' ' + e.getCause() + ' ' + e.getTypeName();
inglog.Event_Status__c = 'Error';
insert inglog;
}
}
}
public class AUthCallout {
private String accessToken{get;set;}
private String refreshToken{get;set;}
public List<String> refresh(){
QBData__c QBData = [SELECT ID,
Name,
refresh_token__c,
client_id__c,
client_secret__c,
auth_url__c
FROM QBData__c];
String url = QBData.auth_url__c;
String clientId = QBData.client_id__c;
String clientSecret = QBData.client_secret__c;
String header = 'Basic ' + EncodingUtil.base64Encode(Blob.valueOf(clientId + ':' + clientSecret));
String refresh_token = QBData.refresh_token__c;
String body = 'grant_type=refresh_token&refresh_token=' + refresh_token;
Http h = new Http();
HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
req.setEndpoint(url);
req.setMethod('POST');
req.setBody(body);
req.setHeader('Authorization', header);
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
res = h.send(req);
System.debug('res.getBody()==>'+res.getBody());
QBRefreshJSON json = QBRefreshJSON.parse(res.getBody());
System.debug('QBRefreshJSON==>'+json);
List<String> tokens = new List<String>{json.access_token,json.refresh_token};
accessToken = tokens[0];
refreshToken = tokens[1];
System.debug('accesToken==>'+accessToken);
System.debug('refreshToken==>'+refreshToken);
return tokens;
}
public String getItemById(String itemId){
//Initialze url endpoint
QBO_Metadata__mdt QBData = [SELECT ID,
base_url__c,
Company_Id__c,
minorversion__c
FROM QBO_Metadata__mdt];
String url = 'https://sandbox-quickbooks.api.intuit.com/v3/company/4620816365312541260/item/'+itemId+'?minorversion=65';
//Start http request
Http h = new Http();
HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
System.debug(accessToken);
req.setEndpoint(url);
req.setMethod('GET');
req.setHeader('Authorization', 'Bearer ' + accessToken);
req.setheader('Accept', 'application/json');
req.setHeader('Content-Type', 'application/text');
res = h.send(req);
String str = res.getBody();
return str;
}
public AUthCallout(){
List<String> tokens = refresh();
}
}
public class RespHookClass {
public List<EventNotification> eventNotifications;
public class EventNotification {
public String realmId;
public DataChangeEvent dataChangeEvent;
}
public class DataChangeEvent {
public List<Entity> entities;
}
public class Entity {
public String name;
public String id;
public String operation;
public String lastUpdated;
}
}
// Generated by JSON2Apex http://json2apex.herokuapp.com/
public class ItemWrapper {
public Item Item {get;set;}
public String time_Z {get;set;} // in json: time
public ItemWrapper(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'Item') {
Item = new Item(parser);
} else if (text == 'time') {
time_Z = parser.getText();
} else {
System.debug(LoggingLevel.WARN, 'ItemWrapper consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
public class Item {
public String Name {get;set;}
public String Description {get;set;}
public Boolean Active {get;set;}
public String FullyQualifiedName {get;set;}
public Boolean Taxable {get;set;}
public Integer UnitPrice {get;set;}
public String Type {get;set;}
public IncomeAccountRef IncomeAccountRef {get;set;}
public String PurchaseDesc {get;set;}
public Integer PurchaseCost {get;set;}
public IncomeAccountRef ExpenseAccountRef {get;set;}
public IncomeAccountRef AssetAccountRef {get;set;}
public Boolean TrackQtyOnHand {get;set;}
public Integer QtyOnHand {get;set;}
public String InvStartDate {get;set;}
public IncomeAccountRef TaxClassificationRef {get;set;}
public String domain {get;set;}
public Boolean sparse {get;set;}
public String Id {get;set;}
public String SyncToken {get;set;}
public MetaData MetaData {get;set;}
public Item(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'Name') {
Name = parser.getText();
} else if (text == 'Description') {
Description = parser.getText();
} else if (text == 'Active') {
Active = parser.getBooleanValue();
} else if (text == 'FullyQualifiedName') {
FullyQualifiedName = parser.getText();
} else if (text == 'Taxable') {
Taxable = parser.getBooleanValue();
} else if (text == 'UnitPrice') {
UnitPrice = parser.getIntegerValue();
} else if (text == 'Type') {
Type = parser.getText();
} else if (text == 'IncomeAccountRef') {
IncomeAccountRef = new IncomeAccountRef(parser);
} else if (text == 'PurchaseDesc') {
PurchaseDesc = parser.getText();
} else if (text == 'PurchaseCost') {
PurchaseCost = parser.getIntegerValue();
} else if (text == 'ExpenseAccountRef') {
ExpenseAccountRef = new IncomeAccountRef(parser);
} else if (text == 'AssetAccountRef') {
AssetAccountRef = new IncomeAccountRef(parser);
} else if (text == 'TrackQtyOnHand') {
TrackQtyOnHand = parser.getBooleanValue();
} else if (text == 'QtyOnHand') {
QtyOnHand = parser.getIntegerValue();
} else if (text == 'InvStartDate') {
InvStartDate = parser.getText();
} else if (text == 'TaxClassificationRef') {
TaxClassificationRef = new IncomeAccountRef(parser);
} else if (text == 'domain') {
domain = parser.getText();
} else if (text == 'sparse') {
sparse = parser.getBooleanValue();
} else if (text == 'Id') {
Id = parser.getText();
} else if (text == 'SyncToken') {
SyncToken = parser.getText();
} else if (text == 'MetaData') {
MetaData = new MetaData(parser);
} else {
System.debug(LoggingLevel.WARN, 'Item consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class MetaData {
public String CreateTime {get;set;}
public String LastUpdatedTime {get;set;}
public MetaData(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'CreateTime') {
CreateTime = parser.getText();
} else if (text == 'LastUpdatedTime') {
LastUpdatedTime = parser.getText();
} else {
System.debug(LoggingLevel.WARN, 'MetaData consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class IncomeAccountRef {
public String value {get;set;}
public String name {get;set;}
public IncomeAccountRef(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'value') {
value = parser.getText();
} else if (text == 'name') {
name = parser.getText();
} else {
System.debug(LoggingLevel.WARN, 'IncomeAccountRef consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public static ItemWrapper parse(String json) {
System.JSONParser parser = System.JSON.createParser(json);
return new ItemWrapper(parser);
}
public static void consumeObject(System.JSONParser parser) {
Integer depth = 0;
do {
System.JSONToken curr = parser.getCurrentToken();
if (curr == System.JSONToken.START_OBJECT ||
curr == System.JSONToken.START_ARRAY) {
depth++;
} else if (curr == System.JSONToken.END_OBJECT ||
curr == System.JSONToken.END_ARRAY) {
depth--;
}
} while (depth > 0 && parser.nextToken() != null);
}
}
“Simply map all these classes and fields, and store the information such as company ID, secret, client ID, refresh token, and access token inside Custom Settings and Custom Metadata. With this approach, your integration setup is complete.
“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!”