Seamless Salesforce Integration: Creating Leads with Attachments using Python and Flask

Integration with Salesforce using Python is a powerful capability, 

particularly when leveraging the simplicity of the Simple Salesforce API. 

In this use case, the objective is to create leads with attachments using Python and Flask, making use of Salesforce’s REST API functionality.

For the purpose of writing a blog, this line succinctly captures the essence of the task at hand: ‘We can leverage the Simple Salesforce API to seamlessly integrate and create leads with attachments, utilizing the robust functionality of Salesforce’s REST API in Python with Flask.

Step (1):  Create a connected app in salesforce

Create a connected app for authentication and authorization, enabling the use of a password with a security token. To obtain the client ID and client secret, simply create a connected app in Salesforce. It’s a straightforward process—just create and allow REST API access; that’s it.

Step (2): Now, install Python on your local machine and use the PyCharm Community Edition editor for running Python locally. 

(i) Import Salesforce SimpleAPI.

(ii) Also, import Flask for the frontend web application.

(iii) Import all the libraries by simply clicking the code and importing; this allows direct importing from the editor.

(iv) Change the username, password, client ID, and client secret on your own. For best practices, you can separate these configurations somewhere else. This is a demo org, which is why I am providing this for understanding purposes.

(v) Create a “templates” folder; the name should be exactly “templates” only. Then, add an HTML file inside and link it with the main Python file, which I am currently using for LeadCreateWithAttachments.py

LeadCreateWithAttachments.py

import json
import os
from flask import Flask, render_template, request, redirect, url_for, jsonify
import base64
import requests

#make sure to import all the packages first

params = {
    "grant_type": "password",
    "client_id": "Your clientId",
    "client_secret": "Your clientId",
    "username": "yourUserName",
    "password": "password + securityToken" #without any space 
}
r = requests.post("https://login.salesforce.com/services/oauth2/token", params=params)
access_token = r.json().get("access_token")
instance_url = r.json().get("instance_url")
print("Access Token:", access_token)
print("Instance URL", instance_url)




def sf_api_call(action, parameters={}, method='get', data={}):
   
    headers = {
        'Content-type': 'application/json',
        'Accept-Encoding': 'gzip',
        'Authorization': 'Bearer %s' % access_token
    }
    if method == 'get':
        r = requests.request(method, instance_url + action, headers=headers, params=parameters, timeout=30)
    elif method in ['post', 'patch']:
        r = requests.request(method, instance_url + action, headers=headers, json=data, params=parameters, timeout=10)
    else:
        raise ValueError('Method should be get or post or patch.')
    print('Debug: API %s call: %s' % (method, r.url))
    if r.status_code < 300:
        if method == 'patch':
            return None
        else:
            return r.json()
    else:
        raise Exception('API error when calling %s : %s' % (r.url, r.content))




app = Flask(__name__)




@app.route('/')
def index():
    return render_template('LeadCreateWithAttachment.html')




@app.route('/submit', methods=['POST'])
def submit():
    # Get form data from the request
    first_name = request.form['firstName']
    last_name = request.form['lastName']
    company = request.form['company']
    


    # Salesforce REST API endpoint for creating a Lead
    api_endpoint = f"{instance_url}/services/data/v60.0/sobjects/Lead/"




    # Prepare data for the Lead object
    lead_data = {
        'FirstName': first_name,
        'LastName': last_name,
        'Company': company


    }
    # Prepare headers with authorization token
    headers = {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type': 'application/json'
    }
    response = requests.post(api_endpoint, headers=headers, json=lead_data)


    print("Lead Creation Response:", response.json())  # Add this line for debugging


    if response.status_code == 201:
        # return upload_file(lead_id)
        lead_id = response.json().get('id')
        upload_file(lead_id);
        return 'Lead created successfully with attachments'
    else:
        # Error creating Lead
        return jsonify({'error': 'Failed to create Lead'})




@app.route('/upload', methods=['POST'])
def upload_file(lead_id):
    file = request.files['file']
    if file:
        encoded_string = base64.b64encode(file.read()).decode("utf-8")
        content_version = sf_api_call('/services/data/v60.0/sobjects/ContentVersion', method="post", data={
            'Title': file.filename,
            'PathOnClient': file.filename,
            'VersionData': encoded_string,
        })
        content_version_id = content_version.get('id')
        content_version = sf_api_call(f'/services/data/v60.0/sobjects/ContentVersion/{content_version_id}')
        content_document_id = content_version.get('ContentDocumentId')


        # Create a ContentDocumentLink
        content_document_link = sf_api_call('/services/data/v60.0/sobjects/ContentDocumentLink', method='post', data={
            'ContentDocumentId': content_document_id,
            'LinkedEntityId': lead_id,
            'ShareType': 'V'
        })


        return f'Successfully uploaded {file.filename} and linked to record with Id {lead_id}!'
    else:
        return 'No file uploaded.'




if __name__ == '__main__':
    app.run(debug=True)

LeadCreateWithAttachment.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Salesforce Lead Form</title>
    <style>
        body {
            text-align: center;
            background-color: #f4f4f4; /* Light gray background */
        }
        form {
            display: inline-block;
            text-align: left;
            background-color: #fff; /* White background */
            padding: 20px;
            border: 1px solid #ccc; /* Light gray border */
            border-radius: 8px; /* Rounded corners */
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* Box shadow for a subtle effect */
        }
        table {
            width: 100%;
        }
        table, th, td {
            border-collapse: collapse;
        }
        th, td {
            padding: 10px;
            border: 1px solid #ddd; /* Light gray border for table cells */
        }
        th {
            background-color: #f2f2f2; /* Light gray background for table header */
        }
        button {
            background-color: #4caf50; /* Green button color */
            color: #fff; /* White text color */
            padding: 10px 15px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
    </style>
</head>

<body>

<center>
    <h2>Create Lead</h2>

    <form method="post" action="{{ url_for('submit') }}" enctype="multipart/form-data">
        <table>
            <tr>
                <td><label for="firstName">First Name:</label></td>
                <td><input type="text" id="firstName" name="firstName" required></td>
            </tr>
            <tr>
                <td><label for="lastName">Last Name:</label></td>
                <td><input type="text" id="lastName" name="lastName" required></td>
            </tr>
            <tr>
                <td><label for="company">Company:</label></td>
                <td><input type="text" id="company" name="company" required></td>
            </tr>

            <tr>
                <td><label for="file">Select a PDF file:</label></td>
                <td><input type="file" id="file" name="file" accept=".pdf" required></td>
            </tr>
            <tr>
                <td colspan="2" style="text-align: center;"><button type="submit">Submit</button></td>
            </tr>
        </table>
    </form>
</center>

</body>
</html>

Sync invoice with Quickbooks invoice (Part 1)

“Here, we have created a custom object ‘Invoice’ for the simple mapping of opportunity product lists for product creation purposes to send invoices for related customers.

The approach we follow is to first query the customer and add them to the invoice. Then, we send a POST request for the invoice related to that particular customer.

Here, we have a sample JSON file for the official documentation, which they provided. You can also visit and refer to this link to check them out:

{
  "Line": [
    {
      "DetailType": "SalesItemLineDetail", 
      "Amount": 100.0, 
      "SalesItemLineDetail": {
        "ItemRef": {
          "name": "Services", 
          "value": "1"
        }
      }
    }
  ], 
  "CustomerRef": {
    "value": "1"
  }
}

Create a wrapper class for the request and response handling of the invoice object. You can refer to this website for the JSON data to convert the wrapper.

public class InvoiceJSON {

 public class Line {
 public Double Amount;
 public String DetailType;
 public Double Qty;
 public String Description;
 public SalesItemLineDetail SalesItemLineDetail;
 }

 public class CustomField {
 public String DefinitionId;
 public String StringValue;
 public String Type;
 public String Name;
 }

 public class BillAddr {
 public String Id;
 public String Line1;
 public String Line2;
 public String Line3;
 public String Line4;
 public String Line5;
 public String City;
 public String CountrySubDivisionCode;
 public String PostalCode;
 public String Lat;
 public String Long_x;
 public String Country;
 }

    public class SalesTermRef {
        public String value;
    }

 public List<Line> Line;
 public List<CustomField> CustomField;
 public CustomerRef CustomerRef;
 public Date DueDate;
 public Double Balance;
 public String Id;
 public String SyncToken;
 public SalesTermRef SalesTermRef;
 public BillAddr BillAddr;
 public BillAddr ShipAddr;
 public EmailAddress BillEmail;
 public MemoRef CustomerMemo;
 public String EmailStatus;
 public String DocNumber;
 public Boolean AutoDocNumber;
 public Boolean AllowOnlineACHPayment;
 public Boolean AllowOnlineCreditCardPayment;
 public TxnTaxDetail TxnTaxDetail;

 public class EmailAddress{
 public string Address;
 }

 public class TxnTaxDetail{
 public TxnTaxCodeRef TxnTaxCodeRef;
 public Decimal TotalTax;
 }

 public class TxnTaxCodeRef{
 public String value;
 }

 public class MemoRef{
 public string value;

 }


 public class SalesItemLineDetail {
 public ItemRef ItemRef;
 public Decimal Qty;
 public Decimal UnitPrice;
 public ItemRef TaxCodeRef;
 }

 public class ItemRef {
 public String value;
 public String name;
 }

 public class CustomerRef {
 public String value;
 }

 
 public static InvoiceJSON parse(String json) {
 return (InvoiceJSON ) System.JSON.deserialize(json, InvoiceJSON .class);
 }
}
public class InvoiceQueryJSON {

	public class CurrencyRef {
		public String value {get;set;} 
		public String name {get;set;} 

		public CurrencyRef(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, 'CurrencyRef consuming unrecognized property: '+text);
							consumeObject(parser);
						}
					}
				}
			}
		}
	}
	
	public class Invoice {
		public Integer Deposit {get;set;} 
		public Boolean AllowIPNPayment {get;set;} 
		public Boolean AllowOnlinePayment {get;set;} 
		public Boolean AllowOnlineCreditCardPayment {get;set;} 
		public Boolean AllowOnlineACHPayment {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 List<CustomField> CustomField {get;set;} 
		public String DocNumber {get;set;} 
		public String TxnDate {get;set;} 
		public CurrencyRef CurrencyRef {get;set;} 
		public List<LinkedTxn> LinkedTxn {get;set;} 
		public List<Line> Line {get;set;} 
		public CurrencyRef CustomerRef {get;set;} 
		public TaxCodeRef SalesTermRef {get;set;} 
		public String DueDate {get;set;} 
		public Double TotalAmt {get;set;} 
		public Boolean ApplyTaxAfterDiscount {get;set;} 
		public String PrintStatus {get;set;} 
		public String EmailStatus {get;set;} 
		public Double Balance {get;set;} 
		public String PrivateNote {get;set;} 
		public TaxCodeRef CustomerMemo {get;set;} 
		public BillEmail BillEmail {get;set;}
		public BillEmail BillEmailCc {get;set;} 
		public BillEmail BillEmailBcc {get;set;} 
		public TxnTaxDetail TxnTaxDetail {get;set;}
		public DeliveryInfo DeliveryInfo {get;set;}
		
		public Invoice(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 == 'Deposit') {
							Deposit = parser.getIntegerValue();
						} else if (text == 'AllowIPNPayment') {
							AllowIPNPayment = parser.getBooleanValue();
						} else if (text == 'AllowOnlinePayment') {
							AllowOnlinePayment = parser.getBooleanValue();
						} else if (text == 'AllowOnlineCreditCardPayment') {
							AllowOnlineCreditCardPayment = parser.getBooleanValue();
						} else if (text == 'AllowOnlineACHPayment') {
							AllowOnlineACHPayment = parser.getBooleanValue();
						} 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 if (text == 'CustomField') {
							CustomField = arrayOfCustomField(parser);
						} else if (text == 'DocNumber') {
							DocNumber = parser.getText();
						} else if (text == 'TxnDate') {
							TxnDate = parser.getText();
						} else if (text == 'PrivateNote') {
							PrivateNote = parser.getText();
						} else if (text == 'CurrencyRef') {
							CurrencyRef = new CurrencyRef(parser);
						} else if (text == 'DeliveryInfo') {
							DeliveryInfo = new DeliveryInfo(parser);
						} else if (text == 'LinkedTxn') {
							LinkedTxn = arrayOfLinkedTxn(parser);
						} else if (text == 'Line') {
							Line = arrayOfLine(parser);
						} else if (text == 'CustomerRef') {
							CustomerRef = new CurrencyRef(parser);
						} else if (text == 'SalesTermRef') {
							SalesTermRef = new TaxCodeRef(parser);
						} else if (text == 'BillEmail') {
							BillEmail = new BillEmail(parser);
						} else if (text == 'BillEmailBcc') {
							BillEmailBcc = new BillEmail(parser);
						} else if (text == 'BillEmailCc') {
							BillEmailCc = new BillEmail(parser);
						} else if (text == 'TxnTaxDetail') {
							TxnTaxDetail = new TxnTaxDetail(parser);
						} else if (text == 'DueDate') {
							DueDate = parser.getText();
						} else if (text == 'TotalAmt') {
							TotalAmt = parser.getDoubleValue();
						} else if (text == 'ApplyTaxAfterDiscount') {
							ApplyTaxAfterDiscount = parser.getBooleanValue();
						} else if (text == 'PrintStatus') {
							PrintStatus = parser.getText();
						} else if (text == 'EmailStatus') {
							EmailStatus = parser.getText();
						} else if (text == 'Balance') {
							Balance = parser.getDoubleValue();
						} else if (text == 'CustomerMemo') {
							CustomerMemo = new TaxCodeRef(parser);
						} else {
							System.debug(LoggingLevel.WARN, 'Invoice consuming unrecognized property: '+text);
							consumeObject(parser);
						}
					}
				}
			}
		}
	}
	
	public class Line_Z {
		public String Id {get;set;} 
		public Integer LineNum {get;set;} 
		public Double Amount {get;set;} 
		public String DetailType {get;set;} 
		public SalesItemLineDetail SalesItemLineDetail {get;set;} 
		public LinkedTxn SubTotalLineDetail {get;set;} 

		public Line_Z(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 == 'Id') {
							Id = parser.getText();
						} else if (text == 'LineNum') {
							LineNum = parser.getIntegerValue();
						} else if (text == 'Amount') {
							Amount = parser.getDoubleValue();
						} else if (text == 'DetailType') {
							DetailType = parser.getText();
						} else if (text == 'SalesItemLineDetail') {
							SalesItemLineDetail = new SalesItemLineDetail(parser);
						} else if (text == 'SubTotalLineDetail') {
							SubTotalLineDetail = new LinkedTxn(parser);
						} else {
							System.debug(LoggingLevel.WARN, 'Line_Z consuming unrecognized property: '+text);
							consumeObject(parser);
						}
					}
				}
			}
		}
	}
	
	public class DeliveryInfo {
		public String DeliveryType {get;set;}
		public String DeliveryTime {get;set;}

		public DeliveryInfo(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 == 'DeliveryType') {
							DeliveryType = parser.getText();
						} else if (text == 'DeliveryTime') {
							DeliveryTime = parser.getText();
						} else {
							System.debug(LoggingLevel.WARN, 'InvoiceQueryJSON  consuming unrecognized property: '+text);
							consumeObject(parser);
						}
					}
				}
			}
		}

	}

	public class Line_Y {
		public String Id {get;set;} 
		public Integer LineNum {get;set;} 
		public Double Amount {get;set;} 
		public String DetailType {get;set;} 
		public SalesItemLineDetail_Z SalesItemLineDetail {get;set;} 
		public LinkedTxn SubTotalLineDetail {get;set;} 

		public Line_Y(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 == 'Id') {
							Id = parser.getText();
						} else if (text == 'LineNum') {
							LineNum = parser.getIntegerValue();
						} else if (text == 'Amount') {
							Amount = parser.getDoubleValue();
						} else if (text == 'DetailType') {
							DetailType = parser.getText();
						} else if (text == 'SalesItemLineDetail') {
							SalesItemLineDetail = new SalesItemLineDetail_Z(parser);
						} else if (text == 'SubTotalLineDetail') {
							SubTotalLineDetail = new LinkedTxn(parser);
						} else {
							System.debug(LoggingLevel.WARN, 'Line_Y consuming unrecognized property: '+text);
							consumeObject(parser);
						}
					}
				}
			}
		}
	}
	
	public class SalesItemLineDetail {
		public CurrencyRef ItemRef {get;set;} 
		public CurrencyRef ItemAccountRef {get;set;} 
		public TaxCodeRef TaxCodeRef {get;set;} 
		public Decimal UnitPrice {get;set;}
		public Decimal Qty {get;set;}
		public String ServiceDate {get;set;}
		
		public SalesItemLineDetail(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 == 'ItemRef') {
							ItemRef = new CurrencyRef(parser);
						} else if (text == 'ItemAccountRef') {
							ItemAccountRef = new CurrencyRef(parser);
						} else if (text == 'UnitPrice') {
							UnitPrice = parser.getDecimalValue();
						} else if (text == 'Qty') {
							Qty = parser.getDecimalValue();
						} else if (text == 'ServiceDate') {
							ServiceDate = parser.getText();
						} else if (text == 'TaxCodeRef') {
							TaxCodeRef = new TaxCodeRef(parser);
						} else {
							System.debug(LoggingLevel.WARN, 'SalesItemLineDetail consuming unrecognized property: '+text);
							consumeObject(parser);
						}
					}
				}
			}
		}
	}
	public class TxnTaxDetail {
		public Double TotalTax {get;set;}

		public TxnTaxDetail(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 == 'TotalTax') {
							TotalTax = parser.getDoubleValue();
						} else {
							System.debug(LoggingLevel.WARN, 'InvoiceQueryJSON consuming unrecognized property: '+text);
							consumeObject(parser);
						}
					}
				}
			}
		}

	}
	public class Line {
		public String Id {get;set;} 
		public Integer LineNum {get;set;} 
		public Double Amount {get;set;} 
		public String DetailType {get;set;} 
		public SalesItemLineDetail SalesItemLineDetail {get;set;}
		public GroupLineDetail GroupLineDetail {get;set;} 
		public LinkedTxn SubTotalLineDetail {get;set;} 
		public String Description {get;set;}
		// 17549
		public Line(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 == 'Id') {
							Id = parser.getText();
						} else if (text == 'LineNum') {
							LineNum = parser.getIntegerValue();
						} else if (text == 'Amount') {
							Amount = parser.getDoubleValue();
						} else if (text == 'DetailType') {
							DetailType = parser.getText();
						} else if (text == 'Description') {
							Description = parser.getText();
						} else if (text == 'SalesItemLineDetail') {
							SalesItemLineDetail = new SalesItemLineDetail(parser);
						} else if (text == 'SubTotalLineDetail') {
							SubTotalLineDetail = new LinkedTxn(parser);
						} else if (text == 'GroupLineDetail') {
							GroupLineDetail = new GroupLineDetail(parser);
						} else {
							System.debug(LoggingLevel.WARN, 'Line consuming unrecognized property: '+text);
							consumeObject(parser);
						}
					}
				}
			}
		}
	}

	public class GroupLineDetail {
		public GroupItemRef GroupItemRef {get;set;}
		public Decimal Quantity {get;set;}
		public Decimal Amount {get;set;}

		public GroupLineDetail(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 == 'GroupItemRef') {
							GroupItemRef = new GroupItemRef(parser);
						} else if(text == 'Quantity'){
							Quantity = parser.getDecimalValue();
						} else if(text == 'Amount'){
							Amount = parser.getDecimalValue();
						} else {
							System.debug(LoggingLevel.WARN, 'SalesItemLineDetail_Z consuming unrecognized property: '+text);
							consumeObject(parser);
						}
					}
				}
			}
		}
	}
	
	public class SalesItemLineDetail_Z {
		public CurrencyRef ItemRef {get;set;} 
		public Integer UnitPrice {get;set;} 
		public Integer Qty {get;set;} 
		public CurrencyRef ItemAccountRef {get;set;} 
		public TaxCodeRef TaxCodeRef {get;set;} 

		public SalesItemLineDetail_Z(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 == 'ItemRef') {
							ItemRef = new CurrencyRef(parser);
						} else if (text == 'UnitPrice') {
							UnitPrice = parser.getIntegerValue();
						} else if (text == 'Qty') {
							Qty = parser.getIntegerValue();
						} else if (text == 'ItemAccountRef') {
							ItemAccountRef = new CurrencyRef(parser);
						} else if (text == 'TaxCodeRef') {
							TaxCodeRef = new TaxCodeRef(parser);
						} else {
							System.debug(LoggingLevel.WARN, 'SalesItemLineDetail_Z consuming unrecognized property: '+text);
							consumeObject(parser);
						}
					}
				}
			}
		}
	}
	
	public class GroupItemRef {
		public String name {get;set;}
		public String value {get;set;}

		public GroupItemRef(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, 'TaxCodeRef consuming unrecognized property: '+text);
							consumeObject(parser);
						}
					}
				}
			}
		}
	}

	public class TaxCodeRef {
		public String value {get;set;} 
		public String name {get;set;} 

		public TaxCodeRef(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, 'TaxCodeRef consuming unrecognized property: '+text);
							consumeObject(parser);
						}
					}
				}
			}
		}
	}
	
	public QueryResponse QueryResponse {get;set;} 
	

	public InvoiceQueryJSON (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 == 'QueryResponse') {
						QueryResponse = new QueryResponse(parser);
					} else {
						System.debug(LoggingLevel.WARN, 'QBInvoiceQueryJSON 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 QueryResponse {
		public List<Invoice> Invoice {get;set;} 
		public Integer startPosition {get;set;} 
		public Integer maxResults {get;set;} 
		public Integer totalCount {get;set;} 

		public QueryResponse(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 == 'Invoice') {
							Invoice = arrayOfInvoice(parser);
						} else if (text == 'startPosition') {
							startPosition = parser.getIntegerValue();
						} else if (text == 'maxResults') {
							maxResults = parser.getIntegerValue();
						} else if (text == 'totalCount') {
							totalCount = parser.getIntegerValue();
						} else {
							System.debug(LoggingLevel.WARN, 'QueryResponse consuming unrecognized property: '+text);
							consumeObject(parser);
						}
					}
				}
			}
		}
	}
	
	public class CustomField {
		public String DefinitionId {get;set;} 
		public String Type_Z {get;set;} // in json: Type

		public CustomField(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 == 'DefinitionId') {
							DefinitionId = parser.getText();
						} else if (text == 'Type') {
							Type_Z = parser.getText();
						} else {
							System.debug(LoggingLevel.WARN, 'CustomField consuming unrecognized property: '+text);
							consumeObject(parser);
						}
					}
				}
			}
		}
	}
	
	public class LinkedTxn {

		public String TxnId;
		public String TxnType;

		public LinkedTxn(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) {
						{
							System.debug(LoggingLevel.WARN, 'LinkedTxn consuming unrecognized property: '+text);
							consumeObject(parser);

							if (text == 'TxnId') {
								TxnId = parser.getText();
							} else if (text == 'TxnType') {
								TxnType = parser.getText();
							} else {
								System.debug(LoggingLevel.WARN, 'LinkedTxn consuming unrecognized property: '+text);
								consumeObject(parser);
							}
						}
					}
				}
			}
		}
	}

	public class BillEmail {

		public String Address;

		public BillEmail(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) {
						{
							System.debug(LoggingLevel.WARN, 'LinkedTxn consuming unrecognized property: '+text);
							consumeObject(parser);

							if (text == 'Address') {
								Address = parser.getText();
							} else {
								System.debug(LoggingLevel.WARN, 'LinkedTxn consuming unrecognized property: '+text);
								consumeObject(parser);
							}
						}
					}
				}
			}
		}
	}
	
	
	public static InvoiceQueryJSON parse(String json) {
		System.JSONParser parser = System.JSON.createParser(json);
		return new QBInvoiceQueryJSON(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);
	}
	


    private static List<CustomField> arrayOfCustomField(System.JSONParser p) {
        List<CustomField> res = new List<CustomField>();
        if (p.getCurrentToken() == null) p.nextToken();
        while (p.nextToken() != System.JSONToken.END_ARRAY) {
            res.add(new CustomField(p));
        }
        return res;
    }





    private static List<Line> arrayOfLine(System.JSONParser p) {
        List<Line> res = new List<Line>();
        if (p.getCurrentToken() == null) p.nextToken();
        while (p.nextToken() != System.JSONToken.END_ARRAY) {
            res.add(new Line(p));
        }
        return res;
    }

    private static List<LinkedTxn> arrayOfLinkedTxn(System.JSONParser p) {
        List<LinkedTxn> res = new List<LinkedTxn>();
        if (p.getCurrentToken() == null) p.nextToken();
        while (p.nextToken() != System.JSONToken.END_ARRAY) {
            res.add(new LinkedTxn(p));
        }
        return res;
    }



    private static List<Line_Z> arrayOfLine_Z(System.JSONParser p) {
        List<Line_Z> res = new List<Line_Z>();
        if (p.getCurrentToken() == null) p.nextToken();
        while (p.nextToken() != System.JSONToken.END_ARRAY) {
            res.add(new Line_Z(p));
        }
        return res;
    }



    private static List<Invoice> arrayOfInvoice(System.JSONParser p) {
        List<Invoice> res = new List<Invoice>();
        if (p.getCurrentToken() == null) p.nextToken();
        while (p.nextToken() != System.JSONToken.END_ARRAY) {
            res.add(new Invoice(p));
        }
        return res;
    }


    private static List<Line_Y> arrayOfLine_Y(System.JSONParser p) {
        List<Line_Y> res = new List<Line_Y>();
        if (p.getCurrentToken() == null) p.nextToken();
        while (p.nextToken() != System.JSONToken.END_ARRAY) {
            res.add(new Line_Y(p));
        }
        return res;
    }


}

QuickBooks Integration with Salesforce

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:

  1. 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.
  2. 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

  1. Log In to QuickBooks: Start by logging in to your QuickBooks account. Enter your credentials to access the dashboard.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. refer to this Link https://developer.intuit.com/app/developer/qbo/docs/get-started you will understand everything.
  7. 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

  1. The webhook class is designed to capture and store Item information of Quickbooks within the Salesforce platform during creation.
  2. 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!”

Sync Salesforce Accounts to QuickBooks Customers

Note:

“Please refer to the previous post for instructions on configuring if you haven’t created a QuickBooks account or Salesforce accounts and configured these settings before.”

First, you have to create a field in the Account object, QBO_Id__c (external), and SyncToken__c (Text – 255), to map the QuickBooks ID to the Salesforce Account ID


“Create a wrapper class for customer creation from QuickBooks inside the API. If needed, you can also refer to the official API documentation for page references regarding the request and response.”

public class CustomerJSON {

    public class BillAddr {
		public String Id;
		public String Line1;
		public String City;
		public String CountrySubDivisionCode;
		public String PostalCode;
		public String Lat;
		public String Long_x;
		public String Country;
	}

	public class ParentRef {
		public String value;
	}

	public class CurrencyRef {
		public String value;
		public String name;
	}

	public QueryResponse QueryResponse;
	public String timeString;

	public class PrimaryPhone {
		public String FreeFormNumber;
	}

	public class Customer {
		public Boolean Taxable;
		public BillAddr BillAddr;
		public BillAddr ShipAddr;
		public Boolean Job;
		public Boolean BillWithParent;
		public ParentRef ParentRef;
		public Integer Level;
		public Decimal Balance;
		public Decimal BalanceWithJobs;
		public CurrencyRef CurrencyRef;
		public String PreferredDeliveryMethod;
		public Boolean IsProject;
		public String domain;
		public Boolean sparse;
		public String Id;
		public String SyncToken;
		public MetaData MetaData;
		public String FullyQualifiedName;
		public String CompanyName;
		public String GivenName;
		public String FamilyName;
		public String DisplayName;
		public String PrintOnCheckName;
		public Boolean Active;
		public PrimaryPhone PrimaryPhone;
		public PrimaryEmailAddr PrimaryEmailAddr;
		public ParentRef DefaultTaxCodeRef;
		public String TaxExemptionReasonId;
		public String ResaleNum;
	}

	public class MetaData {
		public String CreateTime;
		public String LastUpdatedTime;
	}

	public class QueryResponse {
		public List<Customer> Customer;
		public Integer startPosition;
		public Integer maxResults;
	}

	public class PrimaryEmailAddr {
		public String Address;
	}

	public static QBCustomerJSON parse(String json) {
		return (QBCustomerJSON) System.JSON.deserialize(json, QBCustomerJSON.class);
	}

	public static QBCustomerJSON.Customer parseCustomer(String json) {
		return (QBCustomerJSON.Customer) System.JSON.deserialize(json, QBCustomerJSON.Customer.class);
	}

}
public class CustomerResponseJSON {
    
    public class CurrencyRef {
		public String value;
		public String name;
	}

	public class Customer {
		public Boolean Taxable;
		public Boolean Job;
		public Boolean BillWithParent;
		public Decimal Balance;
		public Decimal BalanceWithJobs;
		public CurrencyRef CurrencyRef;
		public String PreferredDeliveryMethod;
		public Boolean IsProject;
		public String domain;
		public Boolean sparse;
		public String Id;
		public String SyncToken;
		public MetaData MetaData;
		public String GivenName;
		public String FamilyName;
		public String FullyQualifiedName;
		public String CompanyName;
		public String DisplayName;
		public String PrintOnCheckName;
		public Boolean Active;
		public PrimaryEmailAddr PrimaryEmailAddr;
		public DefaultTaxCodeRef DefaultTaxCodeRef;
	}

	public class MetaData {
		public String CreateTime;
		public String LastUpdatedTime;
	}

	public Customer Customer;
	public String times;

	public class DefaultTaxCodeRef {
		public String value;
	}

	public class PrimaryEmailAddr {
		public String Address;
	}

	
	public static CustomerResponseJSON parse(String json) {
		return (QBCustomerResponseJSON) System.JSON.deserialize(json, QBCustomerResponseJSON.class);
	}

}
public class AccountHelperClass {
    public static Account createOrUpdateCustomer(Account acc){
        if(String.isNotBlank(acc.QBO_Id__c)){
            CustomerJSON accountData = AUthCallout .getCustomer(acc);
            if(accountData.QueryResponse.Customer != null){
                if(accountData.QueryResponse.Customer.size() == 1){
                    for(CustomerJSON.Customer c : accountData.QueryResponse.Customer){
                        acc.QBO_Id__c = c.id;
                        System.debug('QB Id: ' + acc.QBO_Id__c);
                        acc.QBO_SyncToken__c = c.SyncToken;
                        System.debug('QB Synctoken: ' + acc.QBO_SyncToken__c );
                    }
                }else{

                throw new ListException();
                }
            }
        }

        CustomerResponseJSON accountSend = AUthCallout .createCustomer(CustomerMap .mapAccountData(acc));

        if(accountSend .Customer.Id != null){
            acc.QBO_ID__c = accountSend .Customer.Id;
        }
        acc.QBO_SyncToken__c = accountSend .Customer.SyncToken;
	acc.Sap_Trigger_Date_Time__c=System.Now();
        acc.Integration_Result__c='Success';
        acc.Integration_Message__c=JSON.serializePretty(accountPushData);

        return acc;
    }

    public static Account getAccount(Id accId){
        return [SELECT Id,Name, QBO_Id__c,QBO_SyncToken__c,ShippingCity,ShippingStreet,ShippingState,ShippingCountry,ShippingPostalCod,BillingCity,BillingStreet,BillingCountry,BillingPostalCode FROM Account WHERE Id =: accId];
    }
}
public class CustomerMap {
   
    public static String mapAccountData(Account acc){
        CustomerJSON.Customer customer = new CustomerJSON.Customer();
        customer.CompanyName = acc.Name;
        customer.Taxable = True;       
        customer.SyncToken = acc.QBO_SyncToken__c;
        customer.sparse = True;
        QBCustomerJSON.BillAddr shipAddy = new QBCustomerJSON.BillAddr();
        shipAddy.City = acc.ShippingCity;
        shipAddy.Line1 = acc.ShippingStreet;
        shipAddy.CountrySubDivisionCode = acc.ShippingState;
        shipAddy.PostalCode = acc.ShippingPostalCode;
        shipAddy.Country = acc.ShippingCountry;
        customer.ShipAddr = shipAddy;
        customer.Job = False;
        customer.IsProject = False;
        customer.Id = acc.QBO_ID__c;
        customer.DisplayName = acc.Name;
        customer.Active = True;
        return JSON.serialize(customer, True);
    }
    
public with sharing class CustomerController {

    public Id accId{get;set;}
    public Account acc {get;set;}

    public CustomerController (ApexPages.StandardController controller) {
        accId = controller.getRecord().Id;
        acc = AccountHelperClass.getAccount(accId);
    }

    public Pagereference syncQB(){
        acc = AccountHelperClass .createOrUpdateCustomer(acc);
        update acc;
        return new PageReference('/'+accId);
    }
    
}
/**
 * This class handles authentication and QuickBooks API calls for customer creation.
 */
public class AuthCallOut {
    // Variables to store access and refresh tokens
    private String accessToken { get; set; }
    private String refreshToken { get; set; }

    /**
     * Refreshes the access token using the stored refresh token.
     * @return List of strings containing the new access and refresh tokens.
     */
    public List<String> refresh() {
        // Retrieve QuickBooks data from custom setting
        QBData__c QBData = [SELECT ID, Name, refresh_token__c, client_id__c, client_secret__c, auth_url__c
                            FROM QBData__c];

        // Construct OAuth request
        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;

        // Perform HTTP callout to refresh access 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);

        // Parse response and update tokens
        QBRefreshJSON json = QBRefreshJSON.parse(res.getBody());
        List<String> tokens = new List<String>{json.access_token, json.refresh_token};
        accessToken = tokens[0];
        refreshToken = tokens[1];

        return tokens;
    }

    /**
     * Performs a QuickBooks API call to create a customer.
     * @param customerJson JSON string containing customer data.
     * @return Parsed response for the customer creation.
     */
    public static CustomerResponseJSON createCustomer(String customerJson) {
        // Perform authenticated API callout to create a customer
        HttpResponse res = Authcallout('customer', 'POST', customerJson);
        return QBCustomerResponseJSON.parse(res.getBody());
    }

    /**
     * Internal method to perform authenticated QuickBooks API callouts.
     * @param endpoint The API endpoint to call.
     * @param method The HTTP method (GET, POST, PUT, DELETE).
     * @param body The request body in JSON format.
     * @return HTTPResponse object containing the API response.
     */
    private static HttpResponse Authcallout(String endpoint, String method, String body) {
        // Retrieve QuickBooks metadata from custom metadata type
        QBO_Metadata__mdt credentials;
        List<QBO_Metadata__mdt> creds = [SELECT Id, base_url__c, Company_Id__c, MinorVersion__c 
                                        FROM QBO_Metadata__mdt WHERE DeveloperName = 'Default'];

        credentials = creds[0];

        // Construct API endpoint URL with minor version
        HttpRequest req = new HttpRequest();
        String url = 'v3/company/' + credentials.Company_Id__c + '/' + endpoint;
        if (!url.contains('?')) {
            url += '?minorversion=' + credentials.MinorVersion__c;
        } else {
            url += '&minorversion=' + credentials.MinorVersion__c;
        }
        req.setEndpoint(credentials.base_url__c + '/' + url);
        req.setMethod(method);
        req.setBody(body);
        req.setHeader('Accept', 'application/json');
        req.setHeader('Content-Type', 'application/json');
        req.setHeader('Authorization', 'Bearer ' + accessToken);

        // Adjust content type for query endpoints
        if (endpoint.contains('query')) {
            req.setHeader('Content-Type', 'application/text');
        }

        // Perform HTTP callout and handle exceptions
        Http http = new Http();
        HttpResponse res;
        try {
            res = http.send(req);
        } catch (Exception e) {
            System.debug(e.getMessage());
        }

        return res;
    }
}
<apex:page standardController="Account" extensions="CustomerController">
</apex:page>

Salesforce for Slack – Unlock collaboration across sales and service

Streamlining Communication and Collaboration: Integrating Salesforce with Slack

In today’s fast-paced business environment, effective communication and collaboration are paramount to success. Companies are constantly on the lookout for innovative ways to bridge the gap between their various platforms and tools, ensuring seamless information flow. One such powerful integration is between Salesforce, the leading customer relationship management (CRM) platform, and Slack, a popular team communication app. In this blog post, we’ll dive into a specific use case that highlights the potential of this integration: notifying Slack users about important updates within Salesforce using the @mention chatter feed feature.

The Use Case: Salesforce Integration with Slack App 

Imagine you’re a sales professional working with Salesforce to manage your customer interactions, deals, and opportunities. On the other hand, your team relies on Slack for real-time communication and collaboration. Now, consider a scenario where you want to keep your team updated about the status of deals in Salesforce without having to switch back and forth between platforms. This is where the integration between Salesforce and Slack comes to the rescue.

Seamless @Mentions via Chatter Posts

Salesforce offers a robust feature called Chatter, which allows users to collaborate and communicate within the CRM platform. You can use Chatter to make posts and comments related to various records, including deals, leads, and contacts. With the integration set up, you can now leverage the @mention functionality in Chatter posts to bring the attention of specific team members to important updates.

For instance, let’s say you’ve just closed a deal that was being managed in Salesforce. Instead of sending separate emails or messages to your team members, you can create a Chatter post and @mention relevant users. For example, you can write a post like, “@gaurav Hii, we have a big deal please close asap !”  This simple action ensures that Gaurav receives a notification about the update.

Instant Notifications on Slack

Here’s where the integration’s magic truly shines. As soon as you hit the “Post” button on your Chatter update, the integration between Salesforce and Slack springs into action. The mentioned user, in this case, Gaurav, will receive a notification directly on Slack. This means that Gaurav doesn’t need to keep checking Salesforce for updates; the information comes to him in a platform he’s actively engaged with.

Setting Up the Integration

Setting up this integration doesn’t have to be a daunting task. Both Salesforce and Slack offer user-friendly methods to connect the two platforms. By using Salesforce’s AppExchange and Slack’s App Directory, you can find pre-built integrations or connect the platforms using APIs if you require more customized functionality. Follow the step-by-step guides provided by both platforms to ensure a smooth and secure integration process.

In Conclusion

The integration between Salesforce and Slack offers a powerful solution for enhancing communication and collaboration within your organization. By harnessing the capabilities of Chatter’s @mention feature and the instant notification system in Slack, you can keep your team members informed about important updates without any friction. This streamlined process not only saves time but also ensures that everyone is on the same page when it comes to crucial deals and customer interactions. So, whether you’re a sales professional, a project manager, or anyone looking to optimize their workflow, consider integrating Salesforce with Slack to experience a new level of efficiency and connectivity.

Step 1: Install and Authorize

The first step in the integration process is to install the Slack app and grant the necessary permissions. Follow these steps:

  • Install Slack App: Head over to your Salesforce account’s AppExchange and search for the Slack integration app.or you can click the link https://appexchange.salesforce.com/appxListingDetail?listingId=a0N3A00000FnD9mUAF. Once you’ve found it, click on the “Get It Now” button and follow the installation instructions. This will ensure the app is added to your Salesforce instance.
  • Authorize Slack App: After installation, open the Slack app from your Salesforce account. You’ll be prompted to authorize the app to access your Slack workspace. This step is crucial for enabling data sharing between the two platforms. Click “Authorize” and follow the authentication steps provided.

Step 2: Create a Custom Field

To effectively link the Slack user IDs with Salesforce records, you’ll need to create a custom field in the User object. Here’s how:

  • Navigate to Object Manager: From your Salesforce homepage, click on the “App Launcher” (grid icon) and search for “Object Manager.” Select “User” from the list of objects.
  • Create a Custom Field: In the User object, click on “Fields & Relationships” and then “New.” Choose the data type as “Text” and give the field a meaningful name like “Slack ID.”
  • Field Properties: Set the field properties according to your preferences. You can choose whether the field should be visible to all users, required, or unique.

Step 3: Authorize your workspace

Click 9 dots in your salesforce org  search the slack setup app click and authorize to your workspace.

Step 4: Click the automatic configuration

Click the new Message Destination button A modal will appear to provide the necessary information.

Like MessageDestination Name, Slack workspace, Slack channel, or person Name, And Hit save.

Step 4: Mapping IDs

Now that you have the necessary components in place, it’s time to establish a connection between the Slack user IDs and Salesforce records:

  • Gather Slack User IDs: In your Slack app, find the user IDs that correspond to each user in your Salesforce organization. These IDs are unique identifiers within Slack and will help establish the link.
  • Copy the message destinationId and Update Salesforce User Records: Go to the user profiles in Salesforce one by one. In the newly created “Slack ID” field, input the corresponding Slack user ID. This step effectively maps each user’s Salesforce profile with their Slack identity.

Now Create a Record-Trigger flow  To Feed Item Object  

Follow these steps to create a flow:

Final will Look like this

/*
 *	Author Name: Gaurav Kumar
	Date: 30-Aug-2023
	Purpose: Purpose for the method to get the chatter post text items,and mention user 
 * 
*/
public class feedinvocable {
    
    
    @InvocableMethod(Label='get the feed items')
    public static List<List<Id>> getfeed(List<FeedItem> lstfeed){
        List<List<Id>> lstId = new List<List<Id>>();
        
        String communityId = null;
        List<Id> mentionUser =new List<Id>(); 
        try{
            for(FeedItem fd : lstfeed){
            ConnectApi.FeedElement feedItem = ConnectApi.ChatterFeeds.getFeedElement(communityId, fd.id);
            List<ConnectApi.MessageSegment> messageSegments = feedItem.body.messageSegments;
            for (ConnectApi.MessageSegment messageSegment : messageSegments) {
                if (messageSegment instanceof ConnectApi.MentionSegment) {
                    
                    ConnectApi.MentionSegment mentionSegment = (ConnectApi.MentionSegment) messageSegment;
                    
                    System.debug('Mentioned user name: ' + mentionSegment.name);
                    System.debug('Mentioned user id: ' + mentionSegment.record.id);
                    mentionUser.add(mentionSegment.record.id);
                }
                
            }
                lstId.add(mentionUser);
            
        }
        return lstId;
        }catch(Exception e){
            System.debug('exception occuurred is '+e.getMessage());
            System.debug('exception occuurred is '+e.getLineNumber());
            return null;
            
        }
  
    }
}

Overview

The feedinvocable class is an Apex class in Salesforce that contains an invocable method used to extract mentioned user IDs from Chatter feed items.

Invocable Method: getfeed

The getfeed method is an invocable method within the feedinvocable class. It takes a list of FeedItem objects as input and returns a list of lists of user IDs.

Signature

public static List<List<Id>> getfeed(List<FeedItem> lstfeed)

Parameters

  • lstfeed (Type: List<FeedItem>) – A list of FeedItem objects representing Chatter feed items from which mentioned user IDs need to be extracted.

Return Value

  • Type: List<List<Id>> – A list of lists of user IDs. Each inner list corresponds to the user IDs mentioned in a particular feed item.

Description

The getfeed method iterates through the input list of FeedItem objects. For each FeedItem, it retrieves the associated Chatter feed element using the ConnectApi.ChatterFeeds.getFeedElement method. It then extracts mentioned user IDs from the feed element’s body message segments.

The method specifically looks for ConnectApi.MentionSegment instances within the message segments. For each mention found, the mentioned user’s ID is extracted and added to the mentionUser list. After processing all message segments in a feed item, the mentionUser list is added to the lstId list.

Finally, the lstId list, containing lists of mentioned user IDs, is returned

If an exception occurs during the process, the method catches the exception, logs relevant debug information (error message and line number), and returns null.

Conclusion

The feedinvocable class provides a convenient way to extract mentioned user IDs from Chatter feed items using the getfeed invocable method. It can be utilized in Salesforce applications to enhance collaboration and communication among users