CRM Connector Application

Build a Lead Manager application that connects to your Zoho CRM account using Catalyst connector, and enables you to view, create, update, or delete leads from the Leads module directly

Configure the Functions Directory

We will now begin coding the Lead Manager application by configuring the Advanced I/O function.

If you initialized the Advanced I/O Function in Java, its directory, functions/crmCRUD, contains:

  • The CRMCRUD.java main function file
  • The catalyst-config.json configuration file
  • Java library files in the lib folder
  • .classpath and .project dependency files

If you initialized the Advanced I/O function in Node.js, its directory, functions/crm_crud, contains:

You will be adding code in CRMCRUD.java or index.js based on the stack you initialized.

You can use any IDE to configure the function.

Note: Please go through the code in this section to make sure you fully understand it. We will discuss the function and client code, after you configure the client.

For the Java function, you can directly copy the code below and paste it in CRMCRUD.java located in the functions/crmCRUD directory and save the file.

  • View code for CRMCRUD.java

    Copied 
    import java.util.logging.Logger;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    import java.io.InputStreamReader;
    import java.util.ArrayList;
    import java.util.logging.Level;
    
    import javax.servlet.ServletInputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import com.catalyst.advanced.CatalystAdvancedIOHandler;
    import com.zc.auth.connectors.ZCConnection;
    import com.zc.component.ZCUserDetail;
    import com.zc.component.object.ZCObject;
    import com.zc.component.object.ZCRowObject;
    import com.zc.component.object.ZCTable;
    import com.zc.component.users.ZCUser;
    import com.zc.component.zcql.ZCQL;
    
    import org.json.simple.JSONObject;
    import org.json.simple.parser.JSONParser;
    
    import okhttp3.HttpUrl;
    import okhttp3.MediaType;
    import okhttp3.OkHttpClient;
    import okhttp3.Request;
    import okhttp3.RequestBody;
    
    public class CRMCRUD implements CatalystAdvancedIOHandler {
    	private static final Logger LOGGER = Logger.getLogger(CRMCRUD.class.getName());
    	private String apiUrl = "https://www.zohoapis.com/crm/v2/Leads";
    	private String GET = "GET";
    	private String POST = "POST";
    	private String PUT = "PUT";
    	private String DELETE = "DELETE";
    	private String CLIENT_ID = "{{YOUR_CLIENT_ID}}"; //Add your client ID
    	private String CLIENT_SECRET = "{{YOUR_CLIENT_SECRET}}"; //Add your client secret
    	OkHttpClient client = new OkHttpClient();
    
    	@Override
    	@SuppressWarnings("unchecked")
    	public void runner(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		try {
    
    			String url = request.getRequestURI();
    			String method = request.getMethod();
    
    			String responseData = "";
    			String recordID = "";
    			Pattern p = Pattern.compile("([0-9]+)");
    			MediaType mediaType = MediaType.parse("application/json");
    			JSONParser jsonParser = new JSONParser();
    			JSONObject data = new JSONObject();
    			org.json.simple.JSONArray reqData = new org.json.simple.JSONArray();
    
    			//Fetches the Refresh Token by calling the getRefreshToken() function, and inserts it along with the userID in the Token table
    			if (Pattern.matches("/generateToken", url) && method.equals(GET)) {
    
    				String code = request.getParameter("code");
    				String domain = (request.getHeader("host").contains("localhost") ? ("http://" + request.getHeader("host")) : ("https://") + request.getHeader("host").split(":")[0]);
    				ZCUserDetail details = ZCUser.getInstance().getCurrentUser();
    
    				ZCObject object = ZCObject.getInstance();
    				ZCRowObject row = ZCRowObject.getInstance();
    				row.set("refresh_token", getRefreshToken(code, domain));
    				row.set("userId", details.getUserId());
    
    				ZCTable tab = object.getTable(1824000000686079L); //Replace this with the Table ID of your table 
    				tab.insertRow(row);
    				response.setStatus(200);
    				response.sendRedirect(domain + "/app/index.html"); 
    
    			//Fetches the user details by calling the getUserDetails() function 
    			} else if (Pattern.matches("/getUserDetails", url) && method.equals(GET)) {
    
    				ArrayList<ZCRowObject> user = getUserDetails();
    				JSONObject resp = new JSONObject();
    
    				if (user.isEmpty()) {
    					resp.put("userId", null);
    					response.setContentType("application/json");
    					response.getWriter().write(resp.toJSONString());
    					response.setStatus(200);
    				} else {
    					resp.put("userId", user.get(0).get("Token", "userId"));
    					response.setContentType("application/json");
    					response.getWriter().write(resp.toJSONString());
    					response.setStatus(200);
    				}
    
    			//Executes various APIs to access, add, or modify leads in CRM
    			//Fetches all leads
    			} else if (Pattern.matches("/crmData", url) && method.equals(GET)) {
    
    				responseData = getResponse(GET, null);
    
    			//Fetches a particular lead
    			} else if (Pattern.matches("/crmData/([0-9]+)", url) && method.equals(GET)) {
    
    				Matcher m = p.matcher(url);
    
    				if (m.find()) {
    					recordID = m.group(1);
    				}
    				apiUrl = apiUrl + "/" + recordID;
    
    				responseData = getResponse(GET, null);
    
    			//Adds a new lead
    			} else if (Pattern.matches("/crmData", url) && method.equals(POST)) {
    
    				ServletInputStream requestBody = request.getInputStream();
    
    				JSONObject jsonObject = (JSONObject) jsonParser.parse(new InputStreamReader(requestBody, "UTF-8"));
    
    				reqData.add(jsonObject);
    
    				data.put("data", reqData);
    
    				RequestBody body = RequestBody.create(mediaType, data.toString());
    
    				responseData = getResponse(POST, body);
    
    			//Deletes a lead
    			} else if (Pattern.matches("/crmData/([0-9]+)", url) && method.equals(DELETE)) {
    
    				Matcher m = p.matcher(url);
    
    				if (m.find()) {
    					recordID = m.group(1);
    				}
    				apiUrl = apiUrl + "/" + recordID;
    
    				responseData = getResponse(DELETE, null);
    
    			//Edits a lead
    			} else if (Pattern.matches("/crmData/([0-9]+)", url) && method.equals(PUT)) {
    
    				Matcher m = p.matcher(url);
    
    				if (m.find()) {
    					recordID = m.group(1);
    				}
    
    				apiUrl = apiUrl + "/" + recordID;
    
    				ServletInputStream requestBody = request.getInputStream();
    
    				JSONObject jsonObject = (JSONObject) jsonParser.parse(new InputStreamReader(requestBody, "UTF-8"));
    
    				reqData.add(jsonObject);
    
    				data.put("data", reqData);
    
    				RequestBody body = RequestBody.create(mediaType, data.toJSONString());
    
    				responseData = getResponse(PUT, body);
    
    			} else {
    				LOGGER.log(Level.SEVERE, "Error. Invalid Request"); //The actions are logged. You can check the logs from Catalyst Logs.
    				response.setStatus(404);
    				responseData = "Error. Invalid Request";
    				response.getWriter().write(responseData);
    			}
    			response.setContentType("application/json");
    			response.getWriter().write(responseData);
    			response.setStatus(200);
    		} catch (Exception e) {
    			LOGGER.log(Level.SEVERE, "Exception in CRM Function ", e);
    			response.setStatus(500);
    			response.getWriter().write(e.toString());
    		}
    	}
    
    	@SuppressWarnings("unchecked")
    	
    	//Fetches an Access Token using the Refresh Token
    	public String getAccessToken(Long userId) throws Exception {
    
    		JSONObject authJson = new JSONObject();
    		JSONObject connectorJson = new JSONObject();
    
    		String query = "SELECT refresh_token FROM Token where UserId=" + userId;
    		ArrayList<ZCRowObject> rowList = ZCQL.getInstance().executeQuery(query);
    
    		authJson.put("client_id", CLIENT_ID);
    		authJson.put("client_secret", CLIENT_SECRET);
    		authJson.put("auth_url", "https://accounts.zoho.com/oauth/v2/token");
    		authJson.put("refresh_url", "https://accounts.zoho.com/oauth/v2/token");
    		authJson.put("refresh_token", rowList.get(0).get("Token", "refresh_token"));
    		connectorJson.put(userId.toString(), authJson);
    
    		return ZCConnection.getInstance(connectorJson).getConnector(userId.toString()).getAccessToken();
    	}
    
    	//Fetches the Refresh Token by passing the required details
    	public String getRefreshToken(String code, String domain) throws Exception {
    
    		HttpUrl.Builder urlBuilder = HttpUrl.parse("https://accounts.zoho.com/oauth/v2/token").newBuilder();
    		urlBuilder.addQueryParameter("code", code);
    		urlBuilder.addQueryParameter("client_id", CLIENT_ID);
    		urlBuilder.addQueryParameter("client_secret", CLIENT_SECRET);
    		urlBuilder.addQueryParameter("grant_type", "authorization_code");
    		urlBuilder.addQueryParameter("redirect_uri", domain + "/server/crmCRUD/generateToken"); 
    
    		String URL = urlBuilder.build().toString();
    		MediaType mediaType = MediaType.parse("text/plain");
    		RequestBody body = RequestBody.create(mediaType, "");
    		Request getResponse = new Request.Builder().url(URL).method(POST, body).build();
    
    		JSONParser jsonParser = new JSONParser();
    		JSONObject data = (JSONObject) jsonParser.parse(client.newCall(getResponse).execute().body().string());
    
    		return data.get("refresh_token").toString();
    	}
    
    	//Passes the Access Token fetched to obtain the authorization needed to perform each action on the CRM module
    	public String getResponse(String METHOD, RequestBody body) throws Exception {
    
    		Long userId = ZCUser.getInstance().getCurrentUser().getUserId();
    		String accessToken = getAccessToken(userId);
    
    		Request getResponse = new Request.Builder().url(apiUrl).method(METHOD, body)
    				.addHeader("Authorization", "Zoho-oauthtoken " + accessToken).build();
    
    		return client.newCall(getResponse).execute().body().string();
    
    	}
    
    	//Fetches the record from the Token table that contains the Refresh Token, by passing the userID
    	public ArrayList<ZCRowObject> getUserDetails() throws Exception {
    
    		Long userId = ZCUser.getInstance().getCurrentUser().getUserId();
    
    		String query = "SELECT * FROM Token where UserId=" + userId;
    		ArrayList<ZCRowObject> rowList = ZCQL.getInstance().executeQuery(query);
    		return rowList;
    	}
    }
    
Note: After you copy and paste this code in your function file, ensure that you replace the following values in it:

Install Express and Node Fetch Frameworks for Node.js

The Node.js function requires three frameworks to be installed. You must install the express, node-fetch, and axios dependencies to import those packages in your code.

express

To install Express.js, navigate to the Node function's directory (functions/crm_crud) in your terminal and execute the following command:

$ npm install express --save

This will install the Express module and save the dependencies.

node-fetch

To install node-fetch, execute the following command from the Node function's directory navigate to the Node function's directory (functions/crm_crud):

$ npm install node-fetch

This will install the module.

axios

To install axios, execute the following command from the Node function's directory navigate to the Node function's directory (functions/crm_crud):

$ npm install axios

This will install the module.

This information will also be updated in the package.json file.

{
	"name": "crm_crud",
	"version": "1.0.0",
	"main": "index.js",
	"author": "yashashwini.p@zohocorp.com",
	"dependencies": {
		"axios": "^0.27.2",
		"express": "^4.18.1",
		"node-fetch": "^3.2.6",
		"zcatalyst-sdk-node": "latest"
	}
}

You can now add the code in the function file.

Copy the code below and paste it in index.js located in functions/crm_crud directory and save the file.

  • View code for index.js

    Copied 
    "use strict";
    const express = require("express");
    const http = require("https");
    const app = express();
    app.use(express.json());
    const catalyst = require("zcatalyst-sdk-node");
    const HOST = "www.zohoapis.com";
    const AUTH_HOST = "https://accounts.zoho.com/oauth/v2/token";
    const PORT = 443;
    const axios = require("axios");
    const CLIENTID = '{{YOUR_CLIENT_ID}}'; //Add your client ID
    const CLIENT_SECRET = '{{YOUR_CLIENT_SECRET}}'; //Add your client secret
    
    app.get("/generateToken", async (req, res) => {
      try {
        const catalystApp = catalyst.initialize(req);
        const code = req.query.code;
    
        let userManagement = catalystApp.userManagement();
        let userDetails = await userManagement.getCurrentUser();
    	const domain = `${process.env.X_ZOHO_CATALYST_IS_LOCAL === 'true' ? "http" : "https"}://${process.env.X_ZOHO_CATALYST_IS_LOCAL === 'true' ? req.headers.host : req.headers.host.split(':')[0]}`
        const refresh_token = await getRefreshToken(code, res, domain);
        const userId = userDetails.user_id;
        const catalystTable = catalystApp.datastore().table("Token");
        await catalystTable.insertRow({
          refresh_token,
          userId,
        });
        res.status(200).redirect(`${domain}/app/index.html`);
      } catch (err) {
        console.log(err);
        res
          .status(500)
          .send({
            message: "Internal Server Error. Please try again after sometime.",
            error: err,
          });
      }
    });
    
    app.get("/getUserDetails", async (req, res) => {
      try {
        const catalystApp = catalyst.initialize(req);
        const userDetails = await getUserDetails(catalystApp);
    
        if (userDetails.length !== 0) {
          res.status(200).send({ userId: userDetails[0].Token.userId });
        } else {
          res.status(200).send({ userId: null });
        }
      } catch (err) {
        console.log(err);
        res
          .status(500)
          .send({
            message:
              "Internal Server Error in Getting User Details. Please try again after sometime.",
            error: err,
          });
      }
    });
    
    app.get("/crmData", async (req, res) => {
      try {
        console.log();
        const catalystApp = catalyst.initialize(req);
        const userDetails = await getUserDetails(catalystApp);
        const accessToken = await getAccessToken(catalystApp, userDetails);
        const options = {
          hostname: HOST,
          port: PORT,
          method: "GET",
          path: `/crm/v2/Leads`,
          headers: {
            Authorization: `Zoho-oauthtoken ${accessToken}`,
          },
        };
        var data = "";
        const request = http.request(options, function (response) {
          response.on("data", function (chunk) {
            data += chunk;
          });
    
          response.on("end", function () {
            console.log(response.statusCode);
            res.setHeader("content-type", "application/json");
            res.status(200).send(data);
          });
        });
        request.end();
      } catch (err) {
        console.log(err);
        res
          .status(500)
          .send({
            message: "Internal Server Error. Please try again after sometime.",
          });
      }
    });
    
    app.get("/crmData/:id", async (req, res) => {
      try {
        const catalystApp = catalyst.initialize(req);
        const userDetails = await getUserDetails(catalystApp);
        const accessToken = await getAccessToken(catalystApp, userDetails);
        const options = {
          hostname: HOST,
          port: PORT,
          method: "GET",
          path: `/crm/v2/Leads/${req.params.id}`,
          headers: {
            Authorization: `Zoho-oauthtoken ${accessToken}`,
          },
        };
        var data = "";
        const request = http.request(options, function (response) {
          response.on("data", function (chunk) {
            data += chunk;
          });
    
          response.on("end", function () {
            res.setHeader("content-type", "application/json");
            res.status(200).send(data);
          });
        });
        request.end();
      } catch (err) {
        console.log(err);
        res.status(500).send({
            message: "Internal Server Error. Please try again after sometime.",
          });
      }
    });
    
    app.post("/crmData", async (req, res) => {
      try {
        const catalystApp = catalyst.initialize(req);
        const createData = req.body;
        const reqData = [];
        reqData.push(createData);
        const data = {
          data: reqData,
        };
        if (!createData) {
          res.status(400).send({ message: "Data Not Found" });
        }
        const userDetails = await getUserDetails(catalystApp);
        const accessToken = await getAccessToken(catalystApp, userDetails);
        const options = {
          hostname: HOST,
          port: PORT,
          method: "POST",
          path: `/crm/v2/Leads`,
          headers: {
            Authorization: `Zoho-oauthtoken ${accessToken}`,
            "Content-Type": "application/json",
          },
        };
        const request = http.request(options, function (response) {
          res.setHeader("content-type", "application/json");
          response.pipe(res);
        });
        request.write(JSON.stringify(data));
        request.end();
      } catch (err) {
        console.log(err);
        res
          .status(500)
          .send({
            message: "Internal Server Error. Please try again after sometime.",
          });
      }
    });
    
    app.put("/crmData/:id", async (req, res) => {
      try {
        const catalystApp = catalyst.initialize(req);
        const updateData = req.body;
        const reqData = [];
        reqData.push(updateData);
        const data = {
          data: reqData,
        };
        if (!updateData) {
          res.status(400).send({ message: "Update Data Not Found" });
        }
        const userDetails = await getUserDetails(catalystApp);
        const accessToken = await getAccessToken(catalystApp, userDetails);
        const options = {
          hostname: HOST,
          port: PORT,
          method: "PUT",
          path: `/crm/v2/Leads/${req.params.id}`,
          headers: {
            Authorization: `Zoho-oauthtoken ${accessToken}`,
            "Content-Type": "application/json",
          },
        };
        const request = http.request(options, function (response) {
          res.setHeader("content-type", "application/json");
          response.pipe(res);
        });
        request.write(JSON.stringify(data));
        request.end();
      } catch (err) {
        console.log(err);
        res
          .status(500)
          .send({
            message: "Internal Server Error. Please try again after sometime.",
          });
      }
    });
    
    app.delete("/crmData/:id", async (req, res) => {
      console.log(`/crm/v2/Leads/${req.params.id}`);
      try {
        const catalystApp = catalyst.initialize(req);
        const userDetails = await getUserDetails(catalystApp);
        const accessToken = await getAccessToken(catalystApp, userDetails);
        const options = {
          hostname: HOST,
          port: PORT,
          method: "DELETE",
          path: `/crm/v2/Leads/${req.params.id}`,
          headers: {
            Authorization: `Zoho-oauthtoken ${accessToken}`,
            "Content-Type": "application/json",
          },
        };
        const request = http.request(options, function (response) {
          res.setHeader("content-type", "application/json");
          response.pipe(res);
        });
        request.end();
      } catch (err) {
        console.log(err);
        res.status(500).send({
            message: "Internal Server Error. Please try again after sometime.",
          });
      }
    });
    
    async function getAccessToken(catalystApp, userDetails) {
      const refresh_token = userDetails[0].Token.refresh_token;
      const userId = userDetails[0].Token.userId;
    
      const credentials = {
        [userId]: {
          client_id: CLIENTID,
          client_secret: CLIENT_SECRET,
          auth_url: AUTH_HOST,
          refresh_url: AUTH_HOST,
          refresh_token,
        },
      };
      const accessToken = await catalystApp.connection(credentials).getConnector(userId).getAccessToken();
      return accessToken;
    }
    
    async function getRefreshToken(code, res, domain) {
      try {
        const url = `${AUTH_HOST}?code=${code}&client_id=${CLIENTID}&client_secret=${CLIENT_SECRET}&grant_type=authorization_code&redirect_uri=${domain}/server/crm_crud/generateToken`;
    	const response = await axios({
    		method: "POST",
    		url
    	  });
        return response.data.refresh_token;
      } catch (err) {
        console.log(err);
        res.status(500).send({
            message: "Internal Server Error. Please try again after sometime.",
            error: err,
          });
      }
    }
    
    async function getUserDetails(catalystApp) {
      let userDetails = await catalystApp.userManagement().getCurrentUser();
      let userDetail = await catalystApp.zcql().executeZCQLQuery(`SELECT * FROM Token where UserId=${userDetails.user_id}`);
      return userDetail;
    }
    
    module.exports = app;
    
Note: After you copy and paste this code in your function file, ensure that you replace the following values in it:

The functions directory is now configured. We will discuss the function and client code, after you configure the client.