所有文章 > API使用场景 > 使用 WhatsApp Cloud API 和 Node.js 构建自动化电子商务应用程序

使用 WhatsApp Cloud API 和 Node.js 构建自动化电子商务应用程序

2022 年 5 月,Meta(该公司前身为 Facebook,旗下拥有 WhatsApp)宣布向公众开放 WhatsApp Business API。本文旨在引领您进入Meta所带来的奇妙世界,在那里,WhatsApp聊天机器人能够助您一臂之力,包括生成潜在客户、接收订单、安排预约、开展调查、收集客户反馈、提供灵活多变的客户支持服务,以及发送发票和收据等。

先决条件

在继续深入探讨之前,本文基于以下假设:

  • 您有一个有效的元开发者帐户。
  • 您精通 JavaScript 和 Node.js
  • 你已经安装了ngrok

第 1 步:在 Meta Developer 仪表板上配置我们的应用程序

要使用Meta的任何API,首先需要在Meta仪表板上免费创建一个应用程序。

  • 登录您的 Meta 开发者帐户
  • 单击创建应用程序
  • 在接下来的屏幕中,选择应用程序类型
选择业务应用程序类型
  • 接下来,请填写您的应用程序名称和电子邮件地址,然后选择您希望与此应用程序相关联的页面或业务。
命名应用程序并选择与其关联的页面
  • 提交表单后,您将进入如下屏幕:
选择 WhatsApp 设置

在此屏幕上,选择WhatsApp并单击其设置按钮。

然后您将进入一个新屏幕,如下所示。

入门页面

在此屏幕上,请注意:

  • App ID,这是与我们的 Meta 应用程序关联的 ID。我的是1184643492312754
  • 临时访问令牌 24小时后过期。我的开始于EAAQ1bU6LdrIBA……
  • 测试电话号码 ,我们将使用它向客户发送消息。我的是+15550253483
    • 电话号码ID。我的是113362588047543
    • WhatsApp企业帐户 ID。我的是102432872486730

请注意,临时访问令牌将在 24 小时后过期,届时我们需要更新它。当您的应用程序进入实时模式后,可以申请获取永久访问令牌。但由于我们的应用程序目前仍处于开发模式,因此这一步并非必需。

电话号码 ID 和 WhatsApp 企业帐户 ID 与测试电话号码绑定。

接下来,我们添加一个用于接收消息的电话号码。

在开发模式下,为了防范垃圾邮件和滥用行为,Meta对我们设置了限制,即只能向五个收件人号码发送信息。在实时/生产模式下,该数字代表我们客户的电话号码。

单击选择收件人电话号码并添加您自己的 WhatsApp 号码,如下图所示:

添加收件人电话号码对话框

添加收件人号码后,您将看到如下所示的屏幕。如果这是您第一次将电话号码添加到 Meta 平台(例如 Facebook Pages、Meta Business 套件或 Meta 开发人员仪表板),您将收到来自 Facebook Business 的 OTP 消息,提示您验证您是否确实拥有收件人号码。

使用 API 发送消息

测试我们的设置

让我们测试一下到目前为止这一步是否一切顺利。我们将通过单击“发送消息”按钮来完成此操作。

如果一切顺利,您应该会在 WhatsApp 收件箱中看到一条来自您的测试号码的消息。

WhatsApp 收件箱中来自 Facebook 的 Hello World 消息

到目前为止,我们进行得很顺利!暂停一下并打开代码编辑器。请勿关闭浏览器选项卡,因为我们将在几分钟后返回 Meta Developer 仪表板。

第 2 步:设置 webhook 来接收消息

现在我们的设置已经能够成功发送消息了,接下来让我们来设置一种接收消息的方式。是时候亲自动手,深入编写代码了。我们为本教程编写的所有代码都位于此 GitHub 存储库中。

创建一个新文件夹来包含我们的项目。在终端中打开此文件夹并运行以下脚本:

npm init ---yes

接下来,我们安装一些软件包:

npm install express pdfkit request whatsappcloudapi_wrapper
npm install nodemon --dev

以下是每项的简要说明:

  • express这个软件包对于设置我们的服务器至关重要,因为服务器上将会包含一个作为我们webhook的路由。
  • pdfkit包将用于在客户结帐时为他们生成发票
  • request包将帮助我们运行对 FakeStoreAPI 的获取请求
  • whatsappcloudapi_wrapper帮助我们发送和接收 WhatsApp 消息

接下来,我们将创建三个文件:

  1. ./app.js
  2. ./.env.js
  3. ./routes/index.js

在我们的./.env.js文件中,输入以下代码:

const production = {
...process.env,
NODE_ENV: process.env.NODE_ENV || 'production',
};

const development = {
...process.env,
NODE_ENV: process.env.NODE_ENV || 'development',
PORT: '9000',
Meta_WA_accessToken:'EAAKGUD3eZA28BADAJOmO6L19TmZAIEUpdFGHEGHX5sQ3kk4LDQLlnyh607rKSr0x2SAOPJS0kXOUZAhRDIPPGs4vcXQCo2DnfSJMnnIzFW7vaw8EuL7A0HjGZBwE8VwjRnBNam0ARLmfyOCEh1',
Meta_WA_SenderPhoneNumberId: '113362588047543',
Meta_WA_wabaId: '102432872486730',
Meta_WA_VerifyToken: 'YouCanSetYourOwnToken',
};

const fallback = {
...process.env,
NODE_ENV: undefined,
};

module.exports = (environment) => {
console.log(`Execution environment selected is: "${environment}"`);
if (environment === 'production') {
return production;
} else if (environment === 'development') {
return development;
} else {
return fallback;
}
};

在同一个./.env.js文件中:

  1. Meta_WA_accessToken的值替换为您的元应用程序的临时访问令牌
  2. Meta_WA_SenderPhoneNumberId的值替换为您的电话号码 ID
  3. Meta_WA_wabaId的值替换为您的 WhatsApp Business 帐户 ID
  4. 为您的Meta_WA_VerifyToken设定一个专属的值,这个值可以是字符串或数字。在后续的webhook设置步骤中,您将会看到我们如何运用这个值。

上述代码首先导入了当前的环境变量并进行了解构处理,接着添加了新的环境变量,最终将这两部分组合起来导出为一个对象。

在文件./app.js文件中,插入以下代码:

process.env = require('./.env.js')(process.env.NODE_ENV || 'development');
const port = process.env.PORT || 9000;
const express = require('express');

let indexRoutes = require('./routes/index.js');

const main = async () => {
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use('/', indexRoutes);
app.use('*', (req, res) => res.status(404).send('404 Not Found'));
app.listen(port, () =>
console.log(`App now running and listening on port ${port}`)
);
};
main();

上面代码块的第一行只是导入./.env.js文件并将其分配给process.env,这是 Node.js 中的全局可访问对象。

在文件中./routes/index.js,插入以下代码:

'use strict';
const router = require('express').Router();

router.get('/meta_wa_callbackurl', (req, res) => {
try {
console.log('GET: Someone is pinging me!');

let mode = req.query['hub.mode'];
let token = req.query['hub.verify_token'];
let challenge = req.query['hub.challenge'];

if (
mode &&
token &&
mode === 'subscribe' &&
process.env.Meta_WA_VerifyToken === token
) {
return res.status(200).send(challenge);
} else {
return res.sendStatus(403);
}
} catch (error) {
console.error({error})
return res.sendStatus(500);
}
});

router.post('/meta_wa_callbackurl', async (req, res) => {
try {
console.log('POST: Someone is pinging me!');
return res.sendStatus(200);
} catch (error) {
console.error({error})
return res.sendStatus(500);
}
});
module.exports = router;

接下来,打开终端并运行:

nodemon app.js

Express 服务器将在端口 9000 上运行。接下来,打开另一个单独的终端并运行:

ngrok http 9000

此命令的作用是将我们的Express应用程序对外开放,使其能够被更广泛的互联网用户所访问。这里的目标是设置一个 WhatsApp Cloud 可以 ping 通的 Webhook。

记下 ngrok 分配给您的 Express 服务器的 URL。在我的示例中,ngrok为我提供了一个如下的URL:https://7b9b-102-219-204-54.ngrok.io。请确保Express服务器和ngrok终端都处于运行状态。

接下来,让我们在 Meta Developer 仪表板中继续我们的工作。滚动到标题为“配置 Webhooks 以接收消息”的部分,然后单击“配置 Webhooks”。该链接将显示一个类似于下面屏幕截图的页面:

Webhook 配置页面

单击“编辑”按钮,将显示一个弹出窗口。

“回调 URL”字段中,粘贴 ngrok 向您发出的 URL,并将其附加到回调路由,如./routes/index.js指令中所示。在本例中,我的完整 URL 是https://7b9b-102-219-204-54.ngrok.io/meta_wa_callbackurl

验证令牌字段中,输入文件中显示的Meta_WA_VerifyToken的值./.env.js

在验证令牌字段中输入值

然后点击验证并保存

如果您配置良好,您将console.log在 Express 服务器的终端中看到一条消息:

GET: Someone is pinging me!

配置我们的 Express 服务器

现在,让我们的 Express 服务器接收来自 Meta 的订阅消息。

在同一个 Meta Developers 仪表板屏幕上,单击“管理”,将出现一个弹出窗口。

管理 Express 服务器订阅消息弹出窗口

选择消息并单击位于同一行的测试。

您应该console.log在 Express 服务器的终端中看到一条消息:

POST: Someone is pinging me!

如果您看到此消息,请返回同一弹出窗口,然后单击同一消息行中的“订阅” 。然后,单击“完成”

第三步:编写我们的业务逻辑

配置电子商务数据源

首先,我们将设置相关逻辑,以便从FakeStore API获取数据,进而生成PDF格式的发票,并创建一个虚拟的订单提货地点。我们将把这个逻辑包装到一个 JavaScript 类中,然后将其导入到应用程序的逻辑中。

创建一个文件并命名./utils/ecommerce_store.js。在此文件中,粘贴以下代码:

'use strict';
const request = require('request');
const PDFDocument = require('pdfkit');
const fs = require('fs');

module.exports = class EcommerceStore {
constructor() {}
async _fetchAssistant(endpoint) {
return new Promise((resolve, reject) => {
request.get(
`https://fakestoreapi.com${endpoint ? endpoint : '/'}`,
(error, res, body) => {
try {
if (error) {
reject(error);
} else {
resolve({
status: 'success',
data: JSON.parse(body),
});
}
} catch (error) {
reject(error);
}
}
);
});
}

async getProductById(productId) {
return await this._fetchAssistant(`/products/${productId}`);
}
async getAllCategories() {
return await this._fetchAssistant('/products/categories?limit=100');
}
async getProductsInCategory(categoryId) {
return await this._fetchAssistant(
`/products/category/${categoryId}?limit=10`
);
}

generatePDFInvoice({ order_details, file_path }) {
const doc = new PDFDocument();
doc.pipe(fs.createWriteStream(file_path));
doc.fontSize(25);
doc.text(order_details, 100, 100);
doc.end();
return;
}

generateRandomGeoLocation() {
let storeLocations = [
{
latitude: 44.985613,
longitude: 20.1568773,
address: 'New Castle',
},
{
latitude: 36.929749,
longitude: 98.480195,
address: 'Glacier Hill',
},
{
latitude: 28.91667,
longitude: 30.85,
address: 'Buena Vista',
},
];
return storeLocations[
Math.floor(Math.random() * storeLocations.length)
];
}
};

上面的代码中,我们创建了一个名为EcommerceStore的类。

第一个方法_fetchAssistant用于向fakestoreapi.com的特定端点发送ping请求。

以下方法充当第一个方法的查询构建器:

  1. getProductById接收产品 ID,然后获取与该特定产品相关的数据
  2. getAllCategories获取fakestoreapi.com中的所有类别
  3. getProductsInCategory接收一个产品类别,然后继续获取该特定类别中的所有产品

这些查询构建器将调用第一个方法。

接着,generatePDFInvoice这个方法会接收一段文本信息以及一个文件保存路径作为输入参数。然后,它创建一个 PDF 文档,在其上写入文本,然后将该文档存储在提供的文件路径中。

该方法generateRandomGeoLocation只是返回一个随机地理位置。当我们需要把商店的订单提货位置信息发送给打算前来提货的客户时,这个方法将会非常实用。

配置客户会话

为了管理客户的整个购物流程,我们需要维护一个会话,其中包含客户的个人信息及其购物车内容。这样,每位客户都会拥有一个专属的会话。

在生产中,我们可以使用 MySQLMongoDB 或其他弹性数据库,但为了保持我们的教程精简和简短,我们将使用ES2015 的Map数据结构。通过Map,我们可以存储和检索特定的、可迭代的数据,例如唯一的客户数据。

在您的./routes/index.js文件中,在 router.get('/meta_wa_callbackurl', (req, res)上方添加以下代码。

const EcommerceStore = require('./../utils/ecommerce_store.js');
let Store = new EcommerceStore();
const CustomerSession = new Map();

router.get('/meta_wa_callbackurl', (req, res) => {//this line already exists. Add the above lines

第一行导入EcommerceStore类,第二行初始化它。第三行创建客户的会话,我们将用它来存储客户的旅程。

初始化我们的 WhatsApp Cloud API

还记得我们之前安装的whatsappcloudapi_wrapper软件包吗?现在是时候将其导入并进行初始化了。

在该./routes/index.js文件中,在 Express 路由器声明下方添加以下代码行:

const router = require('express').Router(); // This line already exists. Below it add  the following lines:

const WhatsappCloudAPI = require('whatsappcloudapi_wrapper');
const Whatsapp = new WhatsappCloudAPI({
accessToken: process.env.Meta_WA_accessToken,
senderPhoneNumberId: process.env.Meta_WA_SenderPhoneNumberId,
WABA_ID: process.env.Meta_WA_wabaId,
});

以下值是我们在./.env.js文件中定义的环境变量:

  • process.env.Meta_WA_accessToken
  • process.env.Meta_WA_SenderPhoneNumberId
  • process.env.Meta_WA_wabaId

我们使用上面的三个值初始化类 WhatsAppCloudAPI 并命名我们的实例Whatsapp

接下来,我们来解析通过POST方法发送到/meta_wa_callbackurl这个Webhook的所有数据。通过解析请求正文,我们将能够提取消息和其他详细信息,例如发件人的姓名、发件人的电话号码等。

请注意:我们从此时起所做的所有代码编辑都将完全在./routes/index.js文件中进行。

在语句try{的左括号下方添加以下代码行:

try { // This line already exists. Add the below lines

let data = Whatsapp.parseMessage(req.body);

if (data?.isMessage) {
let incomingMessage = data.message;
let recipientPhone = incomingMessage.from.phone; // extract the phone number of sender
let recipientName = incomingMessage.from.name;
let typeOfMsg = incomingMessage.type; // extract the type of message (some are text, others are images, others are responses to buttons etc...)
let message_id = incomingMessage.message_id; // extract the message id
}

现在,当客户向我们发送消息时,我们的 Webhook 应该会收到它。该消息包含在 Webhook 的请求正文中。为了从请求正文中提取出有用的信息,我们需要将这份正文内容传递给parseMessageWhatsApp实例的相应方法进行处理。

然后,使用if语句检查该方法的结果是否包含有效的 WhatsApp 消息。

在语句内if,我们定义incomingMessage,其中包含消息。我们还定义了其他变量:

  • recipientPhone是向我们发送消息的客户的号码。我们将向他们发送消息回复,因此前缀“收件人”
  • recipientName是向我们发送消息的客户的姓名。这是他们在 WhatsApp 个人资料中为自己设置的名称
  • typeOfMsg是客户发送给我们的消息类型。正如我们接下来会看到的那样,有些消息仅仅是简单的文本内容,而有些则是对按钮点击的回应(别担心,很快您就会明白这一切的意义所在!)
  • message_id是唯一标识我们收到的消息的字符串。当我们想要执行特定于该消息的任务(例如将消息标记为已读)时,这非常有用。

到目前为止,一切似乎都在顺利进行中,但我们很快就会进行确认。

了解并响应客户的意图

由于我们的教程不会深入探讨任何形式的人工智能或自然语言处理 (NLP),因此我们将用简单的if…else逻辑定义聊天流程。

当客户发送短信时,对话逻辑开始。我们暂时不会深入查看消息的具体内容,因此也无法确切知道用户打算做什么。但我们可以向用户介绍我们的机器人具备哪些功能。

让我们为客户提供一个简单的上下文,他们可以根据特定的意图进行回复。我们会给客户两个按钮:

  1. 让我们知道他们想与真人交谈,而不是聊天机器人
  2. 另一个浏览产品

为此,请在message_id下面插入以下代码:

if (typeOfMsg === 'text_message') {
await Whatsapp.sendSimpleButtons({
message: `Hey ${recipientName}, \nYou are speaking to a chatbot.\nWhat do you want to do next?`,
recipientPhone: recipientPhone,
listOfButtons: [
{
title: "'View some products',"
id: 'see_categories',
},
{
title: "'Speak to a human',"
id: 'speak_to_human',
},
],
});
}

上面的if语句仅允许我们处理文本消息。

sendSimpleButtons方法允许我们向客户发送按钮。记下titleid属性。这个title是客户将会看到的内容,我们会利用它来判断客户点击了哪一个带有特定id的按钮。

让我们来检查一下,看看我们是否做得正确。打开您的 WhatsApp 应用程序并向 WhatsApp 企业帐户发送短信。

向 WhatsApp 企业帐户发送短信

如果您收到如上面的屏幕截图所示的回复,那么恭喜您!您刚刚通过 WhatsApp Cloud API 发送了第一条消息。

由于客户可以单击两个按钮中的任何一个,因此我们还要处理“与人工交谈”按钮。

if逻辑语句之外,插入以下代码:

if (typeOfMsg === 'simple_button_message') {
let button_id = incomingMessage.button_reply.id;

if (button_id === 'speak_to_human') {
await Whatsapp.sendText({
recipientPhone: recipientPhone,
message: `Arguably, chatbots are faster than humans.\nCall my human with the below details:`,
});

await Whatsapp.sendContact({
recipientPhone: recipientPhone,
contact_profile: {
addresses: [
{
city: 'Nairobi',
country: 'Kenya',
},
],
name: {
first_name: 'Daggie',
last_name: 'Blanqx',
},
org: {
company: 'Mom-N-Pop Shop',
},
phones: [
{
phone: '+1 (555) 025-3483',
},
{
phone: '+254712345678',
},
],
},
});
}
};

上面的代码执行两个操作:

  1. sendText使用方法发送短信告诉用户他们将收到联系人卡片
  2. sendContact使用该方法发送名片

此代码还使用用户单击的按钮的 ID(在我们的示例中,ID 是incomingMessage.button_reply.id)来检测用户的意图,然后用两个操作选项进行响应。

现在,返回 WhatsApp 并点击与人交谈。如果您操作正确,您将看到如下所示的回复:

发送

当您单击收到的联系人卡片时,您应该会看到以下内容:

联系卡显示全名、公司和两个电话号码

接下来,我们来处理“查看某些产品”按钮。

simple_button_messageif语句内部,但在该if语句的下方、且在speak_to_humanif语句外部,请添加以下代码:

if (button_id === 'see_categories') {
let categories = await Store.getAllCategories();
await Whatsapp.sendSimpleButtons({
message: `We have several categories.\nChoose one of them.`,
recipientPhone: recipientPhone,
listOfButtons: categories.data
.map((category) => ({
title: "category,"
id: `category_${category}`,
}))
.slice(0, 3)
});
}

这是上面代码的作用:

  1. if语句确保用户单击“查看某些产品”按钮
  2. FakeStoreAPI通过getAllCategories方法获取产品类别
  3. 使用数组方法将按钮数量限制为三个 — slice(0,3)— 因为 WhatsApp 只允许我们发送三个简单的按钮
  4. 然后,它会遍历每一个类别,为每个类别创建一个带有唯一ID(该ID以”category_”为前缀)和对应标题(atitle)的按钮。
  5. 通过该方法,我们将这些按钮发送给客户

再次返回您的 WhatsApp 应用程序并点击查看更多产品。如果您正确执行了上述步骤,您应该会看到类似于下面屏幕截图的回复:

发送

按类别获取产品

现在,让我们创建逻辑来获取客户选择的类别中的产品。

仍在simple_button_messageif语句内部,但在该语句的下方、且在see_categoriesif语句外部,添加以下代码:

if (button_id.startsWith('category_')) {
let selectedCategory = button_id.split('category_')[1];
let listOfProducts = await Store.getProductsInCategory(selectedCategory);

let listOfSections = [
{
title: "`🏆 Top 3: ${selectedCategory}`.substring(0,24),"
rows: listOfProducts.data
.map((product) => {
let id = `product_${product.id}`.substring(0,256);
let title = product.title.substring(0,21);
let description = `${product.price}\n${product.description}`.substring(0,68);

return {
id,
title: "`${title}...`,"
description: "`${description}...`"
};
}).slice(0, 10)
},
];

await Whatsapp.sendRadioButtons({
recipientPhone: recipientPhone,
headerText: `#BlackFriday Offers: ${selectedCategory}`,
bodyText: `Our Santa 🎅🏿 has lined up some great products for you based on your previous shopping history.\n\nPlease select one of the products below:`,
footerText: 'Powered by: BMI LLC',
listOfSections,
});
}

上面的语句确认客户单击的按钮确实是包含类别的按钮。

我们在这里做的第一件事是从按钮的 ID 中提取特定类别。随后,我们向FakeStoreAPI发起查询,以搜索属于该特定类别的产品。

查询后,我们收到数组内的产品列表listOfProducts.data。现在,我们循环遍历该数组,对于其中的每个产品,我们提取其价格、标题、描述和 ID。

我们附加product_id,这将帮助我们在下一步中选择客户的选择。请确保根据WhatsApp Cloud API对于单选按钮(或列表)的ID、标题和描述的长度限制,对它们进行适当的裁剪或调整。

然后我们返回三个值:ID、标题和描述。由于 WhatsApp 最多只允许 10 行,因此我们将使用数组方法将产品数量限制为 10 行。

之后,我们调用该sendRadioButtons方法将产品发送给客户。记下属性headerTextbodyTextfooterTextlistOfSections

返回 WhatsApp 应用程序并单击任意产品类别。如果您严格按照说明进行操作,那么您应该会收到一个与下面屏幕截图相似的回复:

选择电子产品类别并接收回复

当您单击“选择产品”时,您应该看到以下屏幕:

在这个时刻,客户可以挑选他们感兴趣的产品,但我们能否知晓他们的选择呢?答案是目前还不能。因此,接下来让我们一同探究这部分内容。

在该simple_button_message if语句之外,我们再添加一条if语句:

if (typeOfMsg === 'radio_button_message') {
let selectionId = incomingMessage.list_reply.id; // the customer clicked and submitted a radio button

}

在上述if语句内的正下方selectionId,添加以下代码:

if (selectionId.startsWith('product_')) {
let product_id = selectionId.split('_')[1];
let product = await Store.getProductById(product_id);
const { price, title, description, category, image: imageUrl, rating } = product.data;

let emojiRating = (rvalue) => {
rvalue = Math.floor(rvalue || 0); // generate as many star emojis as whole number ratings
let output = [];
for (var i = 0; i < rvalue; i++) output.push('⭐');
return output.length ? output.join('') : 'N/A';
};

let text = `_Title_: *${title.trim()}*\n\n\n`;
text += `_Description_: ${description.trim()}\n\n\n`;
text += `_Price_: ${price}\n`;
text += `_Category_: ${category}\n`;
text += `${rating?.count || 0} shoppers liked this product.\n`;
text += `_Rated_: ${emojiRating(rating?.rate)}\n`;

await Whatsapp.sendImage({
recipientPhone,
url: imageUrl,
caption: text,
});

await Whatsapp.sendSimpleButtons({
message: `Here is the product, what do you want to do next?`,
recipientPhone: recipientPhone,
listOfButtons: [
{
title: "'Add to cart🛒',"
id: `add_to_cart_${product_id}`,
},
{
title: "'Speak to a human',"
id: 'speak_to_human',
},
{
title: "'See more products',"
id: 'see_categories',
},
],
});
}

上面的代码执行以下操作:

  1. 从客户单击的单选按钮中提取产品 ID
  2. 使用该产品 ID 查询 FakeStoreAPI
  3. 当它接收并提取产品数据时,它会格式化文本。 WhatsApp 使用下划线以斜体显示文本,而星号以粗体显示文本
  4. 使用该函数渲染星星表情符号emojiRating。如果评分为 3.8,则会呈现三颗星表情符号
  5. sendImage方法会将产品的图像作为附件添加到要发送的文本中,并通过相应的方式发送出去。

之后,我们使用 sendSimpleButtons向客户发送三个按钮的列表。客户有机会将产品添加到购物车。记下前缀为 add_to_cart的按钮 ID 。

现在,返回您的 WhatsApp 应用程序并选择一个产品。如果您严格按照说明进行操作,那么您应该会收到一个与下面屏幕截图相似的回复:

聊天机器人向客户发送三个可选按钮

构建会话来存储客户购物车

为了追踪客户添加到购物车中的商品,我们需要一个存储空间来保存这些购物车物品的信息。这就是CustomerSession发挥作用的地方。让我们为其添加一些逻辑。

在该radio_button_message if语句之外的声明下方message_id,添加以下代码:

let message_id = incomingMessage.message_id; // This line already exists. Add the below lines...

// Start of cart logic
if (!CustomerSession.get(recipientPhone)) {
CustomerSession.set(recipientPhone, {
cart: [],
});
}

let addToCart = async ({ product_id, recipientPhone }) => {
let product = await Store.getProductById(product_id);
if (product.status === 'success') {
CustomerSession.get(recipientPhone).cart.push(product.data);
}
};

let listOfItemsInCart = ({ recipientPhone }) => {
let total = 0;
let products = CustomerSession.get(recipientPhone).cart;
total = products.reduce(
(acc, product) => acc + product.price,
total
);
let count = products.length;
return { total, products, count };
};

let clearCart = ({ recipientPhone }) => {
CustomerSession.get(recipientPhone).cart = [];
};
// End of cart logic

if (typeOfMsg === 'text_message') { ... // This line already exists. Add the above lines...

上述代码会检查是否已经为客户创建了会话。如果尚未创建,则会根据客户的电话号码为其生成一个新的、唯一的会话。然后我们初始化一个名为 的属性cart,它最初是一个空数组。

addToCart函数接受 一个product_id和特定客户的号码。然后,它对 FakeStoreAPI 执行 ping 操作以获取特定产品的数据,并将产品推送到cart数组中。

然后,该listOfItemsInCart函数接收客户的电话号码并检索关联的cart,用于计算购物车中的产品数量及其价格总和。最后,它返回购物车中的商品及其总价。

clearCart函数接收客户的电话号码并清空该客户的购物车。完成购物车逻辑后,让我们构建“添加到购物车”按钮。

在该simple_button_messageif语句内部,并且在声明button_id的下方,添加以下代码:

if (button_id.startsWith('add_to_cart_')) {
let product_id = button_id.split('add_to_cart_')[1];
await addToCart({ recipientPhone, product_id });
let numberOfItemsInCart = listOfItemsInCart({ recipientPhone }).count;

await Whatsapp.sendSimpleButtons({
message: `Your cart has been updated.\nNumber of items in cart: ${numberOfItemsInCart}.\n\nWhat do you want to do next?`,
recipientPhone: recipientPhone,
listOfButtons: [
{
title: "'Checkout 🛍️',"
id: `checkout`,
},
{
title: "'See more products',"
id: 'see_categories',
},
],
});
}

上面的代码从客户单击的按钮中提取产品 ID,然后调用该addToCart函数将产品保存到客户会话的购物车中。然后,它提取客户会话购物车中的商品数量,并告诉客户他们有多少产品。它还提供了两个按钮供用户选择,其中一个按钮允许用户进行结账操作。

记下按钮 ID 并返回您的 WhatsApp 应用程序。单击添加到购物车。如果您很好地遵循了说明,您应该会看到类似于以下屏幕截图的回复:

添加到购物车

现在我们的客户已经可以把商品添加到购物车了,接下来我们可以着手编写结账的逻辑。

编写结账逻辑

simple_button_message if语句内部但在add_to_cart_ if语句外部添加以下代码:

if (button_id === 'checkout') {
let finalBill = listOfItemsInCart({ recipientPhone });
let invoiceText = `List of items in your cart:\n`;

finalBill.products.forEach((item, index) => {
let serial = index + 1;
invoiceText += `\n#${serial}: ${item.title} @ ${item.price}`;
});

invoiceText += `\n\nTotal: ${finalBill.total}`;

Store.generatePDFInvoice({
order_details: invoiceText,
file_path: `./invoice_${recipientName}.pdf`,
});

await Whatsapp.sendText({
message: invoiceText,
recipientPhone: recipientPhone,
});

await Whatsapp.sendSimpleButtons({
recipientPhone: recipientPhone,
message: `Thank you for shopping with us, ${recipientName}.\n\nYour order has been received & will be processed shortly.`,
message_id,
listOfButtons: [
{
title: "'See more products',"
id: 'see_categories',
},
{
title: "'Print my invoice',"
id: 'print_invoice',
},
],
});

clearCart({ recipientPhone });
}

上面的代码执行以下操作:

  1. 获取购物车中的所有商品并放入其中finalBill
  2. 初始化一个变量invoiceText,其中包含我们将发送给客户的文本以及将起草到 PDF 版本发票中的文本
  3. generatePDFInvoice这个方法(与我们之前在EcommerceStore类中定义的方法一样)会接收订单的详细信息,然后基于这些信息起草一份PDF文档,并将这份文档保存在我们指定的本地目录或文件夹的文件路径中。
  4. sendText方法向客户发送一条包含订单详细信息的简单文本消息
  5. sendSimpleButtons向客户发送一些按钮。记下“打印我的发票”按钮及其 ID
  6. 最后该clearCart方法清空购物车

现在,切换回您的 WhatsApp 应用程序并点击“结账”。如果您很好地遵循了说明,您将看到类似于以下屏幕截图的回复:

单击结账按钮

此时,客户应该会收到可打印的 PDF 发票。因此,接下来让我们深入探讨一下与“打印我的发票”按钮相关的逻辑处理。

编写可打印的发票逻辑

simple_button_message if语句内部但在checkout if语句外部添加以下代码:

if (button_id === 'print_invoice') {
// Send the PDF invoice
await Whatsapp.sendDocument({
recipientPhone: recipientPhone,
caption:`Mom-N-Pop Shop invoice #${recipientName}`
file_path: `./invoice_${recipientName}.pdf`,
});

// Send the location of our pickup station to the customer, so they can come and pick up their order
let warehouse = Store.generateRandomGeoLocation();

await Whatsapp.sendText({
recipientPhone: recipientPhone,
message: `Your order has been fulfilled. Come and pick it up, as you pay, here:`,
});

await Whatsapp.sendLocation({
recipientPhone,
latitude: warehouse.latitude,
longitude: warehouse.longitude,
address: warehouse.address,
name: 'Mom-N-Pop Shop',
});
}

上面的代码从本地文件系统中获取上一步生成的PDF文档,并使用该sendDocument方法将其发送给客户。

当客户在线订购产品时,他们还需要知道如何收到实物产品。因此,我们利用EcommerceStore类中的generateRandomGeoLocation方法来生成一些随机的地理坐标,并通过sendLocation方法将这些坐标信息发送给客户,以便他们了解可以在哪里实际提取所购买的产品。

现在,打开您的 WhatsApp 应用程序并点击打印我的发票。如果您正确遵循了上述说明,您应该会看到类似于以下屏幕截图的回复:

发短信

向客户显示已读回执

最后,您可能已经注意到消息下方的复选标记是灰色的,而不是蓝色的。这表明我们发送的消息并没有收到已读回执,尽管我们的机器人实际上已经读取了这些消息。

灰色的勾号可能会让客户感到不满或失望,因此我们应该尽力确保显示的是蓝色的勾号,以给客户带来更好的体验和信心。

simple_button_message语句外部和if语句的右大括号之前,添加以下代码:

await Whatsapp.markMessageAsRead({ message_id });

我们一旦回复了消息,这条简短的单行文字就会被系统自动标记为已读状态。

现在,打开您的 WhatsApp 应用程序并发送随机短信。

留言

如果您之前的聊天记录已更新为蓝色勾号,那么 🎉 恭喜您!您已经完成了本教程,并在此过程中学到了一些东西。

结论

每月活跃用户总数高达20亿,如果忽视将WhatsApp纳入您的电子商务策略,那么您的企业很可能会在竞争中处于不利地位。既然您的大多数客户在日常活动中都在使用WhatsApp,那么您的企业为什么不也应该在那里与他们见面呢?

我希望本教程对揭开 WhatsApp Cloud API 的神秘面纱有所帮助,并希望您在此过程中获得一些乐趣。

原文链接:https://dev.to/logrocket/build-an-automated-ecommerce-app-with-whatsapp-cloud-api-and-nodejs-5g3a

#你可能也喜欢这些API文章!