Инструкция по интеграции Виртуальной АТС МегаФон и 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 и АТС. Это необходимо, чтобы правильно вести статистику и показывать информацию правильным сотрудникам.
Поздравляем! Интеграция настроена.