Ir para o conteúdo principal

WhatsApp (Meta) (Edição)

Visão Geral da Integração WhatsApp Business API

Descrição

O projeto OMN utiliza a API oficial do WhatsApp Business (Graph API v23.0) para comunicação, permitindo envio e recebimento de mensagens, gerenciamento de templates e processamento de webhooks.

1. Configuração e Inicialização

1.1 Constructor

Descrição: Inicializa a classe Whatsapp com cliente HTTP configurado para Graph API e injeta serviço S3 para gerenciamento de mídia.

Entrada:

constructor(@Inject(S3Service) private readonly s3Service: S3Service)

Implementação:

this.http = axios.create({
  baseURL: 'https://graph.facebook.com/v23.0/',
})

Retorno:

Whatsapp // Instância da classe

2. Operações de Mídia

2.1. Obtenção da midia

Descrição: Obtém mídia do WhatsApp através do ID, faz download e upload para S3, retornando URL acessível.

Entrada:

{
  id: string,        // ID da mídia no WhatsApp
  whatsappId: string, // Phone Number ID
  token: string      // Access Token
}

Implementação:

// 1. GET /{id}?phone_number_id={whatsappId}
const response = await this.http.get(url, {
  headers: { Authorization: `Bearer ${token}` }
})

// 2. Download da mídia
const media = await this.http.get(metaUrl, {
  headers: { Authorization: `Bearer ${token}` },
  responseType: 'arraybuffer'
})

// 3. Upload para S3
return this.s3Service.upload(media.data, mimetype)

Retorno:

string // URL da mídia (S3 ou original)

2.2. Upload de Mídia

2.2.1 uploadMedia

Descrição: Faz upload de arquivo para WhatsApp, com conversão automática de áudio para formato compatível.

Entrada:

{
  mediaUri: string,   // URI do arquivo
  whatsappId: string, // Phone Number ID
  token: string       // Access Token
}

Implementação:

// 1. Obter buffer e detectar tipo
let buffer = await Utils.getBuffer(mediaUri)
let mimeType = mime.lookup(extension)

// 2. Conversão de áudio se necessário
if (fileType?.mime.startsWith('audio')) {
  const { buffer: whatsappAudioBuffer, mimeType: whatsappAudioMimeType } =
    await AudioService.getInstance().convertAudioBufferToWhatsAppFormat(buffer)
  buffer = whatsappAudioBuffer
  mimeType = whatsappAudioMimeType
}

// 3. Upload via FormData
const form = new FormData()
form.append('file', blob, fileName)
form.append('messaging_product', 'whatsapp')

const response = await this.http.post(`${whatsappId}/media`, form, {
  headers: { Authorization: `Bearer ${token}` }
})

Retorno:

{
  id: string,        // ID da mídia no WhatsApp
  filename?: string  // Nome do arquivo (se aplicável)
} | null

3. Envio de Mensagens

3.1. Payloads

3.1.1. toWhatsappMessagePayload

Descrição: Converte payload interno para formato da API do WhatsApp, processando texto, mídia e contexto.

Entrada:

{
  body?: string,           // Texto da mensagem
  to: string,             // Destinatário
  mediaUri?: string,      // URI da mídia
  interactive?: object,   // Elementos interativos
  template?: object,      // Template
  quotedMessage?: {       // Mensagem citada
    id: string
  }
}

Implementação:

let payload: SendMessageWhatsappPayload = { 
  messaging_product: 'whatsapp', 
  to 
}

// Contexto para mensagem citada
if (quotedMessage?.id) {
  payload.context = { message_id: message.quotedMessage.id }
}

// Processamento por tipo
if (body) {
  payload = { ...payload, type: 'text', text: { body } }
}

if (mediaUri) {
  const { type, fileName } = Utils.getFileInfo(mediaUri)
  payload = { ...payload, type, [type]: { link: mediaUri } }
  if (type === 'document') payload.document.filename = fileName
}

Retorno:

{
  messaging_product: 'whatsapp',
  to: string,
  type?: string,
  text?: { body: string },
  context?: { message_id: string },
  audio?: { id?: string; link?: string },
  document?: { id?: string; link?: string; filename?: string },
  image?: { id?: string; link?: string },
  sticker?: { id?: string; link?: string },
  video?: { id?: string; link?: string }
}

3.2. Envio de Mensagem

3.2.1. sendMessage

Descrição: Envia mensagem via WhatsApp Business API, com upload automático de mídia e fallback para link de texto.

Entrada:

{
  message: SendMessagePayload, // Payload da mensagem
  whatsappId: string,         // Phone Number ID
  token: string               // Access Token
}

Implementação:

// 1. Transformar payload
const payload = this.toWhatsappMessagePayload(message)

// 2. Upload de mídia se necessário
if (isMedia && link) {
  let media = await this.uploadMedia(link, whatsappId, token)
  if (media) {
    payload[type] = media
  } else {
    // Fallback para link de texto
    delete payload[type]
    payload.type = 'text'
    payload.text = { 
      body: `Por favor, clique no link para realizar o download do arquivo: \n${link}` 
    }
  }
}

// 3. Enviar mensagem
const response = await this.http.post(`${whatsappId}/messages?access_token=${token}`, payload)

// 4. Delay para mídia
if (isMedia) await Utils.delay(parseInt(process.env.MEDIA_MESSAGE_DELAY_MS || '1000', 10))

Retorno:

AxiosResponse // Response da API do WhatsApp

3.3. Envio de Template

3.3.1. sendTemplateMessage

Descrição: Envia mensagem de template para iniciar conversação, com validação de variáveis e fallback para userName/companyName.

Entrada:

{
  to: string,                    // Destinatário
  template?: string,             // Nome do template
  templateVariables?: string[],  // Variáveis do template
  userName?: string,             // Nome do usuário (fallback)
  companyName?: string          // Nome da empresa (fallback)
}

Implementação:

// 1. Validação de variáveis
if (!payload?.templateVariables && !(payload.userName && payload.companyName))
  throw new BadRequestException("You must provide 'templateVariables' or both 'userName' and 'companyName'")

// 2. Preparar variáveis
if (!payload?.templateVariables) 
  payload.templateVariables = [payload.userName, payload.companyName]

// 3. Criar parâmetros
const parameters = []
payload.templateVariables.forEach((v) => parameters.push({ type: 'text', text: v }))

// 4. Montar payload do template
const templatePayload = {
  messaging_product: 'whatsapp',
  to: payload.to,
  type: 'template',
  template: {
    name: payload?.template || WhatsappDefaultTemplateNames.contato_cliente,
    language: { code: 'pt_BR' },
    components: [{
      type: 'body',
      parameters,
    }],
  },
}

// 5. Enviar
return this.http.post(`${whatsappId}/messages`, templatePayload, {
  headers: { Authorization: `Bearer ${token}` }
})

Retorno:

AxiosResponse<{
  messaging_product: string,
  contacts: Array<{ input: string, wa_id: string }>,
  messages: Array<{ id: string }>
}>

4. Gerenciamento de Templates

4.1. Obtenção do template

4.1.1 getTemplates

Descrição: Lista todos os templates de mensagem aprovados na conta WhatsApp Business.

Entrada:

{
  wabaId: string, // WhatsApp Business Account ID
  token: string   // Access Token
}

Implementação:

const templates = (
  await this.http.get(`${wabaId}/message_templates`, {
    headers: { Authorization: `Bearer ${token}` }
  })
)?.data?.data

return templates || []

Retorno:

Array<{
  name: string,
  components: Array<{
    type: string,
    format?: string,
    text?: string,
    example?: object
  }>,
  language: string,
  status: string,
  category: string,
  id: string
}>

4.2. Criação do template

4.2.1 createTemplate

Descrição: Cria novo template de mensagem no WhatsApp Business, com verificação de duplicatas e criação automática do template padrão.

Entrada:

{
  wabaId: string,                        // WABA ID
  token: string,                         // Access Token
  template?: CreateWhatsappTempatePayload // Template personalizado
}

Implementação:

// 1. Verificar template padrão se não fornecido
if (!template) {
  const defaultTemplate = await this.getDefaultTemplate(wabaId, token)
  if (defaultTemplate.length !== 0) return
  template = this.generate360DefaultTemplate()
} else {
  // 2. Verificar duplicatas
  const userTemplates = await this.getTemplates(wabaId, token)
  if (userTemplates.find((t) => t.name === template.name))
    throw new ConflictException('Template name is already in use')
}

// 3. Criar template
await this.http.post(`${wabaId}/message_templates`, template, {
  headers: { Authorization: `Bearer ${token}` }
})

Retorno:

void // Sucesso ou lança exceção

4.3. Template Padrão

4.3.1 generate360DefaultTemplate

Descrição: Gera estrutura do template padrão "contato_cliente" com variáveis para nome do usuário e empresa.


Implementação:

return {
  name: WhatsappDefaultTemplateNames.contato_cliente,
  category: TemplateCategories.UTILITY,
  language: 'pt_BR',
  components: [{
    type: 'BODY',
    text: 'Olá {{1}} !\n\nFalo em nome da {{2}} e estou entrando em contato para continuarmos nossa conversa.\n\nAguardo o seu retorno.',
    example: {
      body_text: [['Bruno', 'Alterdata']],
    },
  }],
}

Retorno:

{
  name: string,
  category: 'UTILITY',
  language: 'pt_BR',
  components: Array<{
    type: 'BODY',
    text: string,
    example: {
      body_text: string[][]
    }
  }>
}

5. Processamento de WebHooks

5.1. Recebimento de Mensagens

5.1.1 whatsappPayloadToIncomingMessage

Descrição: Converte webhook do WhatsApp para formato interno, processando texto, mídia e mensagens citadas.

Entrada:

{
  payload: WhatsappMessageDto,  // Webhook do WhatsApp
  session: SessionDocument,     // Sessão ativa
  whatsapp: Whatsapp           // Instância do WhatsApp
}

Implementação:

// 1. Extrair informações da mensagem
const { message } = getMessagesInfo(payload)

// 2. Criar objeto base
const incomingMessage: IncomingMessageDto = {
  uid: message.id,
  sessionId: session._id,
  platformConfigId: session.platformConfigId,
  channelId: session.channelId,
  from: {
    uid: session.client.providerId,
    userName: session.client.name,
  },
  message: {
    body: message?.text?.body || '',
  },
  source: session.type,
  type: WebhookTypesEnum.MESSAGE,
}

// 3. Processar mensagem citada
if (message?.context?.id) {
  incomingMessage.message.quoted = { id: message?.context?.id }
}

// 4. Processar mídia
if (message.image || message.document || message.voice || message.audio || message.sticker || message.video) {
  const type = message?.type
  const { whatsappId, token } = session.channel.params as WhatsappConfigParams
  incomingMessage.message.media = {
    uri: await whatsapp.getMediaLink(message[type].id, whatsappId, token),
    contentType: message[type].mime_type,
    fileName: '',
  }
}

Retorno:

{
  uid: string,
  sessionId: ObjectId,
  platformConfigId: string,
  channelId: string,
  from: {
    uid: string,
    userName: string
  },
  message: {
    body: string,
    quoted?: { id: string },
    media?: {
      uri: string,
      contentType: string,
      fileName: string
    }
  },
  source: MessageServiceEnum,
  type: WebhookTypesEnum.MESSAGE
}
5.2. whatsappPayloadToStatus (Mapeamento de status)

Descrição: Converte webhook de status do WhatsApp para formato interno, mapeando estados de entrega.

Entrada:

{
  payload: WhatsappMessageDto, // Webhook do WhatsApp
  platformConfigId: string,    // ID da configuração
  channelId: string           // ID do canal
}

Implementação:

// 1. Extrair dados do status
const statusPayload = payload?.entry?.[0]?.changes?.[0]?.value?.statuses?.[0]
const metadata = payload?.entry?.[0]?.changes?.[0]?.value?.metadata

// 2. Mapear status
let messageStatus: WebhookStatusMessagesEnum
switch (statusPayload?.status) {
  case WhatsappMessageUpdateEnum.SENT:
    messageStatus = WebhookStatusMessagesEnum.SENDED
    break
  case WhatsappMessageUpdateEnum.DELIVERED:
    messageStatus = WebhookStatusMessagesEnum.DELIVERED
    break
  case WhatsappMessageUpdateEnum.READ:
    messageStatus = WebhookStatusMessagesEnum.READED
}

// 3. Criar objeto de status
const status: WebhookStatusDto = {
  platformConfigId,
  channelId,
  type: WebhookTypesEnum.MESSAGE_UPDATE,
  payload: {
    message: {
      from: metadata?.display_phone_number || null,
      to: statusPayload?.recipient_id || null,
      messageId: statusPayload?.id || null,
      status: messageStatus,
    }
  }
}

Retorno:

{
  platformConfigId: string,
  channelId: string,
  type: WebhookTypesEnum.MESSAGE_UPDATE,
  payload: {
    message: {
      from: string | null,
      to: string | null,
      messageId: string | null,
      status: WebhookStatusMessagesEnum
    }
  }
}

6. Configuração e Subscrição

6.1. Subscrição de App

6.1.1. subscribeApp

Descrição: Subscreve aplicação aos webhooks da conta WhatsApp Business, verificando se já está subscrito.

Entrada:

{
  channel: PlatformChannelsConfig // Configuração do canal
}

Implementação:

// 1. Obter apps subscritos
const subscribedApps = (
  await this.http.get(`${wabaId}/subscribed_apps`, {
    headers: { Authorization: `Bearer ${token}` }
  })
)?.data?.data

// 2. Verificar se app já está subscrito
const appId = process.env.WHATSAPP_META_API_APP_ID || '1274999130660446'
const whatsappApp = subscribedApps.find((app: any) => 
  app?.whatsapp_business_api_data?.id === appId
)

// 3. Subscrever se necessário
if (!whatsappApp)
  await this.http.post(`${wabaId}/subscribed_apps`, {}, {
    headers: { Authorization: `Bearer ${token}` }
  })

Retorno:

AxiosResponse | void // Response da subscrição ou void se já subscrito

6.2. Subscrição de Mensagens

6.2.1. messagesSubscribe

Descrição: Subscreve página do Facebook aos webhooks de mensagens do WhatsApp.

Entrada:

{
  pageId: string,    // ID da página
  pageToken: string  // Token da página
}

Implementação:

return this.http.post(
  `${pageId}/subscribed_apps?subscribed_fields=messages&access_token=${pageToken}`
)

Retorno:

AxiosResponse<{
  success: boolean
}>

7. DTOs e Estruturas de Dados

7.1. WhatsappMessageDto

Descrição: DTO principal para recebimento de webhooks do WhatsApp, contendo mensagens e atualizações de status.

{
  object: string,
  entry: Array<{
    id: string,
    time?: number,
    changes?: Array<{
      value: {
        messaging_product: string,
        metadata: {
          display_phone_number: string,
          phone_number_id: string
        },
        contacts?: Array<{
          profile: { name: string },
          wa_id: string
        }>,
        messages?: Array<{
          from: string,
          id: string,
          timestamp: string,
          type: string,
          text?: { body: string },
          image?: { mime_type: string, sha256: string, id: string },
          document?: { mime_type: string, sha256: string, id: string },
          voice?: { mime_type: string, sha256: string, id: string },
          audio?: { mime_type: string, sha256: string, id: string },
          sticker?: { mime_type: string, sha256: string, id: string },
          video?: { mime_type: string, sha256: string, id: string },
          context?: { from: string, id: string }
        }>,
        statuses?: Array<{
          id: string,
          status: 'sent' | 'delivered' | 'read',
          timestamp: string,
          recipient_id: string,
          conversation: {
            id: string,
            expiration_timestamp?: string,
            origin: { type: string }
          },
          pricing: {
            billable: boolean,
            pricing_model: string,
            category: string
          }
        }>
      },
      field: string
    }>
  }>
}

7.2. SendMessageWhatsappPayload

Descrição: Payload para envio de mensagens via API do WhatsApp, suportando texto, mídia e templates.

{
  messaging_product: 'whatsapp',
  to: string,
  type?: string,
  template?: {
    name: string,
    language: { code: string }
  },
  text?: { body: string },
  context?: { message_id: string },
  audio?: { id?: string; link?: string },
  document?: { id?: string; link?: string; filename?: string },
  image?: { id?: string; link?: string },
  sticker?: { id?: string; link?: string },
  video?: { id?: string; link?: string }
}

7.3. WhatsappConfigParams

Descrição: Parâmetros de configuração do canal WhatsApp, contendo credenciais e identificadores.

{
  token: string,      // Access Token
  whatsappId: string, // Phone Number ID
  wabaId: string     // WhatsApp Business Account ID
}

8. Enums e Constantes

8.1. WhatsappMessageUpdateEnum

Descrição: Estados de entrega de mensagens no WhatsApp.

enum WhatsappMessageUpdateEnum {
  SENT = 'sent',
  DELIVERED = 'delivered',
  READ = 'read'
}

8.2. WhatsappDefaultTemplateNames

Descrição: Nomes dos templates padrão utilizados no sistema.

enum WhatsappDefaultTemplateNames {
  contato_cliente = 'contato_cliente'
}

8.3. TemplateCategories

Descrição: Categorias de templates suportadas pelo WhatsApp Business.

enum TemplateCategories {
  UTILITY = 'UTILITY'
}