Support your restaurant communication with a customizable chatbot template. You can perform many actions with this pre-designed bot flow. The main features are:
-
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.
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. - Other entities available in the template are
foodType
andtableNumber
. - System entities such as
Any
,Number
, andEmail
help you efficiently collect users’ data. For example, the Number entity validates responses saved to the custom attributeproductQuantity
.
- The bot knows the restaurant’s menu thanks to the
-
Filters - by using them you can 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 - the order is handed to the backend through this block. Follow the steps below to set up your webhook and replace the one in the template when you’re ready.
How the backend works
We’ve prepared a simple backend to add a product to your order, display the current status, and start the process again. You can 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 adds a product to an order, which will be stored in the Firebase Realtime Database.
Initially, 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 in your database and store the order using the sessionID 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);
});
Start using the Restaurant Bot template now to automate taking orders and making reservations.
Read more: