Dados de chamadas do serviço VoIP

No capítulo anterior, abordamos os eventos que podem ocorrer ao final de uma chamada:

  1. Nesse tutorial, nós consideramos que a integração receberá um webhook do serviço VoIP assim que a chamada acabar. Um webhook sobre o fim da chamada é enviado à partir do serviço VoIP. Em seguida, o backend da integração precisa encontrar a entidade correspondente (lead, contato ou empresa), ou criar um lead de entrada caso não exista uma entidade vinculada ao número na sua conta Kommo.
  2. Caso exista um contato vinculado ao telefone, você pode adicionar uma nota de chamada com todos os dados necessários da chamada, e vincular com o contato existente via API.
  3. Se a chamada foi encerrada à partir da Kommo, você pode exibir uma janela modal para que um gerente adicione dados adicionais no resultado da chamada. Se um gerente adicionar os dados da chamada e salvar o resultado, os dados são passados ao backend do serviço VoIP, que por sua vez cria uma nota de chamada no cartão da entidade relacionada.
📘

Como mencionamos anteriormente, esses eventos não são obrigatórios e exibem um exemplo de Integração VoIP na Kommo.

Ao receber um webhook indicando o fim de uma chamada, é necessário fazer o registro de todas as informações de chamada relevantes. Porém, para evitar a criação de registros duplicados, nós devemos primeiro checar se a chamada já existe. Caso a chamada não seja encontrada, nós usaremos a API de Registro de cahamadas para criar um novo registro. Caso a chamada já exista, nós iremos simplesmente atualizar a nota de chamada associada por meio de uma requisição de Edição de nota.

Para implementar o registro de chamadas, nós criamos uma classe para coletar todas as chamadas do serviço VoIP, e registrar elas em um repositório de chamadas no banco de dados da integração.

public static function getByCallIdAndKommoAccountId(string $callId, int $kommoAccountId): VoipCalls {
    return VoipCalls::query() 
        ->where('call_id', '=', $callId)
        ->where('kommo_account_id', '=', $kommoAccountId)
        ->first();
}

Para cada chamada, uma tarefa é criada — tanto para salvar uma nova chamada (SaveCallEventTask), ou para atualizar uma tarefa existente (UpdateCallTask). O construtor de tarefas é assim:

public function __construct(
    private int $kommoAccountId,
    private string $toPhone,
    private string $fromPhone,
    private string $callId,
    private CallType $direction,
    private int $status,
    private int $duration,
    private int $startedAt,
    private int $userId,
    private ?string $recording = null
);
public function __construct(
    private int $kommoAccountId,
    private string $callId,
);

Aqui, dois casos de uso são definidos: um para salvar uma nova chamada (SaveCallEventUseCase), e outro para atualizar uma chamada existente (UpdateCallUseCase). Cada caso de uso recebe a tarefa como um input e envia à fila correspondente para ser processada:

public function handle(SaveCallEventTask $task): void
{
    $voipCall = VoipCalls::getByCallIdAndkommoAccountId(
        $task->getCallId(),
        $task->getKommoAccountId()
    );
    $isNew = $voipCall === null;
    $voipCall = $isNew ? VoipCalls::create() : $voipCall;

    $responsibleUser = $voipCall->getResponsibleUserId() ?? task->$getUserId();
    $voipCall
        ->setDirection($task->getDirection())
        ->setCallId($task->getCallId())
        ->setkommoAccountId($task->getkommoAccountId())
        ->setToNumber($task->getToPhone())
        ->setFromNumber($task->getFromPhone())
        ->setDuration($task->getDuration())
        ->setStartedAt($task->getStartedAt())
        ->setResponsibleUserId($responsibleUser)
        ->setRecording($task->getRecording());
    $voipCall->getStatus() ?: $voipCall->setStatus($task->getStatus());
    $voipCall->save();
  
    // Nós tentaremos fazer o registro da chamada com informações básicas 
    // chamando o AddCallWorker do caso de uso 
    // de registro de chamadas AddCallUseCase
    $data = [
        'call_id' => $task->getCallId(),
        'account_id' => $task->getkommoAccountId(),
    ];
    $queueName = $isNew ? AddCallWorker::QUEUE_NAME : UpdateCallNoteWorker::QUEUE_NAME;
    $queueTask = new QueueTask($queueName, $data);
    $this->queue->send($queueTask);
    $task->setSuccess(true);
}
public function handle(UpdateCallTask $task): void
{
    $voipCall = voipCalls::getByCallIdAndKommoAccountId(
        $task->getCallId(),
        $task->getKommoAccountId()
    );

    $call = Call::fromModel($voipCall); // Entidade de chamada

    if ($record = $voipCall->getRecording() ?? '') {
            $record = sprintf(
                '%s/voip/%s/get_call_record/%s',
                $this->appConfig->getBaseUrl(),
                $task->getKommoAccountId(),
                $call->getCallId()
            );
    }
    $call->setRecordLink($record);

    // Chame nossa API de cliente para criar um evento de chamada
    $this->callService->updateCallEvent(
                $call,
                $voipCall->getResponsibleUserId() ?? self::BOT_USER_ID,
                $voipCall->getEntityId(),
                $voipCall->getEntityType(),
                $voipCall->getParentId()
            );	
    $voipCall->setDelivered(DeliveryStatus::COMPLETED);
    $voipCall->save();
    $task->setSuccess(true);
}

Ao salvar um evento de chamada. nos podemos criar uma nova chamada usando o ProcessCallWebhookWorker, ou atualizar uma existente através do UpdateCallNoteWorker. Vamos definir os dois workers:

public function run(array $data, LoggerInterface $logger): void
{
    $taskData = $data['data'];
    $webhookData = FromWebhook::fromArray($data['data']['webhook_data']);
    $call = [
        'call_id' => $webhookData->getCallId() ?? (string)$webhookData->getSessionId(),
        'to_number' => $webhookData->getToPhoneNumber(),
        'from_number' => $webhookData->getFromPhoneNumber(),
        'direction' => $webhookData->getDirection(),
        'duration' => 0,
        'call_result' => 'done',
        'recording' => null,
        'started_at' => $webhookData->getStartTime()->toIso8601ZuluString()
    ];
    $call = Call::fromArray($call);
    $user = VoipUsers::getByExtensionId($webhookData->getExtension());
    $call->setResponsibleUser($user->getUserId())
    $task = new SaveCallEventTask( 
        (int)$taskData['kommo_account_id'], 
        $call->getToNumber(), 
        $call->getFromNumber(), 
        $call->getCallId(), 
        $call->getCallType(), 
        $call->getDirection(), 
        $call->getDuration(), 
        $call->getStartedAt(), 
        $call->getResponsibleUser(), 
        null 
    ); 
    $this->saveCallEventUseCase->handle($task); 
    if (!$task->isSuccess()) 
    { 
        throw BusinessLogicException::create('Erro ao salvar evento de chamada'); 
    } 
} 
public function run(array $data, LoggerInterface $logger): void 
{ 
    $taskData = $data['data']; 
    $task = new UpdateCallTask($taskData['account_id'], $taskData['call_id']); 
    $this->updateCallUseCase->handle($task); 
    if (!$task->isSuccess())
    { 
        throw BusinessLogicException::create('Erro na chamada de atualização'); 
    } 
} 

Adicionalmente, nós devemos gerenciar os webhooks recebidos e submeter a tarefa correspondente apropriada para a fila.

public function handle(ServerRequestInterface $request): ResponseInterface
{
    $data = $request->getParsedBody();
    if ($data) {
        $callWebhook = FromWebhook::fromArray($data);
        $queueTask = new QueueTask(
            ProcessCallWebhookWorker::QUEUE_NAME,
            [
                'kommo_account_id' => (int)$data['kommo_account_id'],
                'webhook_data' => $callWebhook->toArray(),
            ]
        );
        $this->queue->send($queueTask);
    }
}