Инструкция по интеграции Виртуальной АТС МегаФон и Elma365

Возможности интеграции ВАТС МегаФон и Elma365

Настройка интеграции ВАТС и Elma365 не является простой, но при достаточной концентрации вы сможете реализовать её всего за 10 минут. Не стоит пугаться кода, который вы увидите ниже – его нужно лишь скопировать и вставить в нужные поля и всё заработает. Вам не нужны даже минимальные знаний разработки для настройки интеграции.

1. В интерфейсе Elma365 перейдите в раздел АдминистрированиеМодули.

1. Справа вверху нажмите +Модуль

2. Вбейте любое имя виджета, например Виртуальная АТС МегаФон. Описание – любое.

3. Перейдите в управление модулем, вкладка Методы API.

4. Справа нажмите на вкладку Скрипты.

5. Вставьте данный код под скриншотом.

Код для вставки

// Загрузить значение поля "Ключ для авторизации в облачной АТС" из настроек модуля.
function getApiKey() {
   const apiKey = Namespace.params.data.apiKey;
   if (!apiKey) {
       throw new Error('invalid API key');
   }
   return apiKey;  
}
// Загрузить значение поля "Адрес облачной АТС" из настроек модуля.
function getEndpoint() {
   const endpoint = Namespace.params.data.endpoint;
   if (!endpoint) {
       throw new Error('invalid endpoint URL');
   }
   return endpoint;
}
// Выполнить запрос к АТС
async function fetchEndpoint(command: string, body: Object = {}): Promise<FetchResponse> {
   return fetch(getEndpoint(), {
       method: 'POST',   
       headers: {
           'Content-Type': 'application/json',
       },
       body: JSON.stringify({
           cmd: command,
           token: getApiKey(),
           ...body,
       }),
   });
}
// Распарсить контент с типом "application/x-www-form-urlencoded"
function parseUrlEncoded(body: string): Record<string, string> {
    const result: Record<string, string> = {};
    for (const pair of body.split('&')) {
        const keyValue = pair.split('=');
        if (keyValue.length === 2) {
            const key = decodeURIComponent(keyValue[0]);
            const value = decodeURIComponent(keyValue[1]);
            result[key] = value;
        }
    }
    return result;
}
// Сообщение от АТС
interface MegafonWebhookRequest {
    // Тип операции
    cmd: string;
   // Тип события, связанного со звонком
    type: string;
    // Номер телефона клиента (в формате E.164)
    phone: string;
    // Внутренний номер пользователя облачной АТС, если есть
    ext?: string;
}
// Данные, передающиеся с командой "history".
interface MegafonWebhookHistoryCommand extends MegafonWebhookRequest {
    // Общая длительность звонка в секундах
    duration: string;
    // Уникальный id звонка
    callid: string;
    // Ссылка на запись звонка, если она включена в Облачной АТС
    link?: string;
    // Статус входящего или исходящего звонка
    status: string;
}
// Данные, которые сохраняются с каждым звонком.
interface MegafonCallData {
    id: string;
    link: string;
}
// Сконвертировать статуса звонка АТС в статус звонка ELMA365
function toCallDisposition(status: string): VoipCallDisposition {
    switch (status) {
        case 'Success':
            return VoipCallDisposition.Answered;
        case 'Busy':
            return VoipCallDisposition.Busy;
        case 'Missed':
            return VoipCallDisposition.Cancel;
        case 'NotAvailable':
            return VoipCallDisposition.NoAnswer;
        case 'NotAllowed':
        default:
            return VoipCallDisposition.Unknown;
    }
}



// Проверить соединение к телефонии (вызывается по нажатию на кнопку "Проверить соединение" на странице модуля).
async function VoipTestConnection(): Promise<VoipTestConnectionResult> {
   try {
       // Выполняем команду на получение списка пользователей телефонии (можно выбрать любую другую команду для тестирования).
       const response = await fetchEndpoint('accounts');
       const message = await response.json();
       if (response.status !== 200) {
           throw new Error(`${response.status} ${response.statusText}: ${message.error}`);
       }
       return { success: true };
   } catch (e) {
       return {
           success: false,
           failReason: e.message,
       };
   }
}


// Обработать запрос от провайдера IP-телефонии.
async function VoipParseWebhookRequest(request: FetchRequest): Promise<VoipWebhookParseResult> {
    try {
        const headers = request.headers;
        const contentType = headers ? headers['Content-Type'] : undefined;
        if (contentType !== 'application/x-www-form-urlencoded') {
            throw new Error(`Expected Content-Type to be "application/x-www-form-urlencoded" (got: "${contentType}"`);
        }
        if (typeof request.body !== 'string') {
            throw new Error('Expected request body to be string')
        }
        let event: VoipWebhookRequest | undefined;
        let callRecord: VoipCallRecord | undefined;
        let response: HttpResponse | undefined;
        const data = <MegafonWebhookRequest><unknown> parseUrlEncoded(request.body);
        switch (data.cmd) {
            case 'event': {
// Облачная АТС отправляет в вашу CRM уведомления о событиях входящих звонков пользователям: появлении, принятии или завершении звонка. Команда может быть использована для
// отображения всплывающей карточки клиента в интерфейсе CRM.
                const dstPhone = data.ext ?? '';
                switch (data.type) {
                    case 'INCOMING': {
// Пришел входящий звонок (в это время у менеджера должен начать звонить телефон).
                        event = {
                            event: VoipWebhookEvent.NotifyStart,
                            direction: VoipCallDirection.In,
                            dstPhone: dstPhone,
                            srcPhone: data.phone,
                            disposition: VoipCallDisposition.Unknown,
                        };
                    } break;
                    case 'ACCEPTED': {
 // Звонок успешно принят (менеджер снял трубку). В этот момент можно убрать всплывающую карточку контакта в CRM.
                        event = {
                            event: VoipWebhookEvent.NotifyAnswer,
                            direction: VoipCallDirection.In,
                            dstPhone: dstPhone,
                            srcPhone: data.phone,
                            disposition: VoipCallDisposition.Unknown,
                        }
                    } break;
                    case 'COMPLETED': {
// Звонок успешно завершен (менеджер или клиент положили трубку после разговора).
                        event = {
                            event: VoipWebhookEvent.NotifyEnd,
                            direction: VoipCallDirection.In,
                            dstPhone: dstPhone,
                            srcPhone: data.phone,
                            disposition: VoipCallDisposition.Unknown,
                        }
                    } break;
                    case 'CANCELLED': {
// Звонок сброшен (клиент не дождался пока менеджер снимет трубку. Либо, если это был звонок сразу на группу менеджеров, на звонок мог ответить кто-то еще).
                        event = {
                            event: VoipWebhookEvent.NotifyEnd,
                            direction: VoipCallDirection.In,
                            dstPhone: dstPhone,
                            srcPhone: data.phone,
                            disposition: VoipCallDisposition.Cancel, 
                        }
                    } break;
                    case 'OUTGOING': {
// Менеджер совершает исходящий звонок (в это время облачная АТС пытается дозвониться до клиента).
                        event = {
                            event: VoipWebhookEvent.NotifyStart,
                            direction: VoipCallDirection.Out,
                            dstPhone: dstPhone,
                            srcPhone: data.phone,
                            disposition: VoipCallDisposition.Unknown, 
                        }
                    } break;
                    default: throw new Error(`Unknown event type "${data.type}"`)
                }
            } break;
            case 'history': {
// После успешного звонка в CRM отправляется запрос с данными о звонке и ссылкой на запись разговора.
// Команда может быть использована для сохранения в данных ваших клиентов истории и записей входящих и исходящих звонков.
                const cmd = <MegafonWebhookHistoryCommand> data;
                callRecord = {
                    srcPhone: cmd.phone,
                    dstPhone: cmd.ext ?? '',
                    direction: cmd.type === 'out' ? VoipCallDirection.Out : VoipCallDirection.In,
                    duration: parseInt(cmd.duration),
// Данные из этого поля будут доступны в функции VoipGetCallLink
                    call: <MegafonCallData>{
                        link: cmd.link,
                        id: cmd.callid,
                    },
                    disposition: toCallDisposition(cmd.status),
                }
            } break;
        }
        return { 
            event: event,
            callRecord: callRecord,
            response: response,
        };
    } catch (e) {
        return {
            response: new HttpResponse()
                .status(400)
                .content(JSON.stringify({
                    error: e.message ?? 'internal error',
                }))
        };
    }
}

// Получить список пользователей от провайдера IP телефонии.
// Это внутренние пользователи - внутренние номера/SIPID, нужно для сопоставления между пользователями IP-провайдера и пользователями ELMA365
async function VoipGetMembers(): Promise<VoipMember[]> {
    const response = await fetchEndpoint('accounts');
    if (response.status !== 200) {
        throw new Error(`received error response ${response.status}: ${response.statusText}`);
    }
    interface MegafonVoipUser {
        name: string;
        ext: string;
    }
    const voipUsers = <MegafonVoipUser[]> (await response.json());
    return voipUsers.map(user => ({
        id: user.ext,
        label: user.name,
    }));
}

// Сгенерировать звонок
async function VoipGenerateCall(srcPhone: string, dstPhone: string): Promise<void> {
    const response = await fetchEndpoint('makeCall', {
        phone: dstPhone,
        user: srcPhone,
    });
    if (response.status !== 200) {
        throw new Error(`received error response ${response.status}: ${response.statusText}`);
    }
}

// Получить ссылку на запись разговора
async function VoipGetCallLink(callData: MegafonCallData): Promise<string> {
    return callData.link;
}
 

После этого слева вверху нажмите Сохранить, Опубликовать и вернитесь обратно.

6. Перейдите в Настройки и через кнопку +Добавить создайте два поля. Внутри создания используйте только поля Отображаемое имя и Имя свойства. Друг за другом создайте:

  • Первый параметр. Отображаемое имя: Ключ для авторизации в Виртуальной АТС. Имя свойства: apiKey.
  • Второй параметр. Отображаемое имя: Адрес Виртуальной АТС. В качестве названия свойства укажите endpoint.

7. Перейдите в раздел в карточку модуля, кликнув на его название слева наверху. Теперь нужно настроить связь между Elma365 и МегаФон.

Перейдите в вашу Виртуальную АТС МегаФон, раздел НастройкиИнтеграция с CRM. В конце списка выберите карточку «Ваша CRM».

Теперь нужно вставить 2 параметра АТС в CRM и наоборот.

В интерфейсе Elma365 скопируйте адрес и ключ и вставьте их в ВАТС МегаФон. В ВАТС МегаФон скопируйте ключ АТС и вставьте в Elma365. В качестве адреса облачной АТС нужно вписать адрес вашей АТС (вида https://vats123456.megapbx.ru) и добавить /sys/crm_api.wcgp. Например, https://vats123456.megapbx.ru/sys/crm_api.wcgp.

Нажмите «Сохранить» в ВАТС МегаФон и в интерфейсе Elma365 (но именно верхнюю кнопку «Сохранить»).

8. Нажмите «Проверить подключение» и убедитесь, что всё работает.

9. Нажмите «Настроить» и сопоставьте пользователей CRM и АТС. Это необходимо, чтобы правильно вести статистику и показывать информацию правильным сотрудникам.

Поздравляем! Интеграция настроена.