Support your restaurant communication with a customizable chatbot template.
What can the template do?
-
Show your restaurantâs menu
-
Collect orders and summarize them
-
Manage table reservations and the booking process
-
Collect clientsâ contact information
-
Pass orders automatically to your backend using webhooks
-
Answer frequent questions and show contact details
To start the order process, users must select the Restaurant Menu option from the menu. The interactive gallery shows a preview of the next steps with short descriptions. Users can decide if they want to start by ordering appetizers, first and main courses, or desserts.
When the order is complete, the chatbot shows the summary that must be confirmed. Here you can add the Question action to collect user data.
The template with the relevant code can be easily customized to your needs.
Features included
To make the Restaurant Bot template work, weâve used a few great ChatBot features.
Postback
By default, when a button is clicked, the bot receives its title. Postback allows you to pass a hidden message when a user clicks the button.
For example, the user clicks +Order and the bot knows that Avocado paste was selected thanks to the Postback value assigned to the button.
Entities
The moment you import the template to your dashboard, all the necessary entities are added to your account.
The bot knows the restaurantâs menu thanks to the productName entity with our products added. This user entity helps your bot validate the user query and saves it to the custom attribute under the same name.
The collected attributes are passed to your backend in the webhookâs payload.
System entities such as Any, Number, and Email help you efficiently collect usersâ data. For example, the Number entity validates responses saved to the custom attribute productQuantity.
Other entities available in the template are foodType and tableNumber.
Filters
Another handy feature that can save you time is filters. Filters add rules to bot actions and responses that decide under what conditions they can be triggered. Instead of adding many interactions, you can have one that routes the chats based on usersâ decisions.
Webhooks
Here is where the magic happens, and the order is handed to the backend. Follow the steps below to set up your webhook and replace the one in the template when youâre ready.
How the backend works?
To add a product to your order, display the current status, and start the process again, weâve prepared a simple backend. See the full code and modify it according to your needs.
Technologies used
-
Express.js - to simplify and clarify parsing the ongoing webhook request.
-
Firebase Functions - to deploy our code to a public HTTPS server.
-
Firebase Realtime Database - to store the order based on the user session.
Weâve split our backend functionality into three different parts:
-
Add product - to accept orders.
-
Order summary - to show the order summary.
-
Start again - to remove the order and start the order process again.
Letâs go through our webhook code.
'use strict';
const TOKEN = '{{YOUR_TOKEN}}'; // put your authorization token here
const FIRESTORE_COLLECTION = 'COLLECTION_NAME'; // put your firestore collection name (https://firebase.google.com/docs/firestore)
const express = require('express');
const { Router } = require('express');
const router = new Router();
const app = express();
const functions = require('firebase-functions');
const firebase = require('firebase-admin');
firebase.initializeApp({
credential: firebase.credential.applicationDefault()
});
// connect to firestore
const db = admin.firestore().collection(FIRESTORE_COLLECTION);
Then you will see the transformOrderToText function, which transforms the order into a text message.
// docs for the text fullfillment is described here: https://www.chatbot.com/docs/object-definitions
const transformOrderToText = (responses = []) => {
let text = '';
if (!responses.length) {
return 'Your order is empty.';
}
responses.map(item => {
text += `${item.productQuantity}x ${item.productName}\n`;
});
return text;
}
Ok! Now is the part to handle the ChatBot webhook verification request. In short, itâs a bilateral verification of the webhook.
router
.route('/')
.get((req, res) => {
if (req.query.token !== TOKEN) {
return res.sendStatus(401);
}
return res.end(req.query.challenge);
});
The next part is the most exciting - handling incoming requests from the ongoing chat.
router
.route('/')
.post((req, res, next) => {
if (req.query.token !== TOKEN) {
return res.sendStatus(401);
}
req.version = req.body.result ? 1 : 2;
const action = req.version === 1 ?
req.body.result.interaction.action : req.body.attributes.action;
if (['add-product', 'order-summary', 'start-again'].includes(action)) {
req.url = `/${action}`;
return next();
}
res.json();
});
For the readability of our code, we direct our incoming request to the appropriate part of the code. We used an action that can be defined in the interaction. Thanks to that, we split our bot functionalities into three dedicated actions: add-product, order-summary, and start-again.
When our backend receives a request, we check if the action we defined earlier in the interaction is the one we expect - if so, we direct our request to the appropriate part of our code.
router
.route('/add-product')
.post((req, res, next) => {
const sessionParameters = req.version === 1 ?
req.body.result.sessionParameters : req.body.attributes;
// get attributes collected in the ongoing chat
const productName = sessionParameters.productName;
const productQuantity = Number(sessionParameters.productQuantity);
// make a product object based on the collected attributes
if (productName && productQuantity) {
req.product = { productName, productQuantity };
// go to the next part of request
return next();
}
// return empty response
return res.json();
})
.post(async (req, res, next) => {
let order = [];
// find sessionId
const sessionId = req.version === 1 ? req.body.sessionId : req.body.chatId;
const product = req.product;
// find a document in the firestore db
const doc = db.doc(sessionId);
const products = await doc.get();
const data = { products: [] };
if (products.data()) {
data.products = products.data().products;
}
// find product in data from db
const findProductIndex = data.products.findIndex(item => item.productName === product.productName);
if (findProductIndex > -1) {
data.products[findProductIndex].productQuantity += product.productQuantity;
} else {
data.products.push(product);
}
// update document
await doc.set(data);
order = data.products;
if (order.length) {
req.order = order;
return next();
}
return res.json();
})
.post((req, res) => {
let responses = [];
if (req.version == 2) {
responses = [
{
type: 'text',
message: 'Product has been added successfully. Your order summary:'
},
{
type: 'text',
message: transformOrderToText(req.order)
}
];
} else {
responses = [
{
type: 'text',
elements: ['Product has been added successfully. Your order summary:']
},
{
type: 'text',
elements: [transformOrderToText(req.order)]
}
];
}
// return responses back to the ChatBot
res.json({ responses });
});
This part is used to add a product to an order, which will be stored in the Firebase Realtime Database.
At the start, you save attributes collected in the chatbot to the productName and productQuantity variables. If you collect them, you create an object that stores a single product of the order.
After that, you open the transaction to your database and store the order using sessionID, which you receive with the incoming request.
If you manage to open the transaction to the database, the system checks if the order already contains anything:
-
if not, you need to create an empty order and add the first product;
-
if so, youâve already ordered something, and you need to either increment the product quantity or add the product separately.
Now, you need to return the current status of the order and save it back to the database.
Return an array of responses back to the bot. Check what other responses you can return from the webhook.
router
.route('/order-summary')
.post(async (req, res) => {
const sessionId = req.version === 1 ? req.body.sessionId : req.body.chatId;
const doc = db.doc(sessionId);
const products = await doc.get();
// get order
let order = products.data().products || [];
let responses = [];
if (req.version == 2) {
responses = [
{
type: 'text',
message: 'Your order summary:'
},
{
type: 'text',
message: transformOrderToText(order)
}
];
} else {
responses = [
{
type: 'text',
elements: ['Your order summary:']
},
{
type: 'text',
elements: [transformOrderToText(order)]
}
];
}
res.json({ responses });
});
This action is similar to the end part of our previous function. At the start, you open the transaction to the database, collect the order, and then return the right answer to the bot.
router
.route('/start-again')
.post(async (req, res) => {
// get the sessionId
const sessionId = req.version === 1 ? req.body.sessionId : req.body.chatId;
try {
db.doc(sessionId).delete();
} catch (e) {
next(e);
}
res.json();
});
Here you only open the connection to your database, return an empty array to save it to your database, and clear the current order.
app.use(router);
exports.restaurantBot = functions.https.onRequest((req, res) => {
if (!req.path) {
req.url = `/${req.url}`;
}
return app(req, res);
});
Import template
Start using the Restaurant Bot template now to automate taking orders and making reservations.
Read more: