鍵.png)
使用NestJS和Prisma構(gòu)建REST API:身份驗證
在此屏幕上,選擇WhatsApp并單擊其設(shè)置按鈕。
然后您將進入一個新屏幕,如下所示。
在此屏幕上,請注意:
1184643492312754
EAAQ1bU6LdrIBA
……+15550253483
113362588047543
102432872486730
請注意,臨時訪問令牌將在 24 小時后過期,屆時我們需要更新它。當您的應用程序進入實時模式后,可以申請獲取永久訪問令牌。但由于我們的應用程序目前仍處于開發(fā)模式,因此這一步并非必需。
電話號碼 ID 和 WhatsApp 企業(yè)帳戶 ID 與測試電話號碼綁定。
接下來,我們添加一個用于接收消息的電話號碼。
在開發(fā)模式下,為了防范垃圾郵件和濫用行為,Meta對我們設(shè)置了限制,即只能向五個收件人號碼發(fā)送信息。在實時/生產(chǎn)模式下,該數(shù)字代表我們客戶的電話號碼。
單擊選擇收件人電話號碼并添加您自己的 WhatsApp 號碼,如下圖所示:
添加收件人號碼后,您將看到如下所示的屏幕。如果這是您第一次將電話號碼添加到 Meta 平臺(例如 Facebook Pages、Meta Business 套件或 Meta 開發(fā)人員儀表板),您將收到來自 Facebook Business 的 OTP 消息,提示您驗證您是否確實擁有收件人號碼。
讓我們測試一下到目前為止這一步是否一切順利。我們將通過單擊“發(fā)送消息”按鈕來完成此操作。
如果一切順利,您應該會在 WhatsApp 收件箱中看到一條來自您的測試號碼的消息。
到目前為止,我們進行得很順利!暫停一下并打開代碼編輯器。請勿關(guān)閉瀏覽器選項卡,因為我們將在幾分鐘后返回 Meta Developer 儀表板。
現(xiàn)在我們的設(shè)置已經(jīng)能夠成功發(fā)送消息了,接下來讓我們來設(shè)置一種接收消息的方式。是時候親自動手,深入編寫代碼了。我們?yōu)楸窘坛叹帉懙乃写a都位于此 GitHub 存儲庫中。
創(chuàng)建一個新文件夾來包含我們的項目。在終端中打開此文件夾并運行以下腳本:
npm init ---yes
接下來,我們安裝一些軟件包:
npm install express pdfkit request whatsappcloudapi_wrapper
npm install nodemon --dev
以下是每項的簡要說明:
express
這個軟件包對于設(shè)置我們的服務(wù)器至關(guān)重要,因為服務(wù)器上將會包含一個作為我們webhook的路由。pdfkit
包將用于在客戶結(jié)帳時為他們生成發(fā)票request
包將幫助我們運行對 FakeStoreAPI 的獲取請求whatsappcloudapi_wrapper
幫助我們發(fā)送和接收 WhatsApp 消息接下來,我們將創(chuàng)建三個文件:
./app.js
./.env.js
./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
文件中:
Meta_WA_accessToken
的值替換為您的元應用程序的臨時訪問令牌Meta_WA_SenderPhoneNumberId
的值替換為您的電話號碼 IDMeta_WA_wabaId
的值替換為您的 WhatsApp Business 帳戶 IDMeta_WA_VerifyToken
設(shè)定一個專屬的值,這個值可以是字符串或數(shù)字。在后續(xù)的webhook設(shè)置步驟中,您將會看到我們?nèi)绾芜\用這個值。上述代碼首先導入了當前的環(huán)境變量并進行了解構(gòu)處理,接著添加了新的環(huán)境變量,最終將這兩部分組合起來導出為一個對象。
在文件./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 服務(wù)器將在端口 9000 上運行。接下來,打開另一個單獨的終端并運行:
ngrok http 9000
此命令的作用是將我們的Express應用程序?qū)ν忾_放,使其能夠被更廣泛的互聯(lián)網(wǎng)用戶所訪問。這里的目標是設(shè)置一個 WhatsApp Cloud 可以 ping 通的 Webhook。
記下 ngrok 分配給您的 Express 服務(wù)器的 URL。在我的示例中,ngrok為我提供了一個如下的URL:https://7b9b-102-219-204-54.ngrok.io
。請確保Express服務(wù)器和ngrok終端都處于運行狀態(tài)。
接下來,讓我們在 Meta Developer 儀表板中繼續(xù)我們的工作。滾動到標題為“配置 Webhooks 以接收消息”的部分,然后單擊“配置 Webhooks”。該鏈接將顯示一個類似于下面屏幕截圖的頁面:
單擊“編輯”按鈕,將顯示一個彈出窗口。
在“回調(diào) URL”字段中,粘貼 ngrok 向您發(fā)出的 URL,并將其附加到回調(diào)路由,如./routes/index.js
指令中所示。在本例中,我的完整 URL 是https://7b9b-102-219-204-54.ngrok.io/meta_wa_callbackurl
。
在驗證令牌字段中,輸入文件中顯示的Meta_WA_VerifyToken的值./.env.js
。
然后點擊驗證并保存。
如果您配置良好,您將console.log
在 Express 服務(wù)器的終端中看到一條消息:
GET: Someone is pinging me!
現(xiàn)在,讓我們的 Express 服務(wù)器接收來自 Meta 的訂閱消息。
在同一個 Meta Developers 儀表板屏幕上,單擊“管理”,將出現(xiàn)一個彈出窗口。
選擇消息并單擊位于同一行的測試。
您應該console.log
在 Express 服務(wù)器的終端中看到一條消息:
POST: Someone is pinging me!
如果您看到此消息,請返回同一彈出窗口,然后單擊同一消息行中的“訂閱” 。然后,單擊“完成”。
首先,我們將設(shè)置相關(guān)邏輯,以便從FakeStore API獲取數(shù)據(jù),進而生成PDF格式的發(fā)票,并創(chuàng)建一個虛擬的訂單提貨地點。我們將把這個邏輯包裝到一個 JavaScript 類中,然后將其導入到應用程序的邏輯中。
創(chuàng)建一個文件并命名./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)
];
}
};
上面的代碼中,我們創(chuàng)建了一個名為EcommerceStore
的類。
第一個方法_fetchAssistant
用于向fakestoreapi.com
的特定端點發(fā)送ping請求。
以下方法充當?shù)谝粋€方法的查詢構(gòu)建器:
getProductById
接收產(chǎn)品 ID,然后獲取與該特定產(chǎn)品相關(guān)的數(shù)據(jù)getAllCategories
獲取fakestoreapi.com中的所有類別getProductsInCategory
接收一個產(chǎn)品類別,然后繼續(xù)獲取該特定類別中的所有產(chǎn)品這些查詢構(gòu)建器將調(diào)用第一個方法。
接著,generatePDFInvoice
這個方法會接收一段文本信息以及一個文件保存路徑作為輸入?yún)?shù)。然后,它創(chuàng)建一個 PDF 文檔,在其上寫入文本,然后將該文檔存儲在提供的文件路徑中。
該方法generateRandomGeoLocation
只是返回一個隨機地理位置。當我們需要把商店的訂單提貨位置信息發(fā)送給打算前來提貨的客戶時,這個方法將會非常實用。
為了管理客戶的整個購物流程,我們需要維護一個會話,其中包含客戶的個人信息及其購物車內(nèi)容。這樣,每位客戶都會擁有一個專屬的會話。
在生產(chǎn)中,我們可以使用 MySQL、MongoDB 或其他彈性數(shù)據(jù)庫,但為了保持我們的教程精簡和簡短,我們將使用ES2015 的Map
數(shù)據(jù)結(jié)構(gòu)。通過Map
,我們可以存儲和檢索特定的、可迭代的數(shù)據(jù),例如唯一的客戶數(shù)據(jù)。
在您的./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
類,第二行初始化它。第三行創(chuàng)建客戶的會話,我們將用它來存儲客戶的旅程。
還記得我們之前安裝的whatsappcloudapi_wrapper
軟件包嗎?現(xiàn)在是時候?qū)⑵鋵氩⑦M行初始化了。
在該./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
文件中定義的環(huán)境變量:
process.env.Meta_WA_accessToken
process.env.Meta_WA_SenderPhoneNumberId
process.env.Meta_WA_wabaId
我們使用上面的三個值初始化類 WhatsAppCloudAPI 并命名我們的實例Whatsapp
。
接下來,我們來解析通過POST方法發(fā)送到/meta_wa_callbackurl這個Webhook的所有數(shù)據(jù)。通過解析請求正文,我們將能夠提取消息和其他詳細信息,例如發(fā)件人的姓名、發(fā)件人的電話號碼等。
請注意:我們從此時起所做的所有代碼編輯都將完全在
./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
}
現(xiàn)在,當客戶向我們發(fā)送消息時,我們的 Webhook 應該會收到它。該消息包含在 Webhook 的請求正文中。為了從請求正文中提取出有用的信息,我們需要將這份正文內(nèi)容傳遞給parseMessageWhatsApp
實例的相應方法進行處理。
然后,使用if
語句檢查該方法的結(jié)果是否包含有效的 WhatsApp 消息。
在語句內(nèi)if
,我們定義incomingMessage
,其中包含消息。我們還定義了其他變量:
recipientPhone
是向我們發(fā)送消息的客戶的號碼。我們將向他們發(fā)送消息回復,因此前綴“收件人”recipientName
是向我們發(fā)送消息的客戶的姓名。這是他們在 WhatsApp 個人資料中為自己設(shè)置的名稱typeOfMsg
是客戶發(fā)送給我們的消息類型。正如我們接下來會看到的那樣,有些消息僅僅是簡單的文本內(nèi)容,而有些則是對按鈕點擊的回應(別擔心,很快您就會明白這一切的意義所在!)message_id
是唯一標識我們收到的消息的字符串。當我們想要執(zhí)行特定于該消息的任務(wù)(例如將消息標記為已讀)時,這非常有用。到目前為止,一切似乎都在順利進行中,但我們很快就會進行確認。
由于我們的教程不會深入探討任何形式的人工智能或自然語言處理 (NLP),因此我們將用簡單的if…else
邏輯定義聊天流程。
當客戶發(fā)送短信時,對話邏輯開始。我們暫時不會深入查看消息的具體內(nèi)容,因此也無法確切知道用戶打算做什么。但我們可以向用戶介紹我們的機器人具備哪些功能。
讓我們?yōu)榭蛻籼峁┮粋€簡單的上下文,他們可以根據(jù)特定的意圖進行回復。我們會給客戶兩個按鈕:
為此,請在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
方法允許我們向客戶發(fā)送按鈕。記下title
和id
屬性。這個title
是客戶將會看到的內(nèi)容,我們會利用它來判斷客戶點擊了哪一個帶有特定id
的按鈕。
讓我們來檢查一下,看看我們是否做得正確。打開您的 WhatsApp 應用程序并向 WhatsApp 企業(yè)帳戶發(fā)送短信。
如果您收到如上面的屏幕截圖所示的回復,那么恭喜您!您剛剛通過 WhatsApp Cloud API 發(fā)送了第一條消息。
由于客戶可以單擊兩個按鈕中的任何一個,因此我們還要處理“與人工交談”按鈕。
在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',
},
],
},
});
}
};
上面的代碼執(zhí)行兩個操作:
sendText
使用方法發(fā)送短信告訴用戶他們將收到聯(lián)系人卡片sendContact
使用該方法發(fā)送名片此代碼還使用用戶單擊的按鈕的 ID(在我們的示例中,ID 是incomingMessage.button_reply.id
)來檢測用戶的意圖,然后用兩個操作選項進行響應。
現(xiàn)在,返回 WhatsApp 并點擊與人交談。如果您操作正確,您將看到如下所示的回復:
當您單擊收到的聯(lián)系人卡片時,您應該會看到以下內(nèi)容:
接下來,我們來處理“查看某些產(chǎn)品”按鈕。
在simple_button_message
的if
語句內(nèi)部,但在該if
語句的下方、且在speak_to_human
的if
語句外部,請?zhí)砑右韵麓a:
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)
});
}
這是上面代碼的作用:
if
語句確保用戶單擊“查看某些產(chǎn)品”按鈕FakeStoreAPI
通過getAllCategories
方法獲取產(chǎn)品類別slice(0,3)
— 因為 WhatsApp 只允許我們發(fā)送三個簡單的按鈕atitle
)的按鈕。再次返回您的 WhatsApp 應用程序并點擊查看更多產(chǎn)品。如果您正確執(zhí)行了上述步驟,您應該會看到類似于下面屏幕截圖的回復:
現(xiàn)在,讓我們創(chuàng)建邏輯來獲取客戶選擇的類別中的產(chǎn)品。
仍在simple_button_message
的if
語句內(nèi)部,但在該語句的下方、且在see_categories
的if
語句外部,添加以下代碼:
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發(fā)起查詢,以搜索屬于該特定類別的產(chǎn)品。
查詢后,我們收到數(shù)組內(nèi)的產(chǎn)品列表listOfProducts.data
。現(xiàn)在,我們循環(huán)遍歷該數(shù)組,對于其中的每個產(chǎn)品,我們提取其價格、標題、描述和 ID。
我們附加product_
到id
,這將幫助我們在下一步中選擇客戶的選擇。請確保根據(jù)WhatsApp Cloud API對于單選按鈕(或列表)的ID、標題和描述的長度限制,對它們進行適當?shù)牟眉艋蛘{(diào)整。
然后我們返回三個值:ID、標題和描述。由于 WhatsApp 最多只允許 10 行,因此我們將使用數(shù)組方法將產(chǎn)品數(shù)量限制為 10 行。
之后,我們調(diào)用該sendRadioButtons
方法將產(chǎn)品發(fā)送給客戶。記下屬性headerText
、bodyText
、footerText
和listOfSections
。
返回 WhatsApp 應用程序并單擊任意產(chǎn)品類別。如果您嚴格按照說明進行操作,那么您應該會收到一個與下面屏幕截圖相似的回復:
當您單擊“選擇產(chǎn)品”時,您應該看到以下屏幕:
在這個時刻,客戶可以挑選他們感興趣的產(chǎn)品,但我們能否知曉他們的選擇呢?答案是目前還不能。因此,接下來讓我們一同探究這部分內(nèi)容。
在該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
語句內(nèi)的正下方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',
},
],
});
}
上面的代碼執(zhí)行以下操作:
emojiRating
。如果評分為 3.8,則會呈現(xiàn)三顆星表情符號sendImage
方法會將產(chǎn)品的圖像作為附件添加到要發(fā)送的文本中,并通過相應的方式發(fā)送出去。之后,我們使用 sendSimpleButtons
向客戶發(fā)送三個按鈕的列表。客戶有機會將產(chǎn)品添加到購物車。記下前綴為 add_to_cart
的按鈕 ID 。
現(xiàn)在,返回您的 WhatsApp 應用程序并選擇一個產(chǎn)品。如果您嚴格按照說明進行操作,那么您應該會收到一個與下面屏幕截圖相似的回復:
為了追蹤客戶添加到購物車中的商品,我們需要一個存儲空間來保存這些購物車物品的信息。這就是CustomerSession
發(fā)揮作用的地方。讓我們?yōu)槠涮砑右恍┻壿嫛?/p>
在該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...
上述代碼會檢查是否已經(jīng)為客戶創(chuàng)建了會話。如果尚未創(chuàng)建,則會根據(jù)客戶的電話號碼為其生成一個新的、唯一的會話。然后我們初始化一個名為 的屬性cart
,它最初是一個空數(shù)組。
該addToCart
函數(shù)接受 一個product_id
和特定客戶的號碼。然后,它對 FakeStoreAPI 執(zhí)行 ping 操作以獲取特定產(chǎn)品的數(shù)據(jù),并將產(chǎn)品推送到cart
數(shù)組中。
然后,該listOfItemsInCart
函數(shù)接收客戶的電話號碼并檢索關(guān)聯(lián)的cart
,用于計算購物車中的產(chǎn)品數(shù)量及其價格總和。最后,它返回購物車中的商品及其總價。
該clearCart
函數(shù)接收客戶的電話號碼并清空該客戶的購物車。完成購物車邏輯后,讓我們構(gòu)建“添加到購物車”按鈕。
在該simple_button_message
的if
語句內(nèi)部,并且在聲明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',
},
],
});
}
上面的代碼從客戶單擊的按鈕中提取產(chǎn)品 ID,然后調(diào)用該addToCart
函數(shù)將產(chǎn)品保存到客戶會話的購物車中。然后,它提取客戶會話購物車中的商品數(shù)量,并告訴客戶他們有多少產(chǎn)品。它還提供了兩個按鈕供用戶選擇,其中一個按鈕允許用戶進行結(jié)賬操作。
記下按鈕 ID 并返回您的 WhatsApp 應用程序。單擊添加到購物車。如果您很好地遵循了說明,您應該會看到類似于以下屏幕截圖的回復:
現(xiàn)在我們的客戶已經(jīng)可以把商品添加到購物車了,接下來我們可以著手編寫結(jié)賬的邏輯。
在simple_button_message if
語句內(nèi)部但在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 });
}
上面的代碼執(zhí)行以下操作:
finalBill
invoiceText
,其中包含我們將發(fā)送給客戶的文本以及將起草到 PDF 版本發(fā)票中的文本generatePDFInvoice
這個方法(與我們之前在EcommerceStore
類中定義的方法一樣)會接收訂單的詳細信息,然后基于這些信息起草一份PDF文檔,并將這份文檔保存在我們指定的本地目錄或文件夾的文件路徑中。sendText
方法向客戶發(fā)送一條包含訂單詳細信息的簡單文本消息sendSimpleButtons
向客戶發(fā)送一些按鈕。記下“打印我的發(fā)票”按鈕及其 IDclearCart
方法清空購物車現(xiàn)在,切換回您的 WhatsApp 應用程序并點擊“結(jié)賬”。如果您很好地遵循了說明,您將看到類似于以下屏幕截圖的回復:
此時,客戶應該會收到可打印的 PDF 發(fā)票。因此,接下來讓我們深入探討一下與“打印我的發(fā)票”按鈕相關(guān)的邏輯處理。
在simple_button_message if
語句內(nèi)部但在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',
});
}
上面的代碼從本地文件系統(tǒng)中獲取上一步生成的PDF文檔,并使用該sendDocument
方法將其發(fā)送給客戶。
當客戶在線訂購產(chǎn)品時,他們還需要知道如何收到實物產(chǎn)品。因此,我們利用EcommerceStore
類中的generateRandomGeoLocation
方法來生成一些隨機的地理坐標,并通過sendLocation
方法將這些坐標信息發(fā)送給客戶,以便他們了解可以在哪里實際提取所購買的產(chǎn)品。
現(xiàn)在,打開您的 WhatsApp 應用程序并點擊打印我的發(fā)票。如果您正確遵循了上述說明,您應該會看到類似于以下屏幕截圖的回復:
最后,您可能已經(jīng)注意到消息下方的復選標記是灰色的,而不是藍色的。這表明我們發(fā)送的消息并沒有收到已讀回執(zhí),盡管我們的機器人實際上已經(jīng)讀取了這些消息。
灰色的勾號可能會讓客戶感到不滿或失望,因此我們應該盡力確保顯示的是藍色的勾號,以給客戶帶來更好的體驗和信心。
在simple_button_message
語句外部和if
語句的右大括號之前,添加以下代碼:
await Whatsapp.markMessageAsRead({ message_id });
我們一旦回復了消息,這條簡短的單行文字就會被系統(tǒng)自動標記為已讀狀態(tài)。
現(xiàn)在,打開您的 WhatsApp 應用程序并發(fā)送隨機短信。
如果您之前的聊天記錄已更新為藍色勾號,那么 ?? 恭喜您!您已經(jīng)完成了本教程,并在此過程中學到了一些東西。
每月活躍用戶總數(shù)高達20億,如果忽視將WhatsApp納入您的電子商務(wù)策略,那么您的企業(yè)很可能會在競爭中處于不利地位。既然您的大多數(shù)客戶在日常活動中都在使用WhatsApp,那么您的企業(yè)為什么不也應該在那里與他們見面呢?
我希望本教程對揭開 WhatsApp Cloud API 的神秘面紗有所幫助,并希望您在此過程中獲得一些樂趣。
原文鏈接:https://dev.to/logrocket/build-an-automated-ecommerce-app-with-whatsapp-cloud-api-and-nodejs-5g3a