Назад к новостям для разработчиков

Создание бэкенда Node.js для бронирования встреч с помощью WhatsApp Flows

27 февраля 2024 г.Автор:Гафи Дж (Gafi G) и Ирина Вагнер (Iryna Wagner)

С помощью WhatsApp Flows можно создавать интерактивные сообщения, чтобы пользователи могли выполнять действия прямо в WhatsApp. Сценарии Flows позволяют создавать интерактивные экраны для пользователей. Например, вы можно создавать формы с простым вводом данных для сбора лидов или отправки отзывов. Кроме того, можно создавать сложные сценарии с несколькими экранами для бронирования встреч.

В этом руководстве описано, как создать приложение Node.js, с помощью которого пользователи смогут бронировать встречи через WhatsApp Flows. Вы создадите сценарий на платформе WhatsApp Business, а затем настроите Webhooks для получения ответов сценария и бронирования встреч.

Предварительные требования

Прежде чем начинать, убедитесь, что вы:

Создание сценария WhatsApp Flows

Есть два способа создать сценарий WhatsApp Flows: с помощью Flow Builder в WhatsApp Manager или Flows API. В этом руководстве будет использоваться Flow Builder.

Создание сценария

В меню в левой части панели WhatsApp Manager выберите Инструменты для управления аккаунтом и нажмите Сценарии.

WhatsApp Manager: изображение

Нажмите Создать сценарий в правом верхнем углу экрана.

Создание сценария: изображение

В появившемся диалоговом окне заполните сведения о сценарии бронирования:

  • Название — введите Бронирование_встреч или любое другое название.
  • Категории — выберите Запись.
  • Шаблон — выберите Забронировать встречу. Вы будете использовать шаблон, так как он содержит необходимые элементы: экраны для сведений о встрече, ввода информации пользователя, сводки о бронировании и условия компании. Вы можете настроить шаблон в соответствии со своими потребностями.
Бронирование встречи: изображение

Нажмите Отправить, чтобы создать сценарий.

Вы можете предварительно просмотреть сценарий в правой части пользовательского интерфейса Flow Builder. На экране встречи пользователь может указать такие сведения о встрече, как местоположение и дата проведения, а на экране информации — ввести свои данные. На экране сводки приводится краткая информация о бронировании встречи, а на последнем — условия компании.

На время редактирования сценарий остается в черновой версии. Вы можете поделиться ею с командой, но пока только для тестирования. Чтобы ваш сценарий увидела большая аудитория, его нужно опубликовать. Учтите, что опубликованный сценарий нельзя отредактировать. В этот сценарий бронирования ещё предстоит добавить URL конечной точки. Поэтому пока оставим его как черновик и перейдем к следующему шагу, где вы настроите конечную точку.

Настройка конечной точки сценария

WhatsApp Flows позволяет подключиться к внешней конечной точке. Эта точка может предоставлять динамические данные для вашего сценария, контролировать маршрутизацию и получать от пользователей ответы, отправленные в сценарии.

Для возможности тестирования конечная точка в этой статье будет развернута в Glitch, но это необязательно для работы со сценариями. Вы можете скопировать код конечной точки из GitHub и запустить его из любой среды.

Откройте конечную точку в Glitch и создайте ее форк, чтобы получить свой уникальный домен. Чтобы создать форк, нажмите Remix ("Создать форк") в верхней части страницы. В качестве заполнителя в элементе ввода в правой части страницы Glitch появится уникальный домен.

Но сначала рассмотрим код. В каталоге src доступно четыре файла JavaScript: encryption.js, flow.js, keyGenerator.js и server.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> назначенной парольной фразой. Чтобы перейти к терминалу Glitch, откройте вкладку TERMINAL ("ТЕРМИНАЛ") в нижней части страницы.

node src/keyGenerator.js <your-passphrase>

Скопируйте парольную фразу и закрытый ключ и вставьте их в файл .env. Нажмите на файл .env на панели в левой части экрана и выберите Plain text ✏️ ("Простой текст") в верхней части экрана. Не редактируйте текст прямо из пользовательского интерфейса, так как это нарушит форматирование ключей.

После настройки переменных среды скопируйте сгенерированный открытый ключ и загрузите его через Graph API.

Файл server.js также содержит конечную точку POST, которая выполняет другие операции, а именно:

  • Проверяет наличие закрытого ключа.
       if (!PRIVATE_KEY) { throw new Error('Закрытый ключ пуст. Проверьте переменную "PRIVATE_KEY" конечной точки.'); }
  • Проверяет подпись запроса с помощью функции isRequestSignatureValid, расположенной в конце файла.
if(!isRequestSignatureValid(req)) { // При несоответствии подписи запроса возвращает код состояния 432. // Узнать больше о возвращаемых кодах ошибок можно по ссылке: https://developers.facebook.com/docs/whatsapp/flows/reference/error-codes#endpoint_error_codes return res.status(432).send(); }
  • Расшифровывает входящие сообщения с помощью функции decryptRequest, расположенной в файле encryption.js.
      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("Расшифрованный запрос 💬:", decryptedBody);
  • Решает, какой экран сценария показывать пользователю. Позже мы рассмотрим функцию getNextScreen подробнее.

const screenResponse = await getNextScreen(decryptedBody);

       console.log("Ответ для шифрования 👉:", screenResponse);
  • Шифрует ответ для отправки пользователю.
res.send(encryptResponse(screenResponse, aesKeyBuffer, initialVectorBuffer));

encryption.js

Этот файл содержит логику шифрования и расшифровки сообщений, которыми обмениваются в целях безопасности. В этом руководстве мы не будем на нем останавливаться.

keyGenerator.js

Этот файл помогает генерировать закрытый и открытый ключи в упомянутом выше порядке. Как и в случае с файлом encryption.js, в этом руководстве файл keyGenerator.js не рассматривается подробно.

flow.js

В этом файле находится логика обработки сценария. Файл начинается с объекта, которому присвоено имя SCREEN_RESPONSES. Этот объект содержит идентификаторы экранов с соответствующими сведениями, например предустановленными данными для обмена. Его можно создать во Flow Builder в разделе > Конечная точка > Фрагменты > Ответы. В этом объекте содержится другой идентификатор SUCCESS, который возвращается клиентскому устройству при успешном завершении сценария и закрывает сценарий.

Функция getNextScreen содержит логику, которая указывает конечной точке, какие данные сценария нужно показать пользователю. Она начинается с извлечения необходимых данных из расшифрованного сообщения.

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

Как правило, конечные точки WhatsApp Flows получают три запроса:

Узнать больше можно в документации по конечным точкам.

Функция отвечает за проверку работоспособности и уведомления об ошибках с помощью операторов if и реагирует, как показано во фрагменте ниже.

// handle health check request if (action === "ping") { return { version, data: { status: "active", }, }; } // handle error notification if (data?.error) { console.warn("Получена ошибка на стороне клиента:", data); return { version, data: { acknowledged: true, }, }; }
        

Когда пользователь нажимает кнопку призыва к действию сценария, активируется действие INIT. Оно возвращает экран встречи вместе с данными, а также отключает раскрывающиеся поля местоположения, даты и времени, пока пользователь не заполнит нужные поля.

Например, раскрывающееся поле даты становится доступным, только если заполнено раскрывающееся поле местоположения. Включение и отключение полей обрабатываются при получении запроса data_exchange.

// обрабатывает первичный запрос при открытии сценария и показывает экран встречи APPOINTMENT if (action === "INIT") { return { ...SCREEN_RESPONSES.APPOINTMENT, data: { ...SCREEN_RESPONSES.APPOINTMENT.data, // изначально эти поля отключены, пока не будет выбрано предыдущее поле is_location_enabled: false, is_date_enabled: false, is_time_enabled: false, }, }; }

Для действий data_exchange используется структура ветвления, позволяющая определить, какие данные нужно возвращать в зависимости от идентификатора экрана. Если идентификатор экрана — APPOINTMENT, раскрывающиеся поля включаются только при выборе предшествующих полей.

// Каждое поле включается, только если выбрано предыдущее поле 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} в ${locationName} ${dateName} в ${data.time}`; const details = `Имя: ${data.name} Электронный адрес: ${data.email} Телефон: ${data.phone} "${data.more_details}"`; return { ...SCREEN_RESPONSES.SUMMARY, data: { appointment, details, // возвращает те же поля, которые возвратил клиент для отправки на следующем шаге ...data, }, };
        

После отправки экрана SUMMARY клиентом на клиентское устройство отправляется ответ, отмечающий успешное завершение сценария. Маркер flow_token — это уникальный идентификатор, который можно настроить при отправке сценария пользователю.

// отправляет ответ об успешном выполнении, чтобы завершить и закрыть сценарий return { ...SCREEN_RESPONSES.SUCCESS, data: { extension_message_response: { params: { flow_token, }, }, }, };

На экране условий TERMS нет данных для обмена, поэтому конечная точка не обрабатывает его.

Добавление конечной точки в сценарий

Вы можете скопировать URL из правого верхнего угла страницы Glitch, нажав значок c тремя вертикальными точками и выбрав Copy Link ("Копировать ссылку"). Вы также можете нажать Share ("Поделиться") в правом верхнем углу экрана, чтобы получить эту ссылку.

Перейдите во Flow Editor ("Редактор сценария") и нажмите Set up ("Настроить") на коричневом баннере, который появится в верхней части редактора.

Вы увидите всплывающее окно, где сможете настроить URI конечной точки, номер телефона компании и приложение на сайте Meta for Developers. Внеся нужные изменения, проведите проверку работоспособности. Сперва запустите интерактивный предпросмотр и выберите Request data ("Запросить данные") в разделе Request data on first screen ("Запрос данных на первом экране") в настройках интерактивного предпросмотра. Это отправит в конечную точку запрос на получение данных для первого экрана. Система должна подтвердить, что конечная точка доступна, а вы внедрили проверку работоспособности.

Затем опубликуйте сценарий, нажав значок с тремя горизонтальными точками () и выбрав Publish ("Опубликовать"). Так вы отправите конечной точке запрос проверки работоспособности со значением action === "ping", чтобы подтвердить ее готовность к публикации.

Конечная точка: изображение

Тестирование сценария

По завершении настройки снова откройте интерактивный предпросмотр в интерфейсе WhatsApp Flow Builder, чтобы протестировать сценарий. В появившемся всплывающем окне выберите номер телефона и нажмите Request data ("Запросить данные") в разделе Request data on first screen ("Запрос данных на первом экране"). Нажмите значок X, чтобы закрыть сценарий и снова протестировать его с использованием кнопки призыва к действию.

Откройте журнал Glitch на вкладке LOGS ("ЖУРНАЛЫ") и нажмите Clear ("Очистить"), чтобы очистить его. Затем вернитесь к предпросмотру интерфейса WhatsApp Flow Builder и выберите Preview flow ("Предпросмотр сценария"). Вы увидите что-то наподобие следующего:

Предпросмотр сценария: изображение

Теперь вернитесь к журналам Glitch. Под расшифрованным запросом вы увидите действие INIT, маркер сценария и другие сведения. Здесь также приведен ответ для шифрования, возвращенный в сценарий пользователя после раскрытия поля подразделения.

Расшифрованный запрос: изображение

Выберите подразделение. Обратите внимание, что для параметра is_location_enabled задано значение true, а действие изменилось на data_exchange.

Обмен данными:изображение

Продолжайте тестировать сценарий и наблюдайте за изменениями данных в журналах Glitch. Похожие журналы будут создаваться, когда пользователи начнут взаимодействовать со сценарием с мобильных устройств.

В следующем разделе вы создадите веб-перехватчик Webhooks, который будет отправлять сообщение с подтверждением пользователям, которые забронируют встречу.

Настройка веб-перехватчика Webhooks

Когда пользователь завершает сценарий, на подписанный веб-перехватчик Webhooks отправляется соответствующее сообщение. Отсюда вы уведомите пользователя об успешном бронировании встречи в виде сообщения в чате. Как и с конечной точкой, вы проведете тестирование с помощью Glitch. Перейти к коду и создать его форк можно здесь.

Использование Glitch необязательно для работы со сценариями. Вы можете скопировать код Webhooks из GitHub и запустить его из любой среды.

Настройка переменных среды

Чтобы настроить переменные среды, откройте файл .env в Glitch. Настройте параметры VERIFY_TOKEN для нужной строки, FLOW_ID с идентификатором вашего сценария и GRAPH_API_TOKEN для маркера доступа вашего аккаунта WhatsApp Business. Вы можете найти маркер доступа на панели вашего приложения на сайте Meta for Developers, если нажмете Настройка API под пунктом WhatsApp на панели навигации в левой части экрана.

Настройка API: изображение

На открывшейся странице нажмите Копировать под карточкой Временный маркер доступа и вставьте этот ключ в файл .env.

Подписка веб-перехватчика Webhooks на панели Meta

В своем аккаунте на сайте Meta for Developers нажмите меню Конфигурация под пунктом WhatsApp на панели навигации в левой части экрана.

Настройка: изображение

На карточке Webhook нажмите Редактировать. В открывшемся диалоговом окне вставьте URL, скопированный из Glitch, и добавьте к нему приставку /webhook в поле URL обратного вызова. В поле Маркер подтверждения добавьте маркер из переменной VERIFY_TOKEN файла .env. Когда всё будет готово, нажмите Подтвердить и сохранить. Диалоговое окно закроется, а вы вернетесь на главный экран. Нажмите Управлять и проверьте поле messages. Ваш веб-перехватчик Webhooks готов.

Порядок работы с кодом Webhooks

Код содержит два маршрута: POST /webhook и GET /webhook. Маршрут GET обрабатывает запросы на проверку веб-перехватчика Webhooks. В процессе он сверяет предоставленный маркер с предопределенным маркером подтверждения и отвечает соответствующими кодами состояния и маркером вызова.

const verify_token = process.env.VERIFY_TOKEN; // Анализирует параметры из запроса подтверждения веб-перехватчика Webhooks 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 обрабатывает входящие уведомления Webhooks. У запросов Webhooks могут быть разные полезные данные. Например, приведенный ниже код считывает сообщение и номер телефона компании, получая безопасный доступ к полям запроса на случай, если они не определены.

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", который содержит слово "встреча". Если да, сценарий отправляется пользователю. Сообщение сценария отправляется с параметром flow_action: "data_exchange". Это означает, что при запуске этот сценарий отправит конечной точке запрос INIT для получения начального экрана и данных.

if ( message.type === "text" && // в целях демонстрации отправляйте сообщение сценария всякий раз, когда пользователь отправляет сообщение с упоминанием слова "встреча" message.text.body.toLowerCase().includes("встреча") ) { // 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: "Здравствуйте! 👋", }, body: { text: "Готовы преобразовать свое пространство? Запланируйте индивидуальную консультацию с нашей командой специалистов!", }, footer: { text: "Чтобы продолжить, нажмите кнопку ниже", }, action: { name: "flow", parameters: { flow_id: FLOW_ID, flow_message_version: "3", // замените маркер flow_token уникальным идентификатором сообщения сценария, чтобы отслеживать его в конечной точке и веб-перехватчике Webhooks flow_token: "<FLOW_TOKEN_PLACEHOLDER>", flow_cta: "Забронировать встречу", flow_action: "data_exchange", }, }, }, }, }); } ...

Если тип входящего сообщения отличается от "text", код проверяет, не относится ли он к типу "interactive"nfm_reply". Интерактивный тип " означает, что входящее сообщение — это ответ сценария. Затем система отправляет пользователю сообщение "Вы успешно забронировали встречу".

... if ( message.type === "interactive" && message.interactive?.type === "nfm_reply" ) { // отправляет сообщение с подтверждением 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: "Вы успешно забронировали встречу" }, }, }); } ...

Затем входящее сообщение отмечается как прочитанное, а пользователь видит синие галочки.

... // отмечает входящее сообщение как прочитанное 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, }, }); ...
        

Взаимодействие с пользователем

В этом примере пользователь отправляет на ваш номер сообщение, содержащее слово "встреча", а затем получает сообщение сценария. Вы также можете отправить сценарий после другого взаимодействия или в виде шаблона сообщения.

Пользователь получит сообщение сценария с кнопкой призыва забронировать встречу, где он сможет заполнить свои данные. После завершения сценария он получит сообщение с подтверждением.

Отправка сценария пользователю: изображение

Из этого руководства вы узнали, как настроить сценарий WhatsApp Flows для бесшовного бронирования встреч. С помощью пользовательского интерфейса Flow Builder вы создали форму для сбора сведений о бронировании у пользователей.

Со сценариями Flows можно не перенаправлять пользователей на внешний сайт для бронирования встреч и повысить качество обслуживания клиентов. Благодаря простоте процесса пользователи могут записываться на встречи прямо в WhatsApp. WhatsApp Flows можно использовать не только для планирования встреч, но и сбора отзывов о качестве обслуживании либо помощи пользователям в подписке на промоакции или рассылки. Также WhatsApp Flows предлагает гибкость в подключении внешних API или других приложений в вашей конечной точке.

Пользовательский интерфейс Flow Builder упрощает создание сценариев WhatsApp Flows, а Flow API позволяет вносить изменения прямо в код сценариев. Узнать больше можно в документации по WhatsApp Flows.