Pull to refresh
Flowwow
Маркетплейс локальных магазинов

Кастомный API для маршрутизации звонков: зачем и как

Reading time8 min
Views1.1K

Все сервисы стремительно уходят в digital — мобильные приложения, чат-боты и онлайн-чаты с поддержкой. У маркетплейса Flowwow они тоже есть, однако телефоном до сих пор пользуются. В день мы получаем около 90 звонков — для их обработки нецелесообразно содержать отдельный штат операторов, но и отказываться от этого канала связи мы не хотим.

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

В этой статье пройдем пути звонящего в зависимости от того, кто он и какой у него запрос. (кат)

Мы собрали свой сценарий на платформе Voximplant и настроили его с нашими внутренними системами.

Кто может позвонить:

  • клиент с активным заказом;

  • клиент с новым заказом. 

Или это могут быть наши коллеги из магазина-партнера, про такой сценарий тоже расскажем.

Для начала приветствие

Поздороваться и объяснить, куда клиент попал, — стандартная история. Пока мы маршрутизируем звонок, вот что происходит на бэкстейдже:

/**
 * Класс со сценариями
 * */
class FlowwowScenario {
    /**
     * Создание объекта и инициализация сценариев от входных данных
     * */
    constructor(data = {}, incomingCall) {
 
        //Данные из api flowwow
        this.data = data;
        //Входящий звонок
        this.call = incomingCall;
        //Объект с api
        this.api = new FlowwowAPI();
        this.api.phone = this.call.callerid();
        //Очередь активных звонков
        this.callQueue = [];
        //Очередь событий по времени
        this._timers = [];
        //Храним состояние, кому сейчас перенаправляется звонок
        this._state  = '';
        //Ссылка на запись звонка
        this._record = '';
        //Активный сценарий
        this._scenario = '';
        //Кто ответил
        this._respondent = '';
 
        //При сбрасывании звонка скидываем всё очереди на звонок
        this.call.addEventListener(CallEvents.Disconnected, e => {
            this.cleanTimers();
            this.dropAllCalls();
        });

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

Определяем звонящего: клиент с активным заказом

После приветствия нам нужно понять, кто наш пользователь? В данном сценарии мы получаем звонок от клиента с уже существующим заказом:

_orderScenario() {
        let api = this.api;
        let user = this.data.order.user;
 
 this.api.sendMessageInSlack(`Звонок от ${user.name}     ${this.call.callerid()} по заказу ${FlowwowAPI.getOrderLink(this.data.order.id)}`, 'order');
 
        this.call.handleTones(true);
        this.call.addEventListener(CallEvents.ToneReceived, e => {
            this.call.removeEventListener(CallEvents.ToneReceived, null);
            this.cleanTimers();
            this.call.handleTones(false);
            switch (e.tone) {
                case '1':
                    this.pressButtonOne();
                    break;
                case '2':
                    this.pressButtonTwo();
                    break;
                default:
                    this.api.sendMessageInSlack(`Клиент зачем-то нажал другую кнопку`);
                break;
            }
        });
        //Проигрываем приветствие
        this.call.startPlayback(getSound('FW1'));
        let nextSound = setTimeout(() =>

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

{
            //Проигрываем обращение к пользователю с активным заказом
            this.call.startPlayback(getSound('FW3'));
        }, 28000);
        this._timers.push(nextSound);
    }

Допустим, клиенту хочется узнать, что там с заказом. Для этого он нажимает кнопку с номером 1 на экране pressButtonOne() и переключается напрямую к магазину-партнеру.

pressButtonOne() {
        let shop = this.data.order.shop;
        let link = FlowwowAPI.getShopLink(shop.id);

Тут важно, чтобы магазин был на связи. Если звонок происходит уже во внерабочее время или коллеги не отвечают в течение 15 секунд, то запрос перехватим мы в службе поддержки Flowwow:

// Если магазин открыт (рабочее время)
        if(shop.is_opened){
            this.callShop();
       	 /// Объявляем о переключении на магазин
            this.call.startPlayback(getSound('FW6'));
 
            var playSound8 = setTimeout(() => {
                   	/// Называем имя клиента и заказ
                this.call.startPlayback(getSound('FW8'));
            }, 4000);
            this._timers.push(playSound8);
 
            var playSoundDouble8 = setTimeout(() => {
            /// Называем имя клиента и заказ
                this.call.startPlayback(getSound('FW8'));
            }, 14000);
            this._timers.push(playSoundDouble8);

Время в 15 секунд истекло, поэтому забираем заявку мы — в офис или на колл-центр:

 var playSound4 = setTimeout(() => {
   	     /// Включаем запись о переводе на службу поддержки
                this.call.startPlayback(getSound('FW4'));
            }, 24000);
            this._timers.push(playSound4);
 
             var playSoundTwo8 = setTimeout(() => {
                this.api.sendMessageInSlack(`Исполнитель(${shop.name} ${shop.shop_phone} ${link} ${shop.city}) не ответил - перевод на офис`);
                this.call.startPlayback(getSound('FW8'));
                this.callFlowwow();
            }, 32000);
            this._timers.push(playSoundTwo8);
 
            var tryCallCallCenter = setTimeout(() => {
                if(this._isReportingToday()) {
                    this.api.sendMessageInSlack('Офис не ответил - перевод на кц');
                }
                this.callCallCenter();
            }, 42000);
            this._timers.push(tryCallCallCenter);
        } else {
            this.call.startPlayback(getSound('FW5'));
            this.callCallCenter();
        }
    }

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

Отдельная история, как устроен перевод заявки напрямую в магазин или колл-центр Flowwow. Понять, куда ушел запрос, нам поможет функция _noticeAboutWhoAnswer

_call(phones) {
    }
 
    _noticeAboutWhoAnswer(phoneReceiver)
    {
        let api = this.api;
        let msg = '';
        if (this._state == SHOP) {
            let shop = this.data.order.shop;
            let link = FlowwowAPI.getShopLink(shop.id);
            this._respondent = link;
            msg = `На звонок ответил ${this._state} ${link} ${shop.city} ${phoneReceiver}`;
            api.sendMessageInSlack(msg);

Если магазин не отвечает (как мы говорили, причины две — внерабочее время или просто невозможность ответить на звонок в данный момент), то происходит переключение заявки на офис Flowwow:

} else {
            api.log(phoneReceiver)
                .then(data => {
                    let answerData = JSON.parse(data.text);
                    let msg = '';
                    if (answerData.result.error == 0) {
                        this._respondent = answerData.result.manager_name;
                        msg = this._state == OFFICE
                            ? `На звонок ответила ${answerData.result.manager_name} (${phoneReceiver})`
                            : `На звонок номер звонящего ответил ${answerData.result.manager_name} (${phoneReceiver})`;
                    } else {
                        msg = `${answerData.result.description}`
                    }
                    api.sendMessageInSlack(msg);
                })
                .catch(err => {
                    Logger.write('Api error');
                });
        }
}

Звонок принят, и нам отвечает менеджер офиса. Теперь нужно обнулить счетчик 15 секунд:

_callStart(e) {
        this.cleanTimers();
 
        let api = this.api;
        let incomingCall = this.call;
        let outCall = this.dropAllCalls(e.call.number());
        let time = 0;

И зафиксировать все данные по заказу — имя пользователя, номер заказа и имя менеджера:

this._noticeAboutWhoAnswer(e.call.number());
 
        if (this._isOrderScenario() && this._state == SHOP) {
            let order_id = this.data.order.id.split('').join(' ');
            let user_name = this.data.order.user.name;
            outCall.say(
                `Вам звонит клиент ${user_name} Флаувау. Заказ ${order_id}`,
                Language.RU_RUSSIAN_FEMALE,
                {
                    rate: 'slow',
                }
            );
            time = 9000;
        }
 
        outCall.addEventListener(CallEvents.TransferComplete, function (e) {
 
            let apiTransfer = new FlowwowAPI();
            apiTransfer.log(e.call.number())
                .then(data => {
                    let answerData = JSON.parse(data.text);
                    let msg = '';
                    if (answerData.result.error == 0) {
                        this._respondent = answerData.result.manager_name;
                        msg = `Звонок перенаправлен на ${answerData.result.manager_name}`
                    } else {
                        msg = `${answerData.result.description}`
                    }
                    apiTransfer.sendMessageInSlack(msg);
                })
                .catch(err => {
                    Logger.write('Api error');
                });
 
        });

Соединение пользователей происходит несколькими методами:

VoxEngine.sendMediaBetween(incomingCall, outCall);
            VoxEngine.easyProcess(incomingCall, outCall);
 
            VoxEngine.addEventListener(AppEvents.Terminating, () => {
                recorder.stop();
                this._sendCallRecord();
            });
 
            outCall.addEventListener(CallEvents.Disconnected, () => {
                incomingCall.hangup();
                outCall.hangup();
            });
        }, time);
    }

На этом пути все: клиент позвонил по текущему заказу, выбрал возможность соединиться с магазином напрямую, и если не удалось до него дозвониться, то все равно получил ответ — в офисе Flowwow или колл-центре.

Вернемся на развилку, когда обращается клиент по текущему заказу. Выбор такой — звонить напрямую в магазин или в поддержку Flowwow? Когда у клиента, например, возник спор с магазином, он обращается к сотрудникам Flowwow. Нажимаем на экране кнопку с номером 2 pressButtonTwo()— это запрос в поддержку Flowwow, перенаправляем звонок в офис:

pressButtonTwo() {
        this.call.startPlayback(getSound('FW7'));
        if(this._isReportingToday()) {
            this.api.sendMessageInSlack('Перевод на офис');
        }
        this.callFlowwow();
        
        var soundFW8 = setTimeout(() => {
            this.call.startPlayback(getSound('FW8'));
        }, 4000);
        this._timers.push(soundFW8);
 
        var soundFW8Two = setTimeout(() => {
            this.call.startPlayback(getSound('FW8'));
        }, 14000);
        this._timers.push(soundFW8Two);
 
        var soundFW8Three = setTimeout(() => {
            this.call.startPlayback(getSound('FW8'));
        }, 24000);
        this._timers.push(soundFW8Three);

Прошло 15 секунд и нет ответа? Тогда берем запрос в колл-центр.

var callCallCenter = setTimeout(() => {
            this.call.handleTones(false);
            if(this._isReportingToday()) {
                this.api.sendMessageInSlack('Перевод на кц');
            }
            this.callCallCenter();
        }, 34000);
        this._timers.push(callCallCenter);
    }

Еще один вариант звонка от клиента: создание нового заказа

В этом случае мы проверяем, что у пользователя нет активных заказов, и после записи приветствия сразу переключаем звонок на офис Flowwow:

_otherScenario() {
        let data = this.data;
        let msg = '';
        if (!data.unknown) {
            let link = '';
            if (data.user.hasOwnProperty('orders_link')) {
                link = FlowwowAPI.slackLink(data.user.orders_link, 'все заказы пользователя ');
            }
            msg = `Звонок без заказа  ${data.user.name} ${this.call.callerid()} ${link}`;
        } else {
            msg = `Звонок без заказа ${this.call.callerid()}`;
        }
        this.api.sendMessageInSlack(msg, 'other');
 
        //Проигрываем запись приветствия
        this.call.startPlayback(getSound('FW1'));
 
        var tryCallFlowwow = setTimeout(() => {
            this.call.startPlayback(getSound('FW8'));
            // Переводим звонок на офис
            this.callFlowwow();
        }, 28000);
        this._timers.push(tryCallFlowwow);

Так, 15 секунд истекло. Значит, переключаем заявку на колл-центр:

var tryCallCallCenter = setTimeout(() => {
            // Переводим звонок на call center
            this.callCallCenter();
        }, 38000);
        this._timers.push(tryCallCallCenter);
    }

Если звонит магазин-партнер, что в этом случае?

Мы узнаем магазин по номеру телефона и коллеги услышат запись, в которой мы попросим их перейти в более оперативный канал связи — чат поддержки на сайте или в мобильном приложении. Поскольку взаимодействие с каждым партнером длится годами, важно хранить всю историю общения в текстовом виде. Кроме того, иногда вопрос не удается решить при первом обращении, тогда удобнее вернуться с ответом к чату, чем дополнительно фиксировать в CRM, с каким вопросом звонил партнер.Так сработает в коде:

_shopScenario() {
        //++++++++++++
        Logger.write('Logger: shop scenario start');
        let api  = this.api;
        let shop = this.data.shop;
        let call = this.call.callerid();
        if(this._isReportingToday()) {
            api.sendMessageInSlack(`Звонок из магазина номер ${call} id ${FlowwowAPI.getShopLink(shop.id)} город ${shop.city}`, 'shop');
        }
        //Проигрываем запись для магазина-партнера
        this.call.startPlayback(getSound('FW2'));
        this.call.addEventListener(CallEvents.PlaybackFinished, call => {
            this.call.hangup();
        });
    }

Все статусы автоматически падают в один из чатов саппорта в Slack’е, чтобы команда могла оперативно среагировать, если что-то пошло не по плану. В эти чаты также стекаются репорты по всем остальным каналам с автоматическим распределением тикетов по сотрудникам. Наша внутренняя хелпдеск-система устроена так, чтобы каждый менеджер видел только тикеты, назначенные ему. Получив сообщение, менеджер по поддержке видит номер и статус заказа, название магазина-исполнителя, адрес получателя и все, что нужно, чтобы быстро разобраться, в чем дело.

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

И еще лайфхак: занимаясь любой автоматизацией, мы отталкиваемся от проблем тех, кто в этом процессе участвует. Так, именно менеджеры саппорта на 80% выступают в роли заказчиков своей системы: объясняют, как им будет удобнее работать, и передают фидбек команде разработчиков.

Tags:
Hubs:
Rating0
Comments0

Articles

Information

Website
flowwow.com
Registered
Founded
Employees
101–200 employees
Location
Россия
Representative
savondaria