Configure the Integration Function
Next, we will begin coding the execution logic of the ecommerce bot by configuring the Integration function that we initialized earlier.
The function’s directory, functions/ecommerce_function contains:
- The index.js main function file
- The ConvoKraft handler function files
- The catalyst-config.json configuration file
- Node modules
- package.json and package-lock.json dependency files
We will be adding code in the handler functions: execute.js, fallback.js, failure.js, welcome.js, as well the main index.js file.
Copy and paste the code given below carefully in the respective files using any IDE of your choice and save them.
execute.jscopyimport logger from "./logger.js"; import fetch from "node-fetch"; import catalyst from "zcatalyst-sdk-node"; // Function to make GET requests async function get(url, headers) { const response = await fetch(url, { method: 'GET', headers: headers }); return await response.json(); } function getRandomNumber(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } // Function to make POST requests async function post(url, headers, body) { const response = await fetch(url, { method: 'POST', headers: headers, body: JSON.stringify(body) }); return await response.json(); } // Function to make PUT requests async function put(url, headers, body) { const response = await fetch(url, { method: 'PUT', headers: headers, body: JSON.stringify(body) }); return await response.json(); } async function getCRMToken(request) { const catalystApp = catalyst.initialize(request); var connector = catalystApp.connection({ CRMTOKEN: { client_id: `${process.env.client_id}`, client_secret: `${process.env.client_secret}`, auth_url: `${process.env.auth_url}`, refresh_url: `${process.env.refresh_url}`, refresh_token: `${process.env.refresh_token}` } }).getConnector('CRMTOKEN'); let accessTokenResponse=await connector.getAccessToken().then((accessToken) => { //console.log("Inside accesstoken"+accessToken); return accessToken; }); return accessTokenResponse; } // Invoke URL function async function invokeURL(options) { if (options.type === 'GET') { const response = await get(options.url, options.headers); return response; } else if (options.type === 'POST') { const response = await post(options.url, options.headers, options.body); return response; } else if (options.type === 'PUT') { const response = await put(options.url, options.headers, options.body); return response; } else { throw new Error(`Unsupported HTTP method: ${options.type}`); } } // Handle the execute functionality export default async function handleExecute(request) { logger.info('Handling execute request with params : '+JSON.stringify(request)); logger.info('Action NameSpace : '+request.action.namespace); logger.info('Params : '+JSON.stringify(request.params)); logger.info('Broadcast : '+JSON.stringify(request.broadcast)); const result = {}; const cardlist = []; switch(request.action.namespace) { case "listofitems_2": logger.info('Inside NameSpace '+request.action.namespace); const executeViewItemsLogic = async () => { //let zohocrmapiurl=process.env.crmapiurl; console.log("ENV VARIABLE "+JSON.stringify(process.env)); //console.log("Client ID"+`${process.env.client_id}`); const token=await getCRMToken(request); //console.log("ACCESSTOKEN RESP : "+token); const accessToken = 'Zoho-Oauthtoken ' + token; logger.info('Access Token : '+accessToken); const header_data = new Map(); header_data.set('Content-Type', 'application/json'); header_data.set('Authorization', accessToken); const CRMresponse = await invokeURL({ url: 'https://'+process.env.crmapiurl+'/crm/v2/EcomProducts?fields=Name,Product_image_url,Price,Inventory_Count,Product_image', type: 'GET', headers: header_data }); logger.info("Response for list of items "+ JSON.stringify(CRMresponse)); const productNote = { "type": "note", "content": "List of items available are listed as follows" }; cardlist.push(productNote); for (const record of CRMresponse.data) { logger.info('RECPORD INFO : '+JSON.stringify(record)); const name = record.Name; const price = record.Price; const product_id = record.id; const image = record.Product_image; const inventory = record.Inventory_Count; const listItem = { "type": "list", "format": "bullet", "elements": [ { "label": `Name : ${name}` }, { "label": `Total Price : $ ${price}` }, { "label": `Product ID : ${product_id}` }, { "label": `Stock : ${inventory}` } ] }; const productPic = { "type": "image", "content": image }; cardlist.push(productPic); cardlist.push(listItem); logger.info('Card List : : : :'+JSON.stringify(cardlist)); } result.card= cardlist; result.message= "List of Objects in response"; logger.info('Result Obj inside the : : : ' + result); logger.info('Result OF MAP : : : :'+JSON.stringify(result)); }; await executeViewItemsLogic(); logger.info('Result Obj before return : : : ' + JSON.stringify(result)); return result; break; case "vieworderdetails_3": logger.info('Inside NameSpace ' + request.action.namespace); const executeViewOrderLogic = async () => { const token=await getCRMToken(request); var cxemail=request.params.cxemail; const accessToken = 'Zoho-Oauthtoken ' + token; logger.info('Access Token: ' + accessToken); const header_data = new Map(); header_data.set('Content-Type', 'application/json'); header_data.set('Authorization', accessToken); let resp={} try{ resp = await invokeURL({ url: 'https://'+process.env.crmapiurl+'/crm/v4/Orders/search?criteria=((Email:equals:' + cxemail + '))', type: 'GET', headers: header_data }); }catch(er) { logger.info('ERROR WHILE CRM DATA SEARCH ' + er); } logger.info('Response: ' + JSON.stringify(resp)); let items_count = 0; for (const order of resp.data) { items_count++; logger.info('Inside Resp Iterator - Item count : '+items_count+' Order details : '+JSON.stringify(order)); const order_id = order.id; const CRMresponse = await invokeURL({ url: 'https://'+process.env.crmapiurl+'/crm/v3/Orders/' + order_id, type: 'GET', headers: header_data }); logger.info("CRMRESPONSE : : : :"+JSON.stringify(CRMresponse.data[0].product_image_url)); const orderid = order.OrderID; const name = order.Name; const contact = order.Contact_No; const email = order.Email; const product_name = order.Product_Name; const price = order.Price; const delivery_date = order.Expected_Delivery_Date; const delivery_address = order.Delivery_Address; const order_status = order.Order_Status; const listItem = { "type": "list", "format": "bullet", "elements": [ { "label": "Name: " + name }, { "label": "Contact No: " + contact }, { "label": "Email ID: " + email }, { "label": "Product Name: " + product_name }, { "label": "Total Price: $" + price }, { "label": "Expected Delivery Date: " + delivery_date }, { "label": "Delivery Address: " + delivery_address }, { "label": "Order Status: " + order_status } ] }; const productNote = { "type": "note", "content": items_count + ") Your order details for order id : " + orderid + " are listed as follows: " }; const productPic = { "type": "image", "content": CRMresponse.data[0].product_image_url }; cardlist.push(productNote); cardlist.push(productPic); cardlist.push(listItem); logger.info("CARD LIST : : : "+JSON.stringify(cardlist)); } result.card = cardlist; // defining broadcast result.broadcast = { "cxemail": cxemail }; result.message = "The Shoppers Stores. You don't have an order."; logger.info("Result JSON : : : "+JSON.stringify(result)); }; await executeViewOrderLogic(); //logger.info('Result Obj before return: ' + JSON.stringify(result)); return result; break; case "placeanorder_4": logger.info('Inside NameSpace ' + request.action.namespace); const executePlaceOrderLogic = async () => { const placeorder = {}; const token=await getCRMToken(request); const accessToken = 'Zoho-Oauthtoken ' + token; logger.info('Access Token: ' + accessToken); const header_data = new Map(); header_data.set('Content-Type', 'application/json'); header_data.set('Authorization', accessToken); try { const CRMresponse = await invokeURL({ url: 'https://'+process.env.crmapiurl+'/crm/v2/EcomProducts/' + request.params.productid, type: 'GET', headers: header_data }); logger.info('CRM Response: ' + JSON.stringify(CRMresponse)); if (CRMresponse.data == null && CRMresponse.status != 'error') { const stock_count = 0; logger.info('Empty response. Stock count reset.'); placeorder.put('Stock', 'Empty'); } else { const recordid = CRMresponse.data[0].id; let stock_count = CRMresponse.data[0].Inventory_Count; const imageurl = CRMresponse.data[0].Product_image; const productname = CRMresponse.data[0].Name; const price = CRMresponse.data[0].Price; logger.info(recordid+" "+stock_count+" "+imageurl+" "+productname+" "+price); if (stock_count > 0) { stock_count = stock_count - 1; const reduceStock = { id: recordid, Inventory_Count: stock_count }; const updatedata = [reduceStock]; const updatedatamap = { data: updatedata }; const updateResp = await invokeURL({ url: 'https://'+process.env.crmapiurl+'/crm/v2/EcomProducts', type: 'PUT', body: updatedatamap, headers: header_data }); logger.info('Update Response: ' + JSON.stringify(updateResp)); placeorder.Name = productname + ' ' + request.params.cxdate; // Assign the value using dot notation placeorder.Product_Name = productname; // Assign the value using dot notation placeorder.Email = request.params.cxemail; // Assign the value using dot notation placeorder.Delivery_Address = request.params.cxaddr; // Assign the value using dot notation placeorder.Expected_Delivery_Date = new Date(request.params.cxdate).toLocaleDateString(); // Assign the value using dot notation placeorder.product_image_url = imageurl; // Assign the value using dot notation placeorder.Price = price; // Assign the value using dot notation const orderID = request.params.productid + '_' + getRandomNumber(1111111, 9999999); //placeorder.put('OrderID', orderID); placeorder.OrderID = orderID; placeorder.Order_Status = 'Reviewing the Order'; // Assign the value using dot notation const insertdatamap = { data: [placeorder] }; logger.info("INSERT MAP : : :"+JSON.stringify(insertdatamap)); const insertResp = await invokeURL({ url: 'https://'+process.env.crmapiurl+'/crm/v2/Orders', type: 'POST', body: insertdatamap, headers: header_data }); logger.info('Insert Response: ' + JSON.stringify(insertResp)); result.message = 'Your order for the product - ' + CRMresponse.data[0].Name + ' is placed successfully. It will be tried to be delivered on ' + request.params.cxdate + ' to ' + request.params.cxaddr + '. Our Sales team will reach out to you via email to discuss COD or prepayment before delivery.'; } else { result.message = 'The item is out of stock. Please retry after some days as we restock.'; } } } catch (e) { logger.info(e.lineNo); logger.info(e.message); logger.info(e); result.message = 'There was some issue during the order. Please contact the support team. They shall assist in placing this order for you.'; } result.broadcast = { cxemail: request.params.cxemail }; }; await executePlaceOrderLogic(); return result; break; case "changedeliveryaddress": // New case for "changedeliveryaddress" logger.info('Inside NameSpace ' + request.action.namespace); const executeChangeDeliveryAddressLogic = async () => { const token=await getCRMToken(request); const cxorderid = request.params.cxorderid; const cxaddr = request.params.cxaddr; const cxemailid = request.params.cxemail; const accessToken = 'Zoho-Oauthtoken ' + token; logger.info('Access Token: ' + accessToken); const header_data = new Map(); header_data.set('Content-Type', 'application/json'); header_data.set('Authorization', accessToken); let searchOrderResp={}; try{ searchOrderResp = await invokeURL({ url: 'https://'+process.env.crmapiurl+'/crm/v4/Orders/search?criteria=((OrderID:equals:' + cxorderid + '))', type: 'GET', headers: header_data }); }catch(e) { logger.info("ERROR DURING SEARCH ORDER : : "+e); searchOrderResp={}; } logger.info("Result of search Order: : : "+JSON.stringify(searchOrderResp)); if(searchOrderResp.data!=undefined) { const recordId = searchOrderResp.data[0].id; //logger.info("Result of search Order: : : "+JSON.stringify(searchOrderResp.data[0])); const recordEmail=searchOrderResp.data[0].Email; const recordorderId=searchOrderResp.data[0].OrderID; if(cxemailid == recordEmail && cxorderid == recordorderId) { const updateData = [ { id: recordId, Delivery_Address: cxaddr } ]; const updateDataMap = { data: updateData }; const updateResp = await invokeURL({ url: 'https://'+process.env.crmapiurl+'/crm/v2/Orders', type: 'PUT', body: updateDataMap, headers: header_data }); logger.info('Update Response: ' + JSON.stringify(updateResp)); result.message = 'Delivery address updated successfully.'; } else{ result.message='Given Order ID or Email is not proper. Please check and retry'; } } else { result.message='Given Order ID or Email is not proper. Please check and retry'; } }; await executeChangeDeliveryAddressLogic(); return result; break; case "rescheduleorderdate": logger.info('Inside NameSpace ' + request.action.namespace); const executeChangeDeliveryDateLogic = async () => { const token=await getCRMToken(request); const accessToken = 'Zoho-Oauthtoken ' + token; logger.info('Access Token: ' + accessToken); const header_data = new Map(); header_data.set('Content-Type', 'application/json'); header_data.set('Authorization', accessToken); const cxemailid = request.params.cxemail; const cxorderid = request.params.cxorderid; let searchOrderResp={}; try{ searchOrderResp = await invokeURL({ url: 'https://'+process.env.crmapiurl+'/crm/v2/Orders/search?criteria=((OrderID:equals:' + request.params.cxorderid + '))', type: 'GET', headers: header_data }); }catch(e) { searchOrderResp={}; } if(searchOrderResp.data!=undefined){ logger.info("Result of search Order: : : "+JSON.stringify(searchOrderResp.data[0])); const recordId = searchOrderResp.data[0].id; const recordEmail=searchOrderResp.data[0].Email; const recordorderId=searchOrderResp.data[0].OrderID; if(cxemailid == recordEmail && cxorderid == recordorderId) { const datemap = { id: recordId, Expected_Delivery_Date: new Date(request.params.cxdate).toLocaleDateString() }; const updatedata = [datemap]; const updatedatamap = { data: updatedata }; logger.info("update Map : : : "+JSON.stringify(updatedatamap)); const updateResp = await invokeURL({ url: 'https://'+process.env.crmapiurl+'/crm/v2/Orders', type: 'PUT', body: updatedatamap, headers: header_data }); logger.info("updateResponse : : : "+JSON.stringify(updateResp)); result.broadcast = { cxemail: request.params.cxemail }; result.message = 'Your order has been rescheduled successfully.'; }else{ result.message='Given Order ID or Email is not proper. Please check and retry'; } } else{ result.message='Given Order ID or Email is not proper. Please check and retry'; } }; await executeChangeDeliveryDateLogic(); return result; break; default : result.message='Given request is invalid. Please check and retry'; return result; break; } //return response; }
welcome.jscopyimport logger from "./logger.js"; // Handle the welcome functionality export default function handleWelcome() { logger.info('Handling welcome request'); return { "welcome_response": { "message": "Welcome to your Bot. Please ask your queries" } }; }
failure.jscopyimport logger from "./logger.js"; // Handle the failure functionality export default function handleFailure() { logger.info('Handling failure request'); const result = { "message": "Something went wrong while checking your query. Please try again later." }; return JSON.stringify(result); }
fallback.jscopyimport logger from "./logger.js"; // Handle the fallback functionality export default function handleFallback() { logger.info('Handling fallback request'); const result = { "message": "Sorry couldn't get that." }; return JSON.stringify(result); }
index.jscopyimport logger from './logger.js'; import IntegResponse from './integ-response.js'; import handleWelcome from './welcome.js'; import handleFallback from './fallback.js'; import handleExecute from './execute.js'; import handleFailure from './failure.js'; export default async (request, response) => { let jsonResponse = {}; try { logger.info('REQUEST FLOW FOR : : : ' + request.todo); switch (request.todo) { case "welcome": jsonResponse = handleWelcome(); break; case "execute": jsonResponse = await handleExecute(request); break; case "fallback": jsonResponse = handleFallback(); break; case "failure": jsonResponse = handleFailure(); break; default: jsonResponse = JSON.stringify({ "message": "Error Trying to parse your details" }); } } catch (err) { logger.error('Error while executing handler: ' + err); logger.error('REQUEST OBJECT: ' + JSON.stringify(request)); jsonResponse = JSON.stringify({ "message": "Error Trying to parse your details" }); } //The Response has to be encapuslated into an IntegResponse Object response.end(new IntegResponse(jsonResponse)); };
Note: Please go through all the codes given in this section to ensure you fully understand it.Let’s quickly go through each of the function files and the logic coded in them:
- execute.js: This file contains the main execution logic of all the actions of our ecommerce bot. All these actions authenticate your Zoho CRM account using the configured environmental variables and fetches the required details or performs the below listed actions, as required. We will be configuring the environmental variables for the ecommerce_function Catalyst function directly from the Catalyst console, after deploying the function to the console in this step.
listofitems_2 : This action initiates an API request and triggers a GET method on the custom CRM module EcomProducts, retrieving the product name, product ID, product image URL, price, and stock count.
placeanorder_4 : This action accepts the product ID as an input from the user. It then checks for the stock availability of the particular product in the EcomProducts CRM module. If the stock is available, the bot proceeds to ask the user’s email address, the expected date of delivery and the delivery address. Once these details are entered, the bot places an order by initiating an API request and triggering a POST method on the custom CRM module Orders.The user will be intimated on the successful placement of the order via the chatbot. If the product stock is not available, then the same will be informed to the user.
vieworderdetails_3 :This action triggers the Zoho CRM Search API to perform a search operation on the custom module Orders. The customer name, contact number, email id, product name, price of the product, product image URL, expected delivery date, delivery address and status of the order details are fetched as the response.
changedeliveryaddress : This action accepts the email id associated with the order, the order ID and the address to which the order has to be delivered, as inputs. The Zoho CRM Search API is triggered on our custom module Orders. The given email id and order id are validated and the new address is updated in the order by executing a PUT method on the custom module Orders.
rescheduleorderdate : This action accepts the email id associated with the order, and the order ID as inputs.The Zoho CRM Search API is triggered on our custom module Orders, the search operation is done for the given orderID. The email id and order id are validated and the next possible date is updated as the delivery date in the order again by executing a PUT method on the custom module Orders.
The above mentioned action names are namespaces of the action created in the console. Please make sure to update the namespaces generated for your actions in the code in line number 82, 145, 238, 337, 407 for the switch cases listofitems_2, vieworderdetails, placeanorder, changedeliveryaddress, rescheduleordertolaterdate respectively.
You can get the namespaces of the action from the console as shown in the screenshot below.
welcome.js: In this file, we have configured the message to be displayed to greet the user when initiating a fresh conversation with the user.
Fallback.js: In this file, we have configured the message to be displayed to the user when the bot fails to comprehend user’s message. In this case, this response will be shown in the conversation until the user enters the valid response and the bot understands it.
failure.js: In this file, we have configured the message to be displayed to the user when an exception occurs while executing the business logic.
index.js: his is the entry point of the function and here we will map the corresponding handler files such as execute, welcome, fallback or failure based on the todo prompt during the bot conversation.
Last Updated 2024-05-20 18:33:27 +0530 +0530