返回開發人員最新消息

透過 WhatsApp Flows 安排預約:建立 Node.js 後端

2024年2月27日發佈者:Gafi G 和 Iryna Wagner

您可以運用 WhatsApp Flows 來建立互動式訊息,以便用戶直接在 WhatsApp 完成各項動作。您可以透過 Flows 流程建立各種用戶互動畫面。舉例來說,您可以建立簡單的輸入表格,用來蒐集潛在顧客資料或讓用戶提交評論。此外,您亦可以設計複雜的 Flows 流程,讓用戶透過多個畫面安排預約。

本指南會逐步介紹如何建立 Node.js 應用程式,方便用戶透過 WhatsApp Flows 安排預約。您需要在 WhatsApp Business 平台建立 Flows 流程,然後配置 Webhook,才能收取相關 Flows 流程的回應和安排預約。

必要條件

若要按照本教學導覽操作,請確保您具備下列條件:

建立 WhatsApp Flows 流程

您可透過兩種方式來建立 WhatsApp Flows 流程:一種是可透過 WhatsApp 管理工具存取的 Flows 建造工具,另一種則是 Flows API。本教學導覽會使用 Flows 建造工具。

建立 Flows 流程

在 WhatsApp 管理工具的管理中心左側選單中,選擇「帳戶工具」。然後,點擊「流程」。

圖像:WhatsApp 管理工具

點擊右上角的「建立流程」。

圖像:建立 Flows 流程

在畫面顯示的對話框中,填寫預約 Flows 流程的詳細資訊:

  • 名稱:輸入「預約」,或選擇其他您喜歡的名稱。
  • 類別:選擇「預約」。
  • 範本:選擇「安排預約」。您需要使用此範本,因為當中包含安排預約所需的各種元素。這些元素包括顯示以下內容的不同畫面:預約詳情、用戶詳細資訊輸入欄位、預約摘要和公司條款。您可以根據自己的使用案例,進一步自訂範本。
圖像:安排預約

點擊「提交」以建立 Flows 流程。

您可以在建造工具用戶介面的右側預覽 Flows 流程。在預約畫面,用戶可選擇預約詳細資訊,如位置和日期;在詳細資訊畫面,用戶可中輸入個人資訊;摘要畫面會顯示預約摘要;最後一個畫面會顯示公司的條款。

Flows 流程會在您編輯時維持草稿狀態。您目前可向團隊分享 Flows 流程,但僅供測試之用。若要向廣大受眾分享 Flows 流程,您需要先發佈此流程。不過,Flows 流程一經發佈便無法編輯。由於您仍需要就此預約 Flows 流程加入端點網址,因此請暫時將其保留為草稿狀態,然後繼續執行下一步,也就是配置端點。

配置 Flows 流程的端點

您可以在 WhatsApp Flows 連結外部端點。此端點可就 Flows 流程提供動態資料並控制資料路由,也可從 Flows 流程接收用戶提交的回應。

本文使用 Glitch 代管端點以作測試。使用 Glitch 與否完全由您自行決定,這並非使用 Flows 流程的必要條件。您可以從 GitHub 複製端點程式碼,並在您所選的任何環境中加以執行。

在 Glitch 中取得端點程式碼,並將其混合配搭,以產生您自己的不重複網域。若要混合配搭端點程式碼,請點擊頁面頂端的「Remix」,然後便會有不重複的網域以預留位置形式出現在 Glitch 頁面右側的輸入元素中。

繼續之前,先來了解一下程式碼構成。src 目錄中有 4 個 JavaScript 檔案:encryption.jsflow.jskeyGenerator.jsserver.js。其中 server.js 是入門檔案;我們先來看看這個檔案。

server.js

server.js 檔案會在一開始配置 Express 應用程式,以使用 express.json 中介軟件來剖析傳入的 JSON 要求。然後,此檔案會載入端點所需的環境變數。

const { APP_SECRET, PRIVATE_KEY, PASSPHRASE, PORT = "3000" } = process.env;

APP_SECRET 會用於簽名驗證,有助您檢查訊息是否透過 WhatsApp 傳送,以確保相關訊息是否安全而可繼續處理。您需要在 .env 檔案中加入此變數。

若要取得您的 APP_SECRET,請在 Meta for Developers 前往您的應用程式管理中心。在「應用程式設定」下方的左側導覽面板中,選擇「基本」。點擊「應用程式密鑰」下方的「顯示」,然後複製密鑰。接著,返回 Glitch 並開啟 .env 檔案,然後建立一個名為 APP_SECRET 的變數,當中輸入您複製的密鑰值。

PRIVATE_KEY 可助您將收到的訊息解密。PASSPHRASE 會用於驗證私人密鑰。除了私人密鑰,您亦需要相應的公開密鑰,以便在稍後上載。切勿在此處使用您正式版帳戶的私人密鑰。請建立臨時的私人密鑰,以用於在 Glitch 測試,然後在您自己的基礎架構中將其換成正式版帳戶的密鑰。

在 Glitch 終端機中執行下方指令,以產生公開和私人密鑰組合。將 <your-passphrase> 換成您指定的密碼。點擊頁面底部的「TERMINAL」分頁,以存取 Glitch 終端機。

node src/keyGenerator.js <your-passphrase>

複製密碼和私人密鑰,並將其貼至 .env 檔案。點擊左側邊欄中標註為「.env」的檔案,然後點擊頂端的「✏️ Plain text」。請勿直接在用戶介面編輯檔案,否則會破壞您的密鑰格式

設定環境變數後,請複製您產生的公開密鑰,並透過 Graph API 上載此公開密鑰

server.js 檔案亦包含執行其他不同步驟的 POST 端點:

  • 檢查是否有私人密鑰:
       if (!PRIVATE_KEY) { throw new Error('Private key is empty.Please check your env variable "PRIVATE_KEY".'); }
  • 使用檔案底部的 isRequestSignatureValid 函數,驗證要求簽名:
if(!isRequestSignatureValid(req)) { // Return status code 432 if request signature does not match. // To learn more about return error codes visit: https://developers.facebook.com/docs/whatsapp/flows/reference/error-codes#endpoint_error_codes return res.status(432).send(); }
  • 使用 encryption.js 檔案中的 decryptRequest 函數,將傳入的訊息解密:
      let decryptedRequest = null; try { decryptedRequest = decryptRequest(req.body, PRIVATE_KEY, PASSPHRASE); } catch (err) { console.error(err); if (err instanceof FlowEndpointException) { return res.status(err.statusCode).send(); } return res.status(500).send(); } const { aesKeyBuffer, initialVectorBuffer, decryptedBody } = decryptedRequest; console.log("💬 Decrypted Request:", decryptedBody);
  • 決定要向用戶顯示哪個 Flows 畫面。我們之後會再詳細講解 getNextScreen 函數。

const screenResponse = await getNextScreen(decryptedBody);

       console.log("👉 Response to Encrypt:", screenResponse);
  • 將要傳送給用戶的回應加密:
res.send(encryptResponse(screenResponse, aesKeyBuffer, initialVectorBuffer));

encryption.js

此檔案包含基於安全目的而將收發的訊息加密和解密的邏輯。本教學導覽不會重點介紹此檔案的運作方式。

keyGenerator.js

如前所述,此檔案可協助產生私人和公開密鑰。與 encryption.js 檔案一樣,本教學導覽也不會詳細講解 keyGenerator.js 檔案。

flow.js

此檔案包含處理 Flows 流程的邏輯,其開首是名為「SCREEN_RESPONSES」的物件。此物件包含畫面編號及其相應的詳細資訊,如資料交換中使用的預設資料。此物件是在 Flows 流程建造工具的「」>「端點」>「片段」>「回應」之下產生。在同一個物件中,您亦會有另一個編號 SUCCESS。當 Flows 流程順利完成後,系統便會將此編號會傳回用戶端裝置,然後隨之關閉 Flows 流程。

getNextScreen 函數中所含的邏輯會引導端點來決定向用戶顯示哪些 Flows 流程資料。首先,此函數會從已解密的訊息中擷取所需資料。

const { screen, data, version, action, flow_token } = decryptedBody;

WhatsApp Flows 端點通常會收到 3 個要求:

您可以查閱端點文件,以進一步了解以上要求。

此函數會使用 if 陳述式來處理系統健康檢查和錯誤通知,並且作出相應回應,如下方程式碼片段所示:

// handle health check request if (action === "ping") { return { version, data: { status: "active", }, }; } // handle error notification if (data?.error) { console.warn("Received client error:", data); return { version, data: { acknowledged: true, }, }; }
        

當用戶點擊 Flows 流程的呼籲字句按鈕時,便會觸發 INIT 動作。觸發此動作後,系統會將預約畫面與相關資料一併傳回。系統亦會停用位置、日期和時間下拉式選單,以確保用戶填寫所有欄位。

例如,只有當用戶填妥位置下拉式選單後,系統才會啟用日期下拉式選單。系統會在收到 data_exchange 要求後處理欄位的啟用和停用。

// handle initial request when opening the flow and display APPOINTMENT screen if (action === "INIT") { return { ...SCREEN_RESPONSES.APPOINTMENT, data: { ...SCREEN_RESPONSES.APPOINTMENT.data, // these fields are disabled initially.Each field is enabled when previous fields are selected is_location_enabled: false, is_date_enabled: false, is_time_enabled: false, }, }; }

如果是 data_exchange 動作,系統會使用「switch case」架構,以根據畫面編號決定傳回哪些資料。如果畫面編號是 APPOINTMENT,則只有當用戶已選擇上一個下拉式欄位時,系統才會啟用下一個下拉式欄位。

// Each field is enabled only when previous fields are selected is_location_enabled: Boolean(data.department), is_date_enabled: Boolean(data.department) && Boolean(data.location), is_time_enabled: Boolean(data.department) && Boolean(data.location) && Boolean(data.date)

如果是 DETAILS 畫面,系統便會從 SCREEN_RESPONSES.APPOINTMENT.data 物件擷取位置和部門等資料物件屬性的標題。這段程式碼會假設存在有效的配對,因此請注意,如果系統找不到可配對的物件,可能會擲出錯誤。

我們現以位置物件為例。系統會將陣列中物件的 id 屬性與 data.location 值配對,據此選擇具體的位置物件。

const departmentName = SCREEN_RESPONSES.APPOINTMENT.data.department.find( (dept) => dept.id === data.department ).title; const locationName = SCREEN_RESPONSES.APPOINTMENT.data.location.find( (loc) => loc.id === data.location ).title; const dateName = SCREEN_RESPONSES.APPOINTMENT.data.date.find( (date) => date.id === data.date

).title;

系統隨後會將這些值串連並在回應中傳回,以顯示 SUMMARY 畫面。

const appointment = `${departmentName} at ${locationName} ${dateName} at ${data.time}`; const details = `Name: ${data.name} Email: ${data.email} Phone: ${data.phone} "${data.more_details}"`; return { ...SCREEN_RESPONSES.SUMMARY, data: { appointment, details, // return the same fields sent from client back to submit in the next step ...data, }, };
        

收到用戶端提交的 SUMMARY 畫面後,系統會向用戶端裝置傳送成功回應,以將相關 Flows 流程標示為完成。flow_token 是不重複的識別資料,您可以在向用戶傳送 Flows 流程時加以設定。

// send success response to complete and close the flow return { ...SCREEN_RESPONSES.SUCCESS, data: { extension_message_response: { params: { flow_token, }, }, }, };

TERMS 畫面沒有需要交換的資料,因此端點不會處理此畫面。

在 Flows 流程中加入端點

在 Glitch 頁面的右上方,您可以點擊垂直 3 點選單圖示並選擇「Copy Link」,即可複製相關網址。您亦可以點擊右上方的「分享」來取得連結。

前往 Flows 流程編輯工具。在編輯工具頂端顯示的啡色橫額中,點擊「設定」。

系統隨即會顯示彈出視窗,以便您配置端點 URI、商家電話號碼和 Meta for Developers 上的應用程式。完成必要配置後,請執行系統健康檢查。首先,執行互動式預覽,並確保在互動式預覽設定的「在第一個畫面要求資料」下方選擇「要求資料」。這樣系統便會向端點傳送要求以檢索第一個畫面的資料,從而驗證端點是否有效,以及您是否已執行系統健康檢查。

然後,點擊 3 點()選單並選擇「發佈」,以發佈相關 Flows 流程。這樣系統便會使用 action === "ping" 向您的端點傳送系統健康檢查要求,以在發佈前驗證端點是否設定妥當。

圖像:端點

測試 Flows 流程

完成配置後,在 WhatsApp 建造工具用戶介面再次切換至互動式預覽,測試 Flows 流程。在畫面顯示的彈出視窗中,選擇相關電話號碼,然後選擇「在第一個畫面要求資料」下方的「要求資料」。點擊「X」圖示以關閉 Flows 流程,然後使用呼籲字句按鈕開始重新測試 Flows 流程。

點擊「LOGS」分頁,以開啟 Glitch 記錄。點擊「Clear」來清除記錄。然後,返回 WhatsApp 建造工具用戶介面預覽。點擊「預覽流程」。您會看到如下畫面:

圖像:預覽流程

現在,返回 Glitch 記錄。您會在已解密的要求下方看到 INIT 動作、Flows 流程憑證和其他詳細資訊。待用戶選擇部門下拉式選單後,系統亦會向用戶 Flows 流程傳回需要加密的回應。

圖像:已解密的要求

繼續選擇部門。留意系統如何將 is_location_enabled 設定為 true,您會發現有關動作已變為 data_exchange

圖像:data_exchange

繼續測試相關 Flows 流程,並觀察 Glitch 記錄中的資料變更。當用戶透過流動裝置與 Flows 流程互動時,系統會產生類似的記錄。

在下一部分,您需要建立 Webhook,用於在用戶預約時向其傳送確認訊息。

設定 Webhook

當用戶完成 Flows 流程後,將會有訊息傳送至您訂閱的 Webhook,表示流程已完成。透過此 Webhook,您將會向用戶傳送聊天室訊息,通知其預約成功。與端點類似,您亦將使用 Glitch 測試 Webhook。您可以在此取得程式碼並加以混合配搭

使用 Glitch 與否完全由您自行決定,這並非使用 Flows 流程的必要條件。您可以從 GitHub 複製 Webhook 程式碼,並在您所選的任何環境中加以執行。

設定環境變數

若要設定環境變數,請在 Glitch 中開啟 .env 檔案。將 VERIFY_TOKEN 設定為您所選的任何字串,以您的 Flows 流程編號設定 FLOW_ID,並將 GRAPH_API_TOKEN 設定為您 WhatsApp Business 帳戶的存取憑證。您可以在左側導覽面板的「WhatsApp」區塊下方點擊「API 設定」,前往您 Meta for Developers 應用程式的管理中心取得存取憑證。

圖像:API 設定

在系統顯示的頁面中,點擊「臨時存取憑證」小卡片下方的「複製」按鈕。在您的 .env 檔案中貼上密鑰。

在 Meta 管理中心訂閱 Webhook

前往您的 Meta for Developers 帳戶,然後在左側導覽面板中的「WhatsApp」下方點擊「配置」選單。

圖像:配置

在「Webhook」小卡片中,點擊「編輯」。在系統開啟的對話框中貼上您複製的 Glitch 網址,然後在「回調網址」欄位中為此網址加上 /webhook。在「驗證憑證」欄位,加入 .env 檔案中 VERIFY_TOKEN 變數所提供的憑證。完成後,點擊「驗證並儲存」。系統隨即會關閉對話框並返回主畫面。點擊「管理」並檢查「訊息」欄位。您的 Webhook 現已準備就緒。

Webhook 程式碼詳細說明

此程式碼包含 2 條路徑:POST /webhookGET /webhookGET 路徑會處理 Webhook 驗證要求,方法是檢查所提供的憑證與預先定義的驗證憑證是否相符,並以相應的狀態代碼和挑戰憑證作出回應。

const verify_token = process.env.VERIFY_TOKEN; // Parse params from the webhook verification request let mode = req.query["hub.mode"]; let token = req.query["hub.verify_token"]; let challenge = req.query["hub.challenge"]; if (mode && token) { if (mode === "subscribe" && token === verify_token) { console.log("WEBHOOK_VERIFIED"); res.status(200).send(challenge); } else { res.sendStatus(403); } }

POST /webhook 路徑會處理傳入的 Webhook 通知。Webhook 要求的裝載各有不同。因此,下方的程式碼會先以安全的方式存取要求欄位,然後再讀取訊息和商家電話號碼,以防相關欄位未經定義。

const message = req.body.entry?.[0]?.changes[0]?.value?.messages?.[0]; const business_phone_number_id =

req.body.entry?.[0].changes?.[0].value?.metadata?.phone_number_id;

此程式碼隨後會檢查傳入的要求是否屬於 "text" 訊息類型,以及當中是否包含「appointment」(預約)一詞。如果訊息中含有此詞,系統便會向用戶傳送相關 Flows 流程。系統會使用 flow_action: "data_exchange" 來傳送 Flows 流程訊息,這表示 Flows 流程會在啟動時向端點傳送 INIT 要求,以取得初始畫面和資料。

if ( message.type === "text" && // for demo purposes, send the flow message whenever a user sends a message containing "appointment" message.text.body.toLowerCase().includes("appointment") ) { // send flow message as per the docs here https://developers.facebook.com/docs/whatsapp/flows/gettingstarted/sendingaflow#interactive-message-parameters await axios({ method: "POST", url: `https://graph.facebook.com/v18.0/${business_phone_number_id}/messages`, headers: { Authorization: `Bearer ${GRAPH_API_TOKEN}`, }, data: { messaging_product: "whatsapp", to: message.from, type: "interactive", interactive: { type: "flow", header: { type: "text", text: "Hello there 👋", }, body: { text: "Ready to transform your space?Schedule a personalized consultation with our expert team!", }, footer: { text: "Click the button below to proceed", }, action: { name: "flow", parameters: { flow_id: FLOW_ID, flow_message_version: "3", // replace flow_token with a unique identifier for this flow message to track it in your endpoint & webhook flow_token: "<FLOW_TOKEN_PLACEHOLDER>", flow_cta: "Book an appointment", flow_action: "data_exchange", }, }, }, }, }); } ...

如果傳入的訊息類型不是 "text",此程式碼便會檢查訊息類型是否為 "interactive"。互動式類型 "nfm_reply" 表示傳入的訊息為 Flows 流程回應。然後,系統會向用戶傳回「You’ve successfully booked an appointment」(您已成功完成預約)訊息。

... if ( message.type === "interactive" && message.interactive?.type === "nfm_reply" ) { // send confirmation message await axios({ method: "POST", url: `https://graph.facebook.com/v18.0/${business_phone_number_id}/messages`, headers: { Authorization: `Bearer ${GRAPH_API_TOKEN}`, }, data: { messaging_product: "whatsapp", to: message.from, text: { body: "You've successfully booked an appointment" }, }, }); } ...

接著,系統會將這則傳入的訊息標示為已讀,以便用戶看到藍色剔號。

... // mark incoming message as read await axios({ method: "POST", url: `https://graph.facebook.com/v18.0/${business_phone_number_id}/messages`, headers: { Authorization: `Bearer ${GRAPH_API_TOKEN}`, }, data: { messaging_product: "whatsapp", status: "read", message_id: message.id, }, }); ...
        

用戶體驗

在此範例中,用戶向您的號碼傳送含有「appointment」(預約)一詞的訊息,隨後便收到 Flows 流程訊息。您亦可以選擇在其他互動之後傳送 Flows 流程,或以訊息範本形式傳送此流程。

用戶將收到一則 Flows 流程訊息,當中含有用於預約的呼籲字句按鈕,以便用戶填寫自己的詳細資訊。接著,當用戶完成 Flows 流程後,他們將會收到確認訊息。

圖像:向用戶傳送 Flows 流程

本指南介紹了如何設定 WhatsApp Flows 流程,以提供順暢無阻的預約體驗。您可以運用 Flows 流程建造工具用戶介面來製作所需表格,以蒐集用戶的預約詳細資訊。

有了 Flows 流程,您就不再需要將用戶重新導向外部網站來安排預約,有助改善顧客體驗。新的程序非常簡單直接,用戶可直接在 WhatsApp 中完成預約。除了安排預約,您亦可以使用 WhatsApp Flows 來蒐集顧客服務意見,或協助用戶報名參加推廣活動或加入訂閱電郵名單。WhatsApp Flows 亦提供靈活選項,讓您連結外部 API 或自家端點中的其他應用程式。

您可以在 Flows 流程建造工具用戶介面輕鬆建立 WhatsApp Flows 流程,也可以使用 Flow API 以編程方式建立。如需進一步了解詳細,請參閱 WhatsApp Flows 文件