«А поговорить?» или делаем звонок для подтверждения заказа в интернет магазине с помощью МТТ VoiceBox +1


Экономическая ситуация нынче нестабильная, лишних денег у людей нет. И вот значится обратились ко мне товарищи со словами: "Ты же когда-то там сайты делал, помоги разобраться". После такой просьбы пришлось мне сдувать пыль вековую с постаревших связей нейронных, да бубен шаманский доставать.

Но не переживайте, эта статья не о том, как я спустя 12 лет снова сел ковырять сайты на PHP. Всё это банально и писать об этом на Хабр я бы не стал.

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

Вы готовы загибать пальцы?

Итак, сегодня мы с вами:

  • Настроим сценарий голосового бота для подтверждения интернет заказа.

  • Одним глазом взглянем на API для управления ботом и отправим пару запросов через Postman.

  • Добавим пару кастомных действий в WooCommerce, чтобы робот вместо оператора разными голосами подтверждал заказ.

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

Также, я должен сделать привычный дисклеймер:
"Я техпис, а не программист. поэтому все программные решения в этой статье, скорее всего являются далеко не лучшими практиками".

Ну вот с формальностями покончили можем смело приступать к делу.
Статья получилась объемная поэтому я оставлю оглавление:

Настройка сценария в личном кабинете

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

Также я пропущу описание некоторых очевидных шагов. У разработчиков в Wiki есть инструкция по работе с личным кабинетом и я не буду её дублировать.

После входа в личный кабинет, перейдем на вкладку "Сценарии" и нажмем кнопку "Создание сценария"

Страница "Сценарии"
Страница "Сценарии"

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

Сверху диаграмма сразу после создания, снизу состояние при добавлении первого блока "Проигрыватель"
Сверху диаграмма сразу после создания, снизу состояние при добавлении первого блока "Проигрыватель"

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

Для меня стало некоторым неудобством то, что нельзя потянуть мышкой связи от одного блока к другому (актуально на декабрь 2021), приходится выбирать следующий шаг для блока в панели свойств. Правда со временем к этому привыкаешь.

Для каждого блока можно посмотреть подсказку.

Сохранить изменения в блоке можно только после того, как корректно заполнены все обязательные поля.

Давайте посмотрим на итоговый сценарий:

Итоговый сценарий
Итоговый сценарий

Всего 10 блоков. Вы можете достаточно быстро повторить его самостоятельно.

Но для тех кому не терпится поскорее попробовать, я оставлю в GitHub возможность быстро клонировать сценарий аж целыми двумя способами:

  • через специальную ссылку;

  • через запрос к API;

Прежде чем посмотреть настройки каждого блока расскажу вам о еще одной особенности редактора, на линиях связи между блоками указано в каком случае связь будет активна (например, "Успешно", 'Лимит").

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

Давайте пройдемся по всем блокам. Я оставлю везде скриншоты настроек, чтобы вы могли свериться.

Блоки сценария

Исходящий сценарий

Самый первый блок нашего сценария. Устанавливается автоматически.
В этом блоке можно добавить переменные, передаваемые через тело POST запроса к API. Как вы увидите позже переменные могут быть массивами, например goods.

Еще в настройках блока можно выбрать голос бота (есть и мужские и женские).

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

Я рекомендую давать блокам осмысленные названия, потому что в противном случае будет очень легко запутаться при выборе между: "Состояние 5" и "Состояние 7".
В дальнейшем я не буду подробно останавливаться на блоках перехода, потому что логика везде похожа.

"Исходящий сценарий"
"Исходящий сценарий"

Исходящий вызов ("Вызов")

Блок, который непосредственно совершит звонок на номер клиента.
Опция "Номер из кампании" позволит нам выбрать любой номер, который мы позже вызовем через API.

Перейдем к блоку "Проигрыватель".

Исходящий вызов (Вызов)
Исходящий вызов (Вызов)

Проигрыватель ("Зачитать список товаров")

Блок проигрывает абоненту заданное сообщение
Можно заранее подготовить медиа файл (это мы отложим на другой раз), а можно просто зачитать текст голосом, добавив переменные из блока "Исходящий сценарий".

Переменные в тексте выделяются двойными фигурными скобками, например, {{name}}. Как вы, уже догадались, робот в данном месте проговорит переменную name из POST запроса.

Для меня оказалось приятной неожиданностью, то что робот умеет читать числительные. Таким образом, отправленные мной в поле total10000, были прочитаны именно как десять тысяч, а не "один - ноль - ноль - ноль - ноль - ноль".

Блок похоже умеет распознавать автоответчики, но мне пока было не на чем проверить этот функционал.

Но я всё равно решил его сделать для демонстрации отправки HTTP запроса в случае негативного сценария. К описанию блока "Неуспех" мы придем, ближе к концу раздела, а пока перейдем дальше по успешному пути.

Проигрыватель ("Зачитать список товаров")
Проигрыватель ("Зачитать список товаров")

Интерактивный ввод ("Подтверждение")

Интерактивное меню, отвечает за пользовательский ввод.

Пользователь может осуществить ввод несколькими способами:

  • с помощью клавиатуры телефона;

  • голосом;

  • комбинацией первых двух способов;

Мне показался любопытным именно вариант с голосом.

В данном случае можно самостоятельно подобрать слова (с учётом маски), а можно выбрать готовый набор ответов, как в итоге сделал я.

Рекомендую оставить пользователю голосовое сообщение, о том что надо подтвердить заказ.

После данного блока у нас намечается еще одно ветвление.

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

А пока мы пойдем к следующему блоку по успешному пути.

Интерактивный ввод ("Подтверждение")
Интерактивный ввод ("Подтверждение")

HTTP (Обновить статус)

Данный блок отправляет запрос в интернет-магазин на обновление статуса заказа. Мы будем использовать WooCommerce и соответственного его API. После выполнения запроса в магазине статус заказа изменится на "Заказ подтвержден".

По сути, блок просто представляет поля для сборки запроса. На всякий случай предупрежу, что я проверял отправку запроса из сценария, только для случая, когда магазин имеет сертификат безопасности и следовательно мы обращаемся к нему через "https". С обычным "http" у WooCommerce есть некоторые сложности, с которыми я не хотел бороться.

В принципе параметры запроса я брал из экспорта в cURL через Postman, о чем немного подробнее будет рассказано в следующем разделе.

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

Я не стал усложнять и продумывать действия в случае неудачного запроса.

После успешного HTTP запроса нам остается только попрощаться с клиентом.

HTTP (Обновить статус)
HTTP (Обновить статус)

Проигрыватель ("Спасибо за подтверждение")

Аналогичный блок мы уже разбирали, поэтому просто оставлю скрин параметров.

Осталось только повесить трубку.

Отбой

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

Отбой
Отбой

На этом успешный путь завершен. Давайте рассмотрим случай, когда пользователь не смог справиться с интерактивным вводом.

Проигрыватель ("Дождитесь оператора")

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

Проигрыватель ("Дождитесь оператора")
Проигрыватель ("Дождитесь оператора")

Переадресация ("Звонок оператору")

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

После переадресации по идее этот вызов должен завершиться на блоке "Отбой".

Переадресация ("Звонок оператору")
Переадресация ("Звонок оператору")

Остался последний "неуспешный" блок.

HTTP ("Неуспех")

Запрос практически полностью идентичен "успешному" HTTP запросу, с разницей только в статусе заказа, который мы после запроса изменим на "Нужен звонок".

HTTP ("Неуспех")
HTTP ("Неуспех")

Настройки кампании

Для того, чтобы отправлять запрос к API нам необходимо знать, параметры авторизации и значение поля method. Для этого надо создать кампанию.

Перейдём на вкладку "Кампании"

Создадим новую, кампанию и выберем в её настройках опцию "Запуск - по HTTP запросу".

Остальные параметры, интуитивно понятны.


Вот так выглядит кампания для нашего сценария.

Страница настроек кампании
Страница настроек кампании

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

Есть один важный момент. Как я понял, после привязки сценария к кампании, многие его параметры нельзя будет изменить пока кампания не удалена. Например, нельзя будет удалить блок, или добавить новый. Зато можно будет изменить голос робота. Именно, этим мы с вами воспользуемся в следующих разделах.

Работа с API через Postman

При написании статьи я ориентировался на эту версию документации от разработчиков.

Для вашего удобства я собрал в Postman коллекцию тех методов, которые мы используем для статьи.

На всякий случай помимо методов Voicebox я добавил пару методов для проверки API WooCommerce.

Скачать коллекцию и окружение можно в GitHub.

Не забудьте в окружении указать ваши значения для переменных, потому что свои данные я удалил.

Собственно дальше, мы будем ориентироваться именно на коллекцию в Postman.Как я уже говорил выше, логин, пароль и метод можно найти на странице кампании, с которой мы связали наш сценарий.

Запрос на звонок

Наверняка у вас уже горят руки наконец-то попробовать звонок. Не будем откладывать это дело в долгий ящик.

Звонок, это самое простое. Нам надо отправить POST запрос на адрес
https://voicebox.mtt.ru/v1/sb, выбрав базовую авторизацию с указанием логина и пароля из настроек кампании.

Вот как это выглядит в Postman

Пример запроса в Postman
Пример запроса в Postman

Вот пример cURL запроса (не забудьте подставить свои данные):

curl -L -X POST 'https://voicebox.mtt.ru/api/v1/sb' -H 'Authorization: Basic some_token' -H 'Content-Type: application/json' --data-raw '{
    "method": "10dXXXXXXXXXXXXXXXXXXXbe1",
    "data": {
        "number": "796NNNNNNNN",
        "goods": [
            "Tapki",
            "Kepka"
        ],
        "name": "Roman",
        "total":10000,
        "orderid":17
    }
}'

В ответ метод вернёт, какой-то ID, возможно это ID запроса на звонок (я так и не понял из документации).

Но самое главное, что нам позвонит робот и проговорит то, что мы ожидаем услышать.

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

В первой главе статьи я обещал, что покажу, как клонировать сценарий через запрос к API.

Но есть одна маленькая трудность, мы не знаем наш customerID, ну по крайней мере мне его "заказчик" не дал.

Я не стал мучать техподдержку разработчиков, тем более, что личный кабинет оформлен не на меня, поэтому просто воспользовался старой доброй консолью разработчика.

Открываем страницу со сценариями, а в консоли разработчика (я использую Chrome) вкладку "Network".

Забираем customerID (подчеркнуто синим) из Request URL запроса к методу scenarios.

Есть еще одна вещь, которая нам пригодится. У меня почему-то не работает для этого метода базовая авторизация, API ругается, что нет поля token.

Я думаю, это однажды поправят, а пока просто заберем token из authorization (подчеркнуто синим). Теперь вместо, Basic Auth используем Bearer token.

Метод создаст сценарий и вернет нам всю его структуру вместе с ID.

cUrl запрос спрячу под спойлер.

cURL запрос на создание

curl -L -X POST 'https://voicebox-api.mtt.ru/v1/customers/XX/scenarios' -H 'Authorization: Bearer NDFKXXGITNTUXXXX1YJUTODE2MTQXXXXXXZDRK' -H 'Content-Type: application/json' --data-raw '{ "name": "Для хабра клон1", "type": "outgoing", "comment": "", "states": [ { "type": "Initial", "name": "State", "x": -668, "y": 102, "error": null, "success": { "title": "", "newState": "Вызов" } }, { "type": "OutgoingCall", "name": "Вызов", "x": -512, "y": 205, "error": null, "success": { "title": "", "newState": "Зачитать список товаров" }, "phone": "{{numberB}}" }, { "type": "VoiceMessage", "name": "Зачитать список товаров", "x": -307, "y": 331, "error": null, "success": { "title": "", "newState": "Подтверждение" }, "detectAI": true, "onDetectAI": { "title": "", "newState": "Неуспех" }, "announcement": { "type": "text", "file": "", "text": "Здравствуйте {{name}}, вы заказали {{goods}}, на сумму {{total}} рублей" }, "onNoInput": null, "onExpiry": null, "onAParty": null, "onInvalidInput": null }, { "type": "ReleaseCall", "name": "Отбой", "x": -72, "y": 988, "error": null, "success": null }, { "type": "HTTPRequest", "name": "Неуспех", "x": -472, "y": 669, "error": null, "success": { "title": "", "newState": "Отбой" }, "method": "PUT", "host": "XXXXX.ru", "protocol": "https", "port": 443, "url": "/wp-json/wc/v3/orders/{orderid}", "headers": { "Authorization": "Basic Y2tfMjBiYTI0NjQ0XXXXXXXY2Y1ZXXXXXXkZTE2N2YTQ4YTpjc182NjU2ODVlYjUzMWEwN2JkY2Y3Y2FmYjcwNjY5ODQ4YmRlNmRjZjQ0" }, "requestParameters": null, "requestParametersRaw": "{"status": "need_call"}", "requestParametersType": "raw", "responseCodeVariable": "RESPONSE_CODE_1", "responseBodyVariables": {} }, { "type": "IVR", "name": "Подтверждение", "x": 25, "y": 364, "error": { "title": "", "newState": "Дождитесь оператора" }, "success": null, "onDetectAI": null, "awaitingTime": 10, "announcement": { "type": "text", "file": "", "text": "Вы подтверждаете заказ?" }, "repeatCount": 3, "detectAI": false, "voiceInput": true, "keyboardInput": false, "immediateStt": false, "inputAnalysis": [ { "success": { "title": "", "newState": "Обновить статус" }, "dtmf": "", "words": null, "keywords": [ { "name": "Согласие", "id": "5", "lost": false } ] } ], "onNoInput": { "title": "", "newState": "Дождитесь оператора" }, "onExpiry": { "title": "", "newState": "Дождитесь оператора" }, "onAParty": null, "onInvalidInput": null }, { "type": "HTTPRequest", "name": "Обновить статус", "x": 91, "y": 606, "error": null, "success": { "title": "", "newState": "Спасибо за подтверждение" }, "method": "PUT", "host": "XXXXX.ru", "protocol": "https", "port": 443, "url": "/wp-json/wc/v3/orders/{{orderid}}", "headers": { "Authorization": "Basic Y2tfMjBiYTI0NjQ0MXXXXXXY2FmYjcwNjY5ODQ4YmRlNmRjZjQ0" }, "requestParameters": null, "requestParametersRaw": "{\n "status": "confirmed_ordr"\n}", "requestParametersType": "raw", "responseCodeVariable": "RESPONSE_CODE_2", "responseBodyVariables": {} }, { "type": "Redirection", "name": "Звонок оператору", "x": -136, "y": 722, "error": null, "success": { "title": "", "newState": "Отбой" }, "numberB": "{{RESPONSE_CODE_1}}", "extension": "", "callingPartyDisplay": "forwarder_number", "forwardType": "number", "sipUri": "", "credentialInfo": null }, { "type": "VoiceMessage", "name": "Дождитесь оператора", "x": -194, "y": 499, "error": null, "success": { "title": "", "newState": "Звонок оператору" }, "detectAI": false, "onDetectAI": null, "announcement": { "type": "text", "file": "", "text": "Дождитесь оператора" }, "onNoInput": null, "onExpiry": null, "onAParty": null, "onInvalidInput": null }, { "type": "VoiceMessage", "name": "Спасибо за подтверждение", "x": 185, "y": 842, "error": null, "success": { "title": "", "newState": "Отбой" }, "detectAI": false, "onDetectAI": null, "announcement": { "type": "text", "file": "", "text": "Спасибо за подтверждение" }, "onNoInput": null, "onExpiry": null, "onAParty": null, "onInvalidInput": null } ], "customVariables": [ "goods", "total", "name", "orderid" ], "createdAt": "2021-12-12T22:05:44.441989969Z", "updatedAt": "2021-12-12T22:05:44.442003632Z", "settings": { "voice": "omazh", "speed": 1, "emotion": "neutral" }, "shareLink": "" }'

Обновление сценария

Обновление делается аналогично, только с методом PUT.

Я решил не маяться с обязательными / необязательными параметрами, а просто взять все поля из прошлого ответа.

Нас будет волновать только замена этого фрагмента:

 "settings": {
        "voice": "omazh",
        "speed": 1,
        "emotion": "neutral"
    },

Будем менять голос робота (значение поля voice) на "oksana" или "filipp".

Пример запроса в cURL, я приводить не буду он есть в коллекции Postman, а также внутри одной из функций следующего раздела.

Думаю, пора переходить к завершающей стадии.

Интеграция с WooCommerce (WordPress)

Интеграция — это конечно громко сказано.

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

Такая вот конвергенция двух систем.

Я не буду вдаваться в подробности установки WordPress и WooCommerce.

На момент написания статьи я использовал:

  • WooCommerce - версия 5.9.0

  • WordPress - версия 5.8.2

Первым делом добавим новые статусы в файл functions.php текущей темы.

Напомню, что отредактировать этот файл можно через настройки перейдя в раздел

"Внешний вид" -> "Редактор тем" и открыв "Функции темы".

Вы можете просто забрать код из GitHub и вставить фрагмент, куда-нибудь в конец файла.

Вначале статьи я уже говорил, что я не программист, но не грех будет напомнить.

Я если честно, последний раз смотрел на код на PHP лет 12 назад, а с WordPress и вовсе не работал толком никогда, поэтому не буду врать, я просто позаимствовал код отсюда и адаптировал под себя.

Код под спойлером
/**

Adding custom status for hanr

*/

// Регистрируем наши новые статусы заказа в системе
function register_voicebox_call_statuses() {

register_post_status( 'wc-need_call', array(

'label' => 'Нужен звонок',

'public' => true,

'show_in_admin_status_list' => true,

'show_in_admin_all_list' => true,

'exclude_from_search' => false,

'label_count' => _n_noop( 'Нужен звонок <span class="count">(%s)</span>', 'Нужен звонок <span class="count">(%s)</span>' )

) );

register_post_status( 'wc-confirmed_ordr', array(

'label' => 'Заказ подтвержден',

'public' => true,

'show_in_admin_status_list' => true,

'show_in_admin_all_list' => true,

'exclude_from_search' => false,

'label_count' => _n_noop( 'Заказ подтвержден <span class="count">(%s)</span>', 'Заказ подтвержден <span class="count">(%s)</span>' )

) );

}

add_action( 'init', 'register_voicebox_call_statuses' );

// добавляем статусы куда-то
function add_voicebox_call_to_order_statuses( $order_statuses ) {

$new_order_statuses = array();

foreach ( $order_statuses as $key => $status ) {

$new_order_statuses[ $key ] = $status;

if ( 'wc-processing' === $key ) {

$new_order_statuses['wc-need_call'] = 'Нужен звонок';
$new_order_statuses['wc-confirmed_ordr'] = 'Заказ подтвержден';
}
}
return $new_order_statuses;
}

add_filter( 'wc_order_statuses', 'add_voicebox_call_to_order_statuses' );

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

Теперь наш сценарий полностью укомплектован, мы можем позвонить через Postman и статусы поменяются.

Но нам явно не хватает возможности сделать вызов через админ-панель WooCommerce.

Добавьте данный код ниже:

Код под спойлером
/**

Adding custom actions for hanr

*/



// Добавим действие со звонком в список


add_action( 'woocommerce_order_actions', 'call_voice1' );
function call_voice1( $actions ) {
$actions['call_voice'] = __( 'Звонок бота', 'text_domain' );
return $actions;
}

// Добавим непосредственно логику для звонка
add_action( 'woocommerce_order_action_call_voice', 'call_voice2' );
function call_voice2( ) {

// Получим данные о заказе
global $woocommerce, $post;
$order = new WC_Order($post->ID);

//to escape # from order id
$order_id = trim(str_replace('#', '', $order->get_order_number()));
//Получим основные данные о заказе и заказчике
$billing_phone = $order->get_billing_phone();
$billing_phone =str_replace("+","",$billing_phone);
$order_total = $order->get_total();
$billing_first_name = $order->get_billing_first_name();
$goods = "";
// Пройдемся циклически по всем продуктам заказа и заберем их названия

foreach ($order->get_items() as $item_key => $item_values):
$item_name = $item_values->get_name(); // Name of the product
$goods=$goods.'"'.$item_name.'",';
endforeach;


// cURL запрос автоматически сгенерирован в Postman, я просто добавил переменные в разрывы строк.
$curl = curl_init();

curl_setopt_array($curl, array(
CURLOPT_URL => 'https://voicebox.mtt.ru/api/v1/sb',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS =>'{
"method": "10dada97-XXXX-450d-b681-6a46a6484be1",
"data": {
"number": "'.$billing_phone.'",
"goods": ['.rtrim($goods, ",").'],
"name": "'.$billing_first_name.'",
"total":'.$order_total.',
"orderid":'.$order_id.'}
}',
CURLOPT_HTTPHEADER => array(
'Authorization: Basic XXXXXXhhNjgtMjAxZi00Mjg3LWEyZjItMjRhNmM4NGEyMDc5Ok93NyZAfjZyMUc=',
'Content-Type: application/json'
),
));
$response = curl_exec($curl);
curl_close($curl);
}

Тут код тоже далеко не весь мой. Я взял сами действия тут, а запросы к API просто экспортировал из Postman, добавив в разрывы текста нужные данные.

Остался последний шаг - добавить возможность изменять голос бота из админ-панели.

Мне было лень разбираться с обязательными и необязательными полями в запросе на обновление сценария, поэтому я просто экспортировал его из Postman и добавил смену голоса бота в разрыв текста.

Код под спойлером
// Функция для обновления голоса, через полное обновление сценария

function change_voice($voice) {

$curl = curl_init();

curl_setopt_array($curl, array(
CURLOPT_URL => 'https://voicebox-api.mtt.ru/v1/customers/XX/scenarios/XX2536',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_POSTFIELDS =>'{
"id": 252536,
"name": "Для Хабра 1",
"type": "outgoing",
"comment": "",
"states": [
{
"type": "Initial",
"name": "State",
"x": -668,
"y": 102,
"error": null,
"success": {
"title": "",
"newState": "Вызов"
}
},
{
"type": "OutgoingCall",
"name": "Вызов",
"x": -512,
"y": 205,
"error": null,
"success": {
"title": "",
"newState": "Зачитать список товаров"
},
"phone": "{{numberB}}"
},
{
"type": "VoiceMessage",
"name": "Зачитать список товаров",
"x": -307,
"y": 331,
"error": null,
"success": {
"title": "",
"newState": "Подтверждение"
},
"detectAI": true,
"onDetectAI": {
"title": "",
"newState": "Неуспех"
},
"announcement": {
"type": "text",
"file": "",
"text": "Здравствуйте {{name}}, вы заказали {{goods}}, на сумму {{total}} рублей"
},
"onNoInput": null,
"onExpiry": null,
"onAParty": null,
"onInvalidInput": null
},
{
"type": "ReleaseCall",
"name": "Отбой",
"x": -72,
"y": 988,
"error": null,
"success": null
},
{
"type": "HTTPRequest",
"name": "Неуспех",
"x": -472,
"y": 669,
"error": null,
"success": {
"title": "",
"newState": "Отбой"
},
"method": "PUT",
"host": "XXXX.ru",
"protocol": "https",
"port": 443,
"url": "/wp-json/wc/v3/orders/{orderid}",
"headers": {
"Authorization": "Basic XXXXXjBiYTI0NjQ0MmUxYjU0ZThhM2RjY2Y1ZDFkY2JkZTE2N2Y5NTQ4YTpjc182NjU2ODVlYjUzMWEwN2JkY2Y3Y2FmYjcwNjY5ODQ4YmRlNmRjZjQ0"
},
"requestParameters": null,
"requestParametersRaw": "{\\"status\\": \\"need_call\\"}",
"requestParametersType": "raw",
"responseCodeVariable": "RESPONSE_CODE_1",
"responseBodyVariables": {}
},
{
"type": "IVR",
"name": "Подтверждение",
"x": 25,
"y": 364,
"error": {
"title": "",
"newState": "Дождитесь оператора"
},
"success": null,
"onDetectAI": null,
"awaitingTime": 10,
"announcement": {
"type": "text",
"file": "",
"text": "Вы подтверждаете заказ?"
},
"repeatCount": 3,
"detectAI": false,
"voiceInput": true,
"keyboardInput": false,
"immediateStt": false,
"inputAnalysis": [
{
"success": {
"title": "",
"newState": "Обновить статус"
},
"dtmf": "",
"words": null,
"keywords": [
{
"name": "Согласие",
"id": "5",
"lost": false
}
]
}
],
"onNoInput": {
"title": "",
"newState": "Дождитесь оператора"
},
"onExpiry": {
"title": "",
"newState": "Дождитесь оператора"
},
"onAParty": null,
"onInvalidInput": null
},
{
"type": "HTTPRequest",
"name": "Обновить статус",
"x": 91,
"y": 606,
"error": null,
"success": {
"title": "",
"newState": "Спасибо за подтверждение"
},
"method": "PUT",
"host": "XXXX.ru",
"protocol": "https",
"port": 443,
"url": "/wp-json/wc/v3/orders/{{orderid}}",
"headers": {
"Authorization": "Basic XXXXjBiYTI0NjQ0MmUxYjU0ZThhM2RjY2Y1ZDFkY2JkZTE2N2Y5NTQ4YTpjc182NjU2ODVlYjUzMWEwN2JkY2Y3Y2FmYjcwNjY5ODQ4YmRlNmRjZjQ0"
},
"requestParameters": null,
"requestParametersRaw": "{\\n \\"status\\": \\"confirmed_ordr\\"\\n}",
"requestParametersType": "raw",
"responseCodeVariable": "RESPONSE_CODE_2",
"responseBodyVariables": {}
},
{
"type": "Redirection",
"name": "Звонок оператору",
"x": -136,
"y": 722,
"error": null,
"success": {
"title": "",
"newState": "Отбой"
},
"numberB": "{{RESPONSE_CODE_1}}",
"extension": "",
"callingPartyDisplay": "forwarder_number",
"forwardType": "number",
"sipUri": "",
"credentialInfo": null
},
{
"type": "VoiceMessage",
"name": "Дождитесь оператора",
"x": -194,
"y": 499,
"error": null,
"success": {
"title": "",
"newState": "Звонок оператору"
},
"detectAI": false,
"onDetectAI": null,
"announcement": {
"type": "text",
"file": "",
"text": "Дождитесь оператора"
},
"onNoInput": null,
"onExpiry": null,
"onAParty": null,
"onInvalidInput": null
},
{
"type": "VoiceMessage",
"name": "Спасибо за подтверждение",
"x": 185,
"y": 842,
"error": null,
"success": {
"title": "",
"newState": "Отбой"
},
"detectAI": false,
"onDetectAI": null,
"announcement": {
"type": "text",
"file": "",
"text": "Спасибо за подтверждение"
},
"onNoInput": null,
"onExpiry": null,
"onAParty": null,
"onInvalidInput": null
}
],
"customVariables": [
"goods",
"total",
"name",
"orderid"
],
"campaign": {
"id": 252729,
"name": "Хабр1",
"campaignType": "outbound",
"scenario": {
"id": 252536,
"name": "",
"type": "",
"comment": "",
"states": null,
"customVariables": null,
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"settings": {
"voice": "",
"speed": 0,
"emotion": ""
},
"shareLink": ""
},
"callerNumber": {
"id": 157409,
"number": "74997059704",
"type": "ABC",
"owner": "MTT",
"regionName": "Moscow",
"regionCode": "MOW",
"category": "REGULAR",
"description": "number buying",
"status": "Active",
"campaigns": null
},
"entryPoint": {
"id": 252728,
"url": ""
},
"launchBy": "http",
"routeId": 26589,
"createdAt": "2021-12-12T17:21:50Z",
"updatedAt": "2021-12-12T17:21:50Z"
},
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"settings": {
"voice": "'.$voice.'",
"speed": 1,
"emotion": "neutral"
},
"shareLink": "XXXXX-c3d9-4d71-b23d-c0c47ca206f2"
}',
CURLOPT_HTTPHEADER => array(
'Authorization: Bearer XXXXXMMZKTNWEYYY0ZNJQXLWJLYTETOGRMYTY4ZGEZZMZK',
'Content-Type: application/json'
),
));

$response = curl_exec($curl);

curl_close($curl);
echo ($response);
// exit('Раскомментируй чтобы посмотреть вывод')

}

// добавляем действие в список действий
add_action( 'woocommerce_order_actions', 'woman_voice1' );
function woman_voice1( $actions ) {
$actions['woman_voice'] = __( 'Женский голос у бота', 'text_domain' );
return $actions;
}

// добавляем логику по замене голоса на женский
add_action( 'woocommerce_order_action_woman_voice', 'woman_voice2' );
function woman_voice2( ) {

change_voice("oksana");
}

// добавляем действие в список действий
add_action( 'woocommerce_order_actions', 'man_voice1' );
function man_voice1( $actions ) {
$actions['man_voice'] = __( 'Мужской голос у бота', 'text_domain' );
return $actions;
}

// добавляем логику по замене голоса на женский
add_action( 'woocommerce_order_action_man_voice', 'man_voice2' );
function man_voice2( ) {
change_voice("filipp");
}

Должно получиться примерно вот так:

Новые действия и новый статус в админ-панели WooCommerce
Новые действия и новый статус в админ-панели WooCommerce

Если выбрать "Звонок бота", то на номер из заказа позвонит бот и скажет дословно "Здравствуйте Роман, Вы заказали: кот в мешке, крот в горшке, на сумму 120 рублей. Вы подтверждаете заказ?".

Ну и само собой мы можем теперь изменить голос бота кнопками "Мужской (женский) голос у бота".

Миссия успешно выполнена.

В благодарность за то, что я показал людям, как работать с голосовым ботом, мне пообещали дать поиграться с ним в свое удовольствие. Так что я думаю написать еще 1–2 статьи по данной теме.

Спасибо всем, кто дочитал до конца, если будут ошибки или битые ссылки пишите мне в "личку" или в комментарии.




Комментарии (13):

  1. gmk1
    /#24340992 / +12

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

    Так же часто пытаются всякие фирмы выманить адрес, с улицей, домом и квартирой, вместо анонимного А/Я на почте. Как плохо становится организации когда телефон не дают, адрес настоящий тоже, емаил видно что тоже только для этого заказа, и вместо всего этого, есть только абонентский ящик. Который можно в любой момент закрыть.

    • BosonBeard
      /#24341076

      Супер, причем тут статья? То есть я написал про замену роботом оператора который уведомляет пользователя о совершенном заказе, а Вы написали гневный комментарий о воровстве и утечках данных?

      • unwrecker
        /#24341706

        При том, что в типе ответа оставили только вариант голоса, и убрали вариант с клавиатуры. Я тоже принципиально не общаюсь с роботами.

        • BosonBeard
          /#24341722

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

          Тем более ввод с клавиатуры мне показался более "скучным" с точки зрения разбираемой задачи.

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

  2. Oz_Alex
    /#24340994 / +6

    Я: Делаю заказ через сайт, потому что не хочу общаться с кожаными мешками по телефону.
    Сайт: всё равно звонит мне через робота, чтобы поговорить.

    Я:
    image

    • BosonBeard
      /#24341130

      Для этого умные маркетологи делают галочку на сайте "не нужно подтверждать заказ по телефону", и все, не будет ненужных звонков)

      Я всего-то предложил способ, как можно снять рутинные процессы с операторов тех самых магазинов, кому нужно обзванивать и подтверждать заказы)

      • rstepanov
        /#24341446 / +2

        Все просто, голосовые боты вызывают НЕНАВИСТЬ!!! Разработчики голосовых ботов - тоже... Страдайте...

      • darken99
        /#24342900

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

        • NemoVors
          /#24343154

          Причем не просто издеваться, а издеваться автоматизированно. Т.е. потенциально чаще и дольше.

  3. NemoVors
    /#24341322 / +3

    Хочу прояснить одну деталь: звонок живого оператора применяется в том случае, если в заказе есть что-то непонятное. Человек написал адрес с опечаткой, или что-то там не то выбрал. Или нужно предупредить о чем-то таком нестандартном, что нельзя показать на сайте. Иначе зачем вообще нужен этот звонок с подтверждением?

    Так вот: что такого может у человека уточнить бот, чего нельзя написать на сайте? Зачем плодить сущности?

    • BosonBeard
      /#24341346

      Ну хз мне из некоторых магазинов звонят люди и проговаривают все что я заказал, в конце спрашивают подтверждаю ли.

      Во вторых этож туториал по АПИ, а не маркетинговое руководство Вы можете придумать другой кейс для голосового бота.

    • Newm
      /#24342080 / +3

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

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

  4. midday
    /#24341976 / +4

    А вот кто эти твари, что разрабатывают это! Вот этих людей точно можно назвать врагами народа. Чтоб вас черти драли в аду.