Скрываем номера курьеров и клиентов с помощью key-value хранилища

Скрываем номера курьеров и клиентов с помощью key-value хранилища



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

Каждый сервис использует свои решения для маскировки номеров клиентов и курьеров. В данной статье я расскажу, как сделать это с помощью key-value хранилища в Voximplant.

Как это будет работать

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

У нас будет только один «нейтральный» номер, на который будут звонить и клиент, и курьер. Номер мы арендуем в панели Voximplant. Затем создадим некую структуру данных, где клиент и курьер будут связаны между собой номером заказа (то есть ключом в терминологии key-value storage).


Так при звонке на арендованный номер звонящий введёт номер заказа, и если такой заказ есть в базе, наш сценарий проверит номера телефонов, привязанные к нему. Далее если номер звонящего будет идентифицирован как номер клиента, произойдет соединение с курьером, ответственным за заказ, и наоборот.

Например, звонок курьера клиенту будет выглядеть следующим образом:

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

Перейдем непосредственно к реализации.

Вам понадобятся

1) Чтобы начать разработку, войдите в свой аккаунт: manage.voximplant.com/auth. В меню слева нажмите «Приложения», затем «Создать приложение» в правом верхнем углу. Дайте ему имя, например, numberMasking и снова кликните «Создать».

2) Зайдите в новое приложение, переключитесь на вкладку «Сценарии» и создайте сценарий, нажав на «+». Назовём его kvs-scenario. Здесь мы будем писать код, но об этом чуть позже.

3) Сначала перейдем во вкладку «Роутинг» и создадим правило для нашего сценария. Маску (регулярное выражение) оставим «.*» по умолчанию, так правило будет срабатывать для всех номеров.

4) Далее арендуем реальный городской номер. Для этого перейдем в раздел «Номера», выберем и оплатим номер. На него будут звонить и клиент, и курьер, и он будет отображаться вместо их настоящих номеров.

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

5) Осталось привязать его к нашему приложению. Заходим в приложение, открываем вкладку «Номера» → «Доступные» и нажимаем «Прикрепить». В открывшемся окне можно также прикрепить наше правило, тогда оно будет автоматически назначено для входящих вызовов, а все остальные правила будут проигнорированы.

6) Далее необходимо верифицировать аккаунт, чтобы использовать этот номер для звонков.

Отлично, структура готова, осталось заполнить key-value хранилище и добавить код в сценарий.

Key-value хранилище

Чтобы сценарий заработал, нужно положить что-то в хранилище. Это можно сделать, воспользовавшись Voximplant Management API. Я буду использовать Python API client, он работает с Python 2.x или 3.x с установленным pip и setuptools> = 18.5.

1) Зайдем в папку проекта и установим SDK, используя pip:

python -m pip install --user voximplant-apiclient

2) Создадим файл с расширением .py и добавим в него код, при выполнении которого данные о заказе попадут в key-value хранилище. Применим метод set_key_value_item:

from voximplant.apiclient import VoximplantAPI, VoximplantException

if __name__ == "__main__":
    voxapi = VoximplantAPI("credentials.json")
    
    # SetKeyValueItem example.

    KEY = 12345
    VALUE = '{"courier": "79991111111", "client": "79992222222"}'
    APPLICATION_ID = 1
    TTL = 864000
    
    try:
        res = voxapi.set_key_value_item(KEY,
            VALUE,
            APPLICATION_ID,
            ttl=TTL)
        print(res)
    except VoximplantException as e:
        print("Error: {}".format(e.message))

Файл с необходимыми credentials вы сможете сгенерировать при создании сервисного аккаунта в разделе «Служебные аккаунты» в настройках панели. 

APPLICATION_ID появится в адресной строке при переходе в ваше приложение. 

В качестве ключа (KEY) будет использоваться пятизначный номер заказа, а в качестве значений телефонные номера: courier – номер курьера, client – номер клиента. TTL нам здесь необходимо для указания срока хранения значений.

3) Осталось запустить файл, чтобы сохранить данные заказа:

python3 kvs.py
Если мы больше не захотим, чтобы клиент и курьер беспокоили друг друга, можно будет удалить их данные из хранилища. Информацию о всех доступных методах key-value storage вы найдёте в нашей документации: management API и VoxEngine.

Код сценария

Код, который необходимо вставить в сценарий kvs-scenario, представлен ниже, его можно смело копировать as is:

Полный код сценария

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

Вводим номер заказа

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

store.input += e.tone;

Если звонящий ввел #, сразу соединяем его с оператором:

if (e.tone === '#') {
    store.data.need_operator = "Да";
    store.call.removeEventListener(CallEvents.ToneReceived);
    store.call.handleTones(false);
    callOperator();
    return;
}

Если он ввел последовательность из 5 цифр, вызываем функцию handleInput:

if (store.input.length >= 5) {
    repeatAskForInput = true;
    Logger.write('Получен код ${store.input}. ');
    store.call.handleTones(false);
    store.call.removeEventListener(CallEvents.ToneReceived);
    handleInput(store.input);
    return;
}

Ищем заказ в хранилище

Здесь мы будем сравнивать введенный номер заказа с номером в хранилище, используя метод ApplicationStorage.get(), в качестве ключа используем введенную последовательность:

store.data.order_number = store.input;
Logger.write('Ищем совпадение в kvs по введенному коду: ' + store.input)
inputRecieved = true;
let kvsAnswer = await ApplicationStorage.get(store.input);

Если заказ найден, получаем для него номера клиента и курьера:

if (kvsAnswer) {
    store.data.order_search = 'Заказ найден';
    Logger.write('Получили ответ от kvs: ' + kvsAnswer.value)
    let { courier, client } = JSON.parse(kvsAnswer.value);

Теперь осталось разобраться, кому звонить. Если номер звонящего принадлежит курьеру, будем выполнять переадресацию на клиента, если клиенту – на курьера. В этом нам поможет функция callCourierOrClient:

if (store.caller == courier) {
    Logger.write('Звонит курьер')
    store.callee = client;
    store.data.sub_status = 'Курьер';
    store.data.phone_search = 'Телефон найден';
    callCourierOrClient();
} else if (store.caller == client) {
    Logger.write('Звонит клиент')
    store.callee = courier;
    store.data.sub_status = 'Клиент';
    store.data.phone_search = 'Телефон найден';
    callCourierOrClient();
}

Если номера нет в хранилище, просим перезвонить с другого номера, который указывался при оформлении заказа:

else {
    Logger.write('Номер звонящего не совпадает с номерами, полученными из kvs');
    wrongPhone = true;
    store.data.phone_search = 'Телефон не найден';
    store.input = '';
    store.call.handleTones(true);
    store.call.addEventListener(CallEvents.ToneReceived, dtmfHandler);
    await say(phrases.wrongPhone);
    addInputTimeouts();
}

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

else {
    Logger.write('Совпадение в kvs по введенному коду не найдено');
    store.data.order_search = 'Заказ не найден';
    store.input = '';
    store.call.handleTones(true);
    store.call.addEventListener(CallEvents.ToneReceived, dtmfHandler);
    await say(phrases.wrongOrder);
    Logger.write(`Очищаем таймер ${longInputTimerId}. `);
    addInputTimeouts();
}

Звоним клиенту/курьеру

Переходим непосредственно к звонку клиенту/курьеру, то есть к логике функции callCourierOrClient. Здесь мы сообщим звонящему, что переводим его звонок на курьера/клиента, и включим музыку на ожидание. С помощью метода callPSTN позвоним клиенту или курьеру (в зависимости от того, чей номер был ранее идентифицирован как номер звонящего):

await say(store.data.sub_status === 'Курьер' ? phrases.waitForClient : phrases.waitForCourier, store.call);
const secondCall = VoxEngine.callPSTN(store.callee, store.callid);
store.call.startPlayback('http://cdn.voximplant.com/toto.mp3');

В этот же момент сообщим второй стороне о том, что звонок касается уточнения информации по заказу:

secondCall.addEventListener(CallEvents.Connected, async () => {
    store.data.sub_available = 'Да';
    await say(store.data.sub_status === 'Курьер' ? phrases.courierIsCalling : phrases.clientIsCalling, secondCall);
    store.call.stopPlayback();
    VoxEngine.sendMediaBetween(store.call, secondCall);
});

Обработаем событие дисконнекта:

secondCall.addEventListener(CallEvents.Disconnected, () => {
    store.call.hangup();
});

И оповестим звонящего, если вторая сторона недоступна:

secondCall.addEventListener(CallEvents.Failed, async () => {
    store.data.sub_available = 'Нет';
    store.call.stopPlayback();
    await say(store.data.sub_status === 'Курьер' ? phrases.clientUnavailable : phrases.courierUnavailable, store.call);
    store.call.hangup();
});

За все фразы, который произносит робот, отвечает функция say, а сами фразы перечислены в ассоциативном массиве phrases. В качестве TTS провайдера мы используем Yandex, голос Alena:

function say(text, call = store.call, lang = VoiceList.Yandex.Neural.ru_RU_alena) {
    return new Promise((resolve) => {
        call.say(text, lang);
        call.addEventListener(CallEvents.PlaybackFinished, function callback(e) {
            resolve(call.removeEventListener(CallEvents.PlaybackFinished, callback));
        });
    });
};

Кроме всего прочего, наш сценарий записывает звонки, используя метод record, и показывает, как можно сохранить статистику в базу данных (в нашем коде за это отвечает функция sendResultToDb). Это очень важно для бизнеса, поскольку позволяет анализировать статистику, обеспечивать контроль качества и оперативно решать спорные ситуации, которые могли возникнуть в процессе доставки заказа.


Оригинал статьи


Report Page