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

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

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

Естественно, в рамках нашей статьи мы не будем «выдумывать» целый сервис, но попробуем сделать систему общения с персоналом интернет-магазина удобной и защищенной от кражи персональных данных недобросовестными исполнителями. А поможет нам в этом Telecom API от компании MTT.

Сегодня мы с вами:

  • используем API, для умной переадресации вызова;

    • скроем номер звонящего, от некоторых сотрудников сервиса;

    • разберемся с добавочными номерами;

    • сделаем аудио сообщения из строки текста;

  • обработаем вызовы REST API на сайте с помощью PHP;

  • напишем на Kotlin простенький макет приложения для звонка.

Оглавление

Постановка задачи

Как я уже говорил, у нас нет цели покрыть все функции нашего выдуманного сервиса, мы сосредоточимся только на API для звонков, а остальное закроем макетами и заглушками.

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

Процесс заказа и получения товара
Процесс заказа и получения товара

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

Итак мы хотим, чтобы наша Бабушка для заказа товаров нажимала всего одну кнопку «Позвонить в магазин». А умный сервис сам будет переводить её на сотрудника, отвечающего за текущий шаг обработки заказа.

  • Шаг 1 — менеджер примет у Бабушки список товаров для заказа.

    • Для учебных целей представим, что наши менеджеры очень порядочные и ответственные люди. Мы им покажем полный номер телефона Бабушки, на всякий случай.

  • Шаг 2 — кладовщик подскажет Бабушке, когда её заказ соберут на складе. 

    • Кладовщику мы не покажем номер звонящего.

  • Шаг 3 — курьер согласует с Бабушкой время доставки и прочие детали.

    • Курьеру тоже не покажем номер телефона. Он будет звонить ей через свое специальное приложение для курьеров.

Теперь, когда мы уверены, что Бабушке не будут звонить мошенники и предлагать  «вундер-вафельницу» по цене звездолета, можно смело перейти к знакомству с API.

TELECOM API MTT

Рабочей лошадкой для нашей статьи станет API для управления входящим вызовом с добавочными номерами (Smart УВВ), от компании MTT. Если вы никогда не сталкивались с IP телефонией, то не пугайтесь, всё просто.

По сути мы будем три раза звонить на определенный телефон, выбирая разные добавочные номера. После звонка, API отправляет POST запрос на заранее заданный URL, а мы в ответном сообщении задаем сценарий переадресации (или отбоя).

Давайте взглянем на схему.

Схема передачи данных
Схема передачи данных

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

Создание premedia

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

Не переживайте, нам не понадобится микрофон, API позволяет не только загрузить готовый медиафайл, но и синтезировать речь из текста. Хотя при желании вы бы могли загрузить готовый файл в формате mp3. Но мы воспользуемся именно генерацией аудио из текста.

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

Но мы воспользуемся именно генерацией из текста.

Пора создать первый файл, условно звонок менеджеру.

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

Начнем с авторизации. Авторизация поможет убедиться, что вам открыта возможность выполнить метод API. Авторизация будет по логину и паролю (Basic Auth), которые вам выдадут специалисты MTT.

Создайте запрос со следующими параметрами
(или просто скачайте коллекцию в Postman):

Точка подключения: https://gapi.mtt.ru:6443/v1/api

Mетод HTTP - POST

Название метода — createCustomerPrompt

Пример тела запроса:

{
    "id": "1",
    "jsonrpc": "2.0",
    "method": "createCustomerPrompt",
    "params":
    {
        "customer_name" : "ваш логин МТТ",
        "prompt_name": "название медиафайла.mp3, например habr.mp3",
        "prompt_type" : "text",
        "prompt_file_contents": "Соединяем с менеджером"
    }
}

Пример успешного ответа.

{
   "jsonrpc":"2.0",
   "id":55585,
   "result":
   {
      "success":true
   }
}

Запрос простой, и параметры очевидны. Их подробное описание параметров есть в документации к API.

Для нашего сценария нам надо сделать еще 3 медиафайла:

  • «Соединяем с кладовщиком» (услышит Бабушка).

  • «Соединяем с курьером» (услышит бабушка).

  • «Соединяем с клиентом» (услышит любой из трех сотрудников).

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

Убедимся, что все наши медиафайлы успешно созданы в хранилище МТТ, для этого вызовем метод getCustomerPrompts.

Точка подключения: https://gapi.mtt.ru:6443/v1/api

Mетод HTTP - POST

Название метода — getCustomerPrompts

Пример тела запроса:

{
    "id": "1",
    "jsonrpc": "2.0",
    "method": "getCustomerPrompts",
    "params":
    {
        "customer_name": “логин МТТ”
    }
}

Пример успешного ответа для нашего случая:

{
    "jsonrpc": "2.0",
    "id": "1",
    "result": {
{
    "id":"1",
    "jsonrpc":"2.0",
    "method": "setSipCallControlURL",
    "params": {
        "sip_id": "номер телефона на который звоним в формате - 78005110559",

        "prompts_list": [
            {
                "id": 39577,
                "name": "TTS_habr_manager.mp3",
                "file_id": "da2ba2ad46344d7a6ee1966463832785"
            },
            {
                "id": 39578,
                "name": "TTS_habr_store.mp3",
                "file_id": "aa7ba2ad46344d7a6ee1966463831246"
            },
            {
                "id": 39579,
                "name": "TTS_habr_courier.mp3",
                "file_id": "ca3ba2ad46344d3a6ee1966463831240"
            },
            {
                "id": 39580,
                "name": "TTS_habr_staff.mp3",
                "file_id": "aa1ba2ad46344d0a6ee1966463831241"
            }
        ]
    }
}

Этот запрос еще проще, но на всякий случай вот ссылка на подробное описание

Умное управление входящим вызовом (smart УВВ)

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

Но прежде всего важное уточнение: убедитесь, что на вашем сервере или хостинге не заблокировано подключение с параметром «user-agent» = «fasthttp».

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

API smart УВВ позволяет нам при необходимости назначить вызов разных URL для разных добавочных номеров. но мы для простоты сделаем один адрес для заглушки, поэтому воспользуемся методом setSipCallControlURL (он также есть в коллекции Postman).

Точка подключения: https://api.mtt.ru/ipcr/

Mетод HTTP - POST

Название метода — setSipCallControlURl

Пример тела запроса:

{
    "id":"1",
    "jsonrpc":"2.0",
    "method": "setSipCallControlURL",
    "params": {
        "sip_id": "номер телефона на который звоним в формате - 79000000009",
         "url": "http://testt.api.ruu/call.php - адрес на который отправится запрос (это не настоящий адрес заглушки)"
    }
}

Пример успешного ответа для нашего случая:

{
    "jsonrpc": "2.0",
    "id": "1",
    "result": {
        "success": 1
    }
}

Теперь, когда Бабушка позвонит на специальный номер телефона через приложение, сервис МТТ отправит следующий POST запрос на нашу заглушку. 

{
    "id": "1",
    "jsonrpc": "2.0",
    "method": "getcontrolcallfollowme",
    "params": {
        "h323_conf_id": " BC5F236C 5AD211E9 81BA5CB9 01FED6FC",
        "numberA": "70000000006 - с которого звоним",
        "sip_id": "70000000002 - на который звоним",
        "extension": "0001 - добавочный"
    }
}

Подробное описание запроса можно посмотреть в документации.

Запрос есть, а значит должен быть ответ. Давайте напишем простую REST API заглушку.

Заглушка и макет

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

REST API PHP

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

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

Алгоритм работы заглушки API
Алгоритм работы заглушки API

Код будет простым, мы возьмем пример ответа из документации, и в зависимости от добавочного номера, будем менять в нём номер телефона для переадресации и id медиафайлов проигрываемых Бабушке.

Код на PHP я спрячу под спойлер. Я прокомментировал основные блоки, поэтому не буду отдельно на нём останавливаться.

Показать код
<?php
    header("Access-Control-Allow-Origin: *");
    header("Access-Control-Allow-Headers: access");
    header("Access-Control-Allow-Methods: POST");
    header("Access-Control-Allow-Credentials: true");
    header('Content-Type: application/json; charset=utf-8');
    
    # медиа файл для сотрудника
    $file_to_employee = "7876cbe22572bf3828df7d26ed7c3ee0";

    # Читаем входящий запрос
    $data = json_decode(file_get_contents("php://input"));
    $extension = (int)$data->params->extension;
    $id = (int)$data->id;
        # Анализируем добавочный и выбираем кому звонить и какой медиа файл проигрывать для Бабушки    # 1 - звонок менеджеру, 2 - кладовщику, 3 - курьеру, по умолчанию звоним менеджеру пусть разбирается
        switch ($extension) {
        case 1:
            $REDIRECT_NUMBER = "79000000002";
            $file_to_granny = "7876cbe22572bf3828df7d26ed7c3ee1";
            break;
        case 2:
            $REDIRECT_NUMBER = "79000000003";
            $file_to_granny = "f8fe95347d176b14e3925dbe590303c4";
            break;
        case 3:
            $REDIRECT_NUMBER = "79000000004";
            $file_to_granny = "f8fe95347d176b14e3925dbe590303c4";
            break;
        default:
            $REDIRECT_NUMBER = "79000000002";
            $file_to_granny = "f8fe95347d176b14e3925dbe590303c4";
            break;
    }
    # Формируем ответ
     $response_call = array(
        "jsonrpc" => "2.0",
        "id" => "1",
        "result" => array(
            "redirect_type" => 1,
            "event_URL" => "http://www.XXXXX.ru/ адрес где будет обработчик статистки запросов (не использую  в статье)",
            "event_extended" => "Y",
            "client_id" => "1235",
            "file_to_A" => $file_to_granny,
            "file_to_B" => $file_to_employee,
            "masking" => "Y",
            "followme_struct" => array(
                1,
                array(
                    array(
                        "I_FOLLOW_ORDER" => 1,
                        "PERIOD" => "always",
                        "PERIOD_DESCRIPTION" => "Always",
                        "TIMEOUT" => "30",
                        "ACTIVE" => "Y",
                        "NAME" => "79000000001",
                        "REDIRECT_NUMBER" => $REDIRECT_NUMBER
                    )
                )
            )
        )
    );
     echo json_encode($response_call, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
?>

Дальше файл необходимо разместить на каком-нибудь хостинге или сервере с поддержкой PHP.

Пример ответа для добавочного 0001:

{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "redirect_type": 1,
        "event_URL": "http://testrest.XXXXX.ru/",
        "event_extended": "Y",
        "client_id": "Babushka",
        "masking": "Y",
        "followme_struct": [
            1,
            [
                {
                    "I_FOLLOW_ORDER": 1,
                    "PERIOD": "always",
                    "PERIOD_DESCRIPTION": "Always",
                    "TIMEOUT": "30",
                    "ACTIVE": "Y",
                    "NAME": "redirect",
                    "REDIRECT_NUMBER": "7900000000X"
                }
            ]
        ]
    }
}

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

Мобильное приложение Android kotlin

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

В приложении будет одно текстовое поле с информацией о статусе заказа и одна кнопка «позвонить».

Чтобы не усложнять приложение, мы будем использовать «встроенную звонилку», но в качестве домашнего задания вы можете реализовать звонки непосредственно из приложения.

Посмотрим на сценарий работы макета приложения:

Действующее лицо: Бабушка

Предусловия: Счетчик = 1, текст в текстовом поле:  «Сделать или уточнить заказ», текст кнопки:  «Позвонить менеджеру».

  1. Открываем приложение.

  2. Нажимаем на кнопку звонка.

  3. Откроется окно стандартного приложения для звонков смартфона.

    1. В поле номера будет введен номер магазина,,000N (N — значение счетчика).

  4. Звоним в магазин — нас переключают на сценарий заглушки для добавочного 0001.

  5. Возвращаемся  в приложение (можно кнопкой назад).

  6. Счетчик увеличивается на 1.

    1. Текст в текстовом поле:  «Заказ комплектуется».

    2. Текст кнопки -  «Позвонить кладовщику».

  7. Еще раз звоним — все будет аналогично звонку на прошлом шаге, но добавочный теперь 0002.

  8. Возвращаемся в приложение.

  9. Счетчик увеличивается на 1.

    1. В текстовом поле:  «Заказ передан в доставку».

    2. Текст кнопки -  «Позвонить менеджеру».

  10. Звоним последний раз — все будет аналогично звонку на прошлом шаге, но добавочный теперь 0003.Если честно, до этой статьи я вообще не пользовался Android Studio и не писал программ на Kotlin, но наш макет приложения будет примитивнее некоторых бактерий, поэтому собрать его сможет любой человек с минимальными знаниями в программировании.

  11. Возвращаемся  в приложение.

  12. Счетчик > 3 .

  13. Счетчик сброшен, установлено значение  = 1 .

  14. Текст кнопки и поля, как в предусловиях.

  15. Сценарий успешно выполнен (или можно пройти еще один цикл).

Если бы мы реализовывали полноценное приложение, то стоило подключить базу данных, в ней хранить данные об идентификаторе звонка и статусе заказа. И вместо счетчика в приложении хранить реальные данные о статусе заказа, обновляемые по запросу на сервер. Но я решил не усложнять, потому что основная цель статьи — показать взаимодействие с Telecom API.

Перейдем непосредственно к коду приложения:

Вы можете скачать проект с GitHub или выполнить следующие шаги:

  • откройте Android Studio;

  • создайте новый проект с шаблоном «Empty Activity»;

  • назовите проект HabrCall (если назовёте иначе, то надо в коде поменять 1 строку отмеченную комментарием);

  • выберите версию API (я брал версию API для Android 5.1)

  • откройте файл <папка с проектом>/app/src/main/java/com/example/habrcall/MainActivity.kt;

  • замените код шаблона на код под спойлером;

Показать код
package com.example.habrcall; //замените habrcall на название вашего проекта

import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView


class MainActivity: AppCompatActivity() {
    private var clickCount = 1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        var buttonCall = findViewById<Button>(R.id.button_call)
        var textViewCall = findViewById<TextView>(R.id.text_call)
        buttonCall.setOnClickListener {
            // звонок на разные добавочные (номер выдуман)
            val dialIntent = Intent(Intent.ACTION_DIAL)
            dialIntent.data = Uri.parse("tel:+79000000001;000$clickCount")
            startActivity(dialIntent)
            clickCount++
            // измененеи текста в зависимости от этапа обработки заказа.y
            when (clickCount) {
                2 -> {
                    textViewCall.text="Заказ комплектуется"
                    buttonCall.text="Позвонить кладовщику"
                }
                3 -> {
                    textViewCall.text="Заказ комплектуется"
                    buttonCall.text="Позвонить курьеру"
                }
                else ->
                {
                    clickCount = 1 // reset count
                    textViewCall.text="Сделать или уточнить заказ"
                    buttonCall.text="Позвонить менеджеру"
                }
            }

        }
    }
}
Пример тела запроса:
{
    "id": "1",
    "jsonrpc": "2.0",
    "method": "createCustomerPrompt",
    "params":
    {
        "customer_name" : "ваш логин МТТ",
        "prompt_name": "название медиафайла.mp3, например habr.mp3",
        "prompt_type" : "text",
        "prompt_file_contents": "Соединяем с менеджером"
    }
}
Пример успешного ответа.
{
   "jsonrpc":"2.0",
   "id":55585,
   "result":
   {
      "success":true
   }

Пример тела запроса:
{
    "id": "1",
    "jsonrpc": "2.0",
    "method": "createCustomerPrompt",
    "params":
    {
        "customer_name" : "ваш логин МТТ",
        "prompt_name": "название медиафайла.mp3, например habr.mp3",
        "prompt_type" : "text",
        "prompt_file_contents": "Соединяем с менеджером"
    }
}
Пример успешного ответа.
{
   "jsonrpc":"2.0",
   "id":55585,
   "result":
   {
      "success":true
   }

  • откройте файл <папка с проектом>app/src/main/res/layout/activity_main.xml;

  • замените код шаблона на код под спойлером;

Показать код
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text_call"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Сделать или уточнить заказ"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:textSize="25dp"
        />

    <Button
        android:id="@+id/button_call"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Позвонить менеджеру"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

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

Собираем всё вместе

Давайте пройдемся по шагам.

Шаг 1. Звоним менеджеру, чтобы сделать заказ:

Шаг 1. Скриншоты из приложения
Шаг 1. Скриншоты из приложения

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

Отправка кодСкриншотыСкриншотыа для вызова
Отправка кодСкриншотыСкриншотыа для вызова

После того как позвоним:

  • сервис МТТ вызовет нашу заглушку;

  • заглушка отправит данные в ответ;

  • Бабушка прослушает голосовое сообщение «Ожидайте менеджера»;

  • менеджер видит номер Бабушки;

  • менеджер возьмет трубку и прослушает «Ожидайте клиента«»

  • абоненты говорят.

Шаг 2. Звоним на склад уточнить статус комплектации

Шаг 2. Скриншоты из приложения
Шаг 2. Скриншоты из приложения

После звонка:

  • сервис МТТ вызовет заглушку;

  • заглушка отправит данные в ответ;

  • Бабушка прослушает голосовое сообщение «Ожидайте кладовщика».

  • кладовщик видит номер, купленный у МТТ (на который Бабушка звонит);

  • кладовщик возьмет трубку и прослушает «Ожидайте клиента»;

  • Абоненты говорят.

Шаг 3. Звоним курьеру уточнить время доставки

Шаг 3. Скриншоты из приложения
Шаг 3. Скриншоты из приложения

После звонка:

  • Весь цикл такой же как и на прошлом шаге, но меняется добавочный сотрудника магазина и голосовое сообщение для бабушки («Ожидайте курьера»).

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

Молодцы тимуровцы!
Выучили азы умного управления вызовом с помощью API.

Комментарии (2)


  1. Chuvi
    10.06.2022 11:59

    Поскольку мы очень заботливые тимуровцы, то не хотим, чтобы Бабушка или сотрудник сервиса просто слушали гудки

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


    1. BosonBeard Автор
      10.06.2022 13:42
      +1

      Вы понимаете, что это кейс для туториала?

      Причем подобранный с некоторой долей юмора

      Это звучит примерно так же как:

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

      -Я в ковид пробовал шинковать картоху и порезался 300 раз, поэтому поварские ножи зло.

      UPD: я к тому, что применение API на практике, это на усмотрение разработчиков. Я уверен существует множество кейсов, в которых вы не потеряете свои кровные.