@natyapp/meta 1.6.6 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +1540 -0
- package/README.md +513 -40
- package/dist/elements/index.d.ts +1 -0
- package/dist/elements/index.js +1 -0
- package/dist/elements/mediaTemplate.d.ts +45 -0
- package/dist/elements/mediaTemplate.js +47 -0
- package/dist/elements/textTemplate.d.ts +12 -1
- package/dist/elements/textTemplate.js +13 -6
- package/dist/index.d.ts +7 -2
- package/dist/index.js +11 -2
- package/dist/interfaces/IConnection.d.ts +2 -2
- package/dist/interfaces/ILog.d.ts +2 -2
- package/dist/interfaces/ILogger.d.ts +62 -0
- package/dist/interfaces/ILogger.js +2 -0
- package/dist/interfaces/ISdk.d.ts +4 -2
- package/dist/interfaces/IWebhook.d.ts +2 -2
- package/dist/interfaces/index.d.ts +1 -0
- package/dist/interfaces/index.js +1 -0
- package/dist/queue/messageQueue.d.ts +1 -1
- package/dist/queue/messageQueue.js +45 -0
- package/dist/routes/webhooks/methods/connection.js +78 -11
- package/dist/routes/webhooks/methods/messages.js +18 -3
- package/dist/services/axiosInstances.d.ts +14 -5
- package/dist/services/axiosInstances.js +111 -23
- package/dist/services/middlewares/validations.d.ts +2 -2
- package/dist/services/middlewares/validations.js +1 -2
- package/dist/services/mutations/connection.js +1 -1
- package/dist/services/mutations/logs.js +1 -1
- package/dist/services/mutations/messages.js +1 -1
- package/dist/services/mutations/validation.d.ts +1 -1
- package/dist/services/mutations/validation.js +1 -1
- package/dist/services/mutations/webhooks.js +1 -1
- package/dist/types/logs.d.ts +1 -1
- package/dist/types/requestTypes.d.ts +2 -0
- package/dist/useCases/connection/index.d.ts +9 -9
- package/dist/useCases/connection/index.js +156 -7
- package/dist/useCases/events/NatyEvents.d.ts +4 -2
- package/dist/useCases/events/NatyEvents.js +13 -1
- package/dist/useCases/log/index.d.ts +5 -5
- package/dist/useCases/log/index.js +65 -3
- package/dist/useCases/message/whatsappResponse.d.ts +48 -22
- package/dist/useCases/message/whatsappResponse.js +672 -105
- package/dist/useCases/messages/index.d.ts +5 -5
- package/dist/useCases/messages/index.js +66 -3
- package/dist/useCases/sdk/index.d.ts +8 -4
- package/dist/useCases/sdk/index.js +38 -6
- package/dist/useCases/webhook/index.d.ts +9 -9
- package/dist/useCases/webhook/index.js +154 -7
- package/dist/utils/consoleLogger.d.ts +20 -0
- package/dist/utils/consoleLogger.js +51 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/loggerContext.d.ts +57 -0
- package/dist/utils/loggerContext.js +90 -0
- package/dist/utils/methodContext.d.ts +34 -0
- package/dist/utils/methodContext.js +48 -0
- package/dist/utils/parseError.d.ts +12 -0
- package/dist/utils/parseError.js +27 -3
- package/dist/utils/pinoAdapter.d.ts +30 -0
- package/dist/utils/pinoAdapter.js +68 -0
- package/dist/utils/sanitize.d.ts +42 -0
- package/dist/utils/sanitize.js +120 -0
- package/dist/utils/tryCatch.d.ts +10 -1
- package/dist/utils/tryCatch.js +40 -5
- package/docs/01-visao-geral.md +355 -0
- package/docs/02-contexto-negocio.md +596 -0
- package/docs/03-arquitetura.md +925 -0
- package/docs/04-fluxos-funcionais.md +887 -0
- package/docs/05-integracoes.md +960 -0
- package/docs/06-entidades.md +849 -0
- package/docs/07-guia-pratico.md +1133 -0
- package/docs/08-troubleshooting.md +816 -0
- package/docs/README.md +125 -0
- package/examples/logger-example.ts +279 -0
- package/package.json +2 -2
- /package/dist/{Entities → entities}/Logs.d.ts +0 -0
- /package/dist/{Entities → entities}/Logs.js +0 -0
- /package/dist/{Entities → entities}/connection.d.ts +0 -0
- /package/dist/{Entities → entities}/connection.js +0 -0
- /package/dist/{Entities → entities}/errorLogs.d.ts +0 -0
- /package/dist/{Entities → entities}/errorLogs.js +0 -0
- /package/dist/{Entities → entities}/index.d.ts +0 -0
- /package/dist/{Entities → entities}/index.js +0 -0
- /package/dist/{Entities → entities}/messages.d.ts +0 -0
- /package/dist/{Entities → entities}/messages.js +0 -0
- /package/dist/{Entities → entities}/webhooks.d.ts +0 -0
- /package/dist/{Entities → entities}/webhooks.js +0 -0
- /package/dist/{Entities → entities}/whatsappMessage.d.ts +0 -0
- /package/dist/{Entities → entities}/whatsappMessage.js +0 -0
- /package/dist/{Errors → errors}/Either.d.ts +0 -0
- /package/dist/{Errors → errors}/Either.js +0 -0
- /package/dist/{Errors → errors}/ErrorHandling.d.ts +0 -0
- /package/dist/{Errors → errors}/ErrorHandling.js +0 -0
- /package/dist/{Errors → errors}/index.d.ts +0 -0
- /package/dist/{Errors → errors}/index.js +0 -0
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
# 04 - Fluxos Funcionais
|
|
2
|
+
|
|
3
|
+
[⬅️ Voltar ao Índice](README.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📋 Sumário
|
|
8
|
+
- [Fluxo 1: Inicialização do SDK](#fluxo-1-inicialização-do-sdk)
|
|
9
|
+
- [Fluxo 2: Envio de Mensagens](#fluxo-2-envio-de-mensagens)
|
|
10
|
+
- [Fluxo 3: Recebimento de Mensagens (Webhooks)](#fluxo-3-recebimento-de-mensagens-webhooks)
|
|
11
|
+
- [Fluxo 4: Verificação de Conexão](#fluxo-4-verificação-de-conexão)
|
|
12
|
+
- [Fluxo 5: Upload e Download de Mídia](#fluxo-5-upload-e-download-de-mídia)
|
|
13
|
+
- [Fluxo 6: Gestão de Tokens](#fluxo-6-gestão-de-tokens)
|
|
14
|
+
- [Fluxos Alternativos e Tratamento de Erros](#fluxos-alternativos-e-tratamento-de-erros)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Fluxo 1: Inicialização do SDK
|
|
19
|
+
|
|
20
|
+
### Objetivo
|
|
21
|
+
Autenticar a aplicação no SDK e preparar o ambiente para envio/recepção de mensagens.
|
|
22
|
+
|
|
23
|
+
### Pré-condições
|
|
24
|
+
- Aplicação possui um `appToken` válido fornecido pela plataforma Naty
|
|
25
|
+
- Variável de ambiente `API_META` configurada
|
|
26
|
+
- (Opcional) Instância do Express.js para webhooks
|
|
27
|
+
|
|
28
|
+
### Atores
|
|
29
|
+
- **Desenvolvedor/Aplicação:** Inicializa e configura o SDK
|
|
30
|
+
- **SDK Naty Meta:** Processa autenticação e configuração
|
|
31
|
+
- **Naty Meta API:** Valida credenciais
|
|
32
|
+
|
|
33
|
+
### Fluxo Principal
|
|
34
|
+
|
|
35
|
+
```mermaid
|
|
36
|
+
sequenceDiagram
|
|
37
|
+
participant Dev as Desenvolvedor
|
|
38
|
+
participant App as Aplicação
|
|
39
|
+
participant SDK as NatyMeta SDK
|
|
40
|
+
participant Interceptor as Axios Interceptor
|
|
41
|
+
participant Naty as Naty Meta API
|
|
42
|
+
participant Express as Express App
|
|
43
|
+
|
|
44
|
+
Note over Dev,App: 1. Instanciação
|
|
45
|
+
Dev->>App: Implementa código de inicialização
|
|
46
|
+
App->>SDK: new NatyMeta()
|
|
47
|
+
SDK-->>App: Instância criada (não autenticada)
|
|
48
|
+
|
|
49
|
+
Note over App,Naty: 2. Autenticação
|
|
50
|
+
App->>SDK: connect({ appToken, app?, pathname? })
|
|
51
|
+
SDK->>SDK: Valida parâmetros obrigatórios
|
|
52
|
+
|
|
53
|
+
SDK->>Naty: POST /validate/validateAppToken<br/>{ appToken }
|
|
54
|
+
alt Token válido
|
|
55
|
+
Naty-->>SDK: 200 OK { valid: true, data }
|
|
56
|
+
|
|
57
|
+
Note over SDK,Interceptor: 3. Configuração
|
|
58
|
+
SDK->>Interceptor: updateAxiosInterceptor(appToken)
|
|
59
|
+
Interceptor->>Interceptor: Injeta header 'x-access-token'
|
|
60
|
+
|
|
61
|
+
alt Express fornecido
|
|
62
|
+
SDK->>Express: app.use(pathname, webhookRoutes)
|
|
63
|
+
Express-->>SDK: Rotas registradas
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
SDK->>SDK: Inicializa managers<br/>(Connection, Message, Webhook, Log)
|
|
67
|
+
|
|
68
|
+
SDK-->>App: Either.right({ success: true })
|
|
69
|
+
App-->>Dev: SDK pronto para uso
|
|
70
|
+
else Token inválido
|
|
71
|
+
Naty-->>SDK: 401 Unauthorized
|
|
72
|
+
SDK-->>App: Either.left(Error: 'Invalid appToken')
|
|
73
|
+
App-->>Dev: Erro de autenticação
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Código Exemplo
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import express from 'express';
|
|
81
|
+
import { NatyMeta } from '@natyapp/meta';
|
|
82
|
+
|
|
83
|
+
const app = express();
|
|
84
|
+
app.use(express.json());
|
|
85
|
+
|
|
86
|
+
// Inicializar SDK
|
|
87
|
+
const sdk = new NatyMeta();
|
|
88
|
+
const result = await sdk.connect({
|
|
89
|
+
appToken: process.env.NATY_APP_TOKEN,
|
|
90
|
+
app: app, // (Opcional) Express para webhooks
|
|
91
|
+
pathname: '/webhooks' // (Opcional) Base path
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (result.isRight()) {
|
|
95
|
+
console.log('SDK inicializado com sucesso!');
|
|
96
|
+
|
|
97
|
+
// Acessar módulos
|
|
98
|
+
const connectionManager = sdk.Connection;
|
|
99
|
+
const messageManager = sdk.Message;
|
|
100
|
+
|
|
101
|
+
app.listen(3000, () => {
|
|
102
|
+
console.log('Servidor rodando na porta 3000');
|
|
103
|
+
});
|
|
104
|
+
} else {
|
|
105
|
+
console.error('Erro ao inicializar SDK:', result.value);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Pós-condições (Sucesso)
|
|
111
|
+
- ✅ SDK autenticado com appToken válido
|
|
112
|
+
- ✅ Interceptores configurados (header automático)
|
|
113
|
+
- ✅ Webhooks registrados (se Express fornecido)
|
|
114
|
+
- ✅ Managers disponíveis para uso
|
|
115
|
+
|
|
116
|
+
### Pós-condições (Falha)
|
|
117
|
+
- ❌ SDK não autenticado
|
|
118
|
+
- ❌ Managers não disponíveis
|
|
119
|
+
- ❌ Erro retornado via Either.left
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Fluxo 2: Envio de Mensagens
|
|
124
|
+
|
|
125
|
+
### Objetivo
|
|
126
|
+
Enviar mensagem de qualquer tipo (texto, mídia, interativa) para um número WhatsApp.
|
|
127
|
+
|
|
128
|
+
### Pré-condições
|
|
129
|
+
- SDK inicializado e conectado
|
|
130
|
+
- Conexão WhatsApp cadastrada no sistema (companyId + phoneNumberId)
|
|
131
|
+
- Número destinatário no formato E.164
|
|
132
|
+
|
|
133
|
+
### Atores
|
|
134
|
+
- **Aplicação:** Solicita envio de mensagem
|
|
135
|
+
- **WhatsappResponse:** Orquestra o fluxo de envio
|
|
136
|
+
- **Connection Manager:** Busca e atualiza credenciais
|
|
137
|
+
- **Meta Graph API:** Entrega mensagem ao WhatsApp
|
|
138
|
+
|
|
139
|
+
### Fluxo Principal
|
|
140
|
+
|
|
141
|
+
```mermaid
|
|
142
|
+
sequenceDiagram
|
|
143
|
+
participant App as Aplicação
|
|
144
|
+
participant WR as WhatsappResponse
|
|
145
|
+
participant ConnMgr as Connection Manager
|
|
146
|
+
participant NatyAPI as Naty Meta API
|
|
147
|
+
participant MetaAPI as Meta Graph API
|
|
148
|
+
participant WA as WhatsApp
|
|
149
|
+
|
|
150
|
+
Note over App,WR: 1. Criação da Instância
|
|
151
|
+
App->>WR: new WhatsappResponse({<br/>companyId, phone_number_id })
|
|
152
|
+
|
|
153
|
+
Note over WR,NatyAPI: 2. Buscar Conexão
|
|
154
|
+
WR->>ConnMgr: getConnection(companyId, phoneNumberId)
|
|
155
|
+
ConnMgr->>NatyAPI: GET /connection?companyId&phoneNumberId
|
|
156
|
+
NatyAPI-->>ConnMgr: ConnectionEntity { accessToken, appId, appSecret }
|
|
157
|
+
ConnMgr-->>WR: ConnectionEntity
|
|
158
|
+
|
|
159
|
+
Note over WR,MetaAPI: 3. Refresh Token (se necessário)
|
|
160
|
+
WR->>WR: update_long_lived_token(connection)
|
|
161
|
+
WR->>MetaAPI: POST /oauth/access_token<br/>{ grant_type, client_id, client_secret, fb_exchange_token }
|
|
162
|
+
MetaAPI-->>WR: { access_token: newToken, expires_in: 5184000 }
|
|
163
|
+
WR->>NatyAPI: PUT /connection { accessToken: newToken }
|
|
164
|
+
|
|
165
|
+
Note over WR,WR: 4. Criar Cliente Meta
|
|
166
|
+
WR->>WR: createMetaAxios(newAccessToken, phoneNumberId)
|
|
167
|
+
|
|
168
|
+
Note over App,MetaAPI: 5. Enviar Mensagem
|
|
169
|
+
App->>WR: send_text({ to: '5511999999999', message: 'Olá!' })
|
|
170
|
+
WR->>WR: Monta payload JSON<br/>{ messaging_product: 'whatsapp', to, type: 'text', text: { body } }
|
|
171
|
+
WR->>MetaAPI: POST /{phoneNumberId}/messages<br/>Authorization: Bearer {accessToken}
|
|
172
|
+
|
|
173
|
+
MetaAPI->>WA: Processa e entrega
|
|
174
|
+
WA-->>MetaAPI: Mensagem entregue
|
|
175
|
+
|
|
176
|
+
MetaAPI-->>WR: 200 OK { messages: [{ id: 'wamid.xxx' }] }
|
|
177
|
+
WR-->>App: Either.right({ id: 'wamid.xxx' })
|
|
178
|
+
|
|
179
|
+
Note over App: 6. Mensagem enviada com sucesso
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Código Exemplo - Texto Simples
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// Criar instância WhatsappResponse
|
|
186
|
+
const whatsapp = sdk.Message.WhatsappResponse({
|
|
187
|
+
companyId: 'abc123',
|
|
188
|
+
phone_number_id: '123456789012345'
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Enviar mensagem de texto
|
|
192
|
+
const result = await whatsapp.send_text({
|
|
193
|
+
to: '5511999999999',
|
|
194
|
+
message: 'Olá! Como posso ajudar você hoje?'
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (result.isRight()) {
|
|
198
|
+
console.log('Mensagem enviada! ID:', result.value.messages[0].id);
|
|
199
|
+
} else {
|
|
200
|
+
console.error('Erro ao enviar:', result.value);
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Código Exemplo - Botões Interativos
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import { Button } from '@natyapp/meta';
|
|
208
|
+
|
|
209
|
+
// Construir mensagem com botões
|
|
210
|
+
const buttonPayload = new Button()
|
|
211
|
+
.setBody('Escolha uma opção:')
|
|
212
|
+
.addButton({ id: 'opt_suporte', title: '💬 Suporte' })
|
|
213
|
+
.addButton({ id: 'opt_vendas', title: '🛒 Vendas' })
|
|
214
|
+
.addButton({ id: 'opt_financeiro', title: '💰 Financeiro' })
|
|
215
|
+
.build();
|
|
216
|
+
|
|
217
|
+
// Enviar
|
|
218
|
+
const result = await whatsapp.send_buttons({
|
|
219
|
+
to: '5511999999999',
|
|
220
|
+
...buttonPayload
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Código Exemplo - Lista de Opções
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { List } from '@natyapp/meta';
|
|
228
|
+
|
|
229
|
+
const listPayload = new List()
|
|
230
|
+
.setBody('Confira nossos produtos:')
|
|
231
|
+
.setButtonText('Ver Produtos')
|
|
232
|
+
.addSection('Eletrônicos')
|
|
233
|
+
.addItem({ id: 'prod_001', title: 'Smartphone', description: 'R$ 2.000,00' })
|
|
234
|
+
.addItem({ id: 'prod_002', title: 'Notebook', description: 'R$ 4.500,00' })
|
|
235
|
+
.addSection('Eletrodomésticos')
|
|
236
|
+
.addItem({ id: 'prod_101', title: 'Geladeira', description: 'R$ 3.200,00' })
|
|
237
|
+
.addItem({ id: 'prod_102', title: 'Máquina Lavar', description: 'R$ 2.800,00' })
|
|
238
|
+
.build();
|
|
239
|
+
|
|
240
|
+
const result = await whatsapp.send_list({
|
|
241
|
+
to: '5511999999999',
|
|
242
|
+
...listPayload
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Pós-condições (Sucesso)
|
|
247
|
+
- ✅ Mensagem enviada ao WhatsApp do destinatário
|
|
248
|
+
- ✅ ID da mensagem retornado (`wamid.xxx`)
|
|
249
|
+
- ✅ Token atualizado (se estava próximo de expirar)
|
|
250
|
+
|
|
251
|
+
### Pós-condições (Falha)
|
|
252
|
+
- ❌ Mensagem não enviada
|
|
253
|
+
- ❌ Erro retornado via Either.left com contexto
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Fluxo 3: Recebimento de Mensagens (Webhooks)
|
|
258
|
+
|
|
259
|
+
### Objetivo
|
|
260
|
+
Receber mensagens enviadas por clientes via WhatsApp e disponibilizá-las à aplicação via eventos.
|
|
261
|
+
|
|
262
|
+
### Pré-condições
|
|
263
|
+
- SDK inicializado com Express integrado
|
|
264
|
+
- Webhook URL configurada no Meta for Developers
|
|
265
|
+
- Aplicação possui listener para evento `'message'`
|
|
266
|
+
|
|
267
|
+
### Atores
|
|
268
|
+
- **Cliente WhatsApp:** Envia mensagem
|
|
269
|
+
- **Meta WhatsApp Platform:** Recebe mensagem e envia webhook
|
|
270
|
+
- **Webhook Route:** Processa payload e emite evento
|
|
271
|
+
- **Aplicação:** Escuta evento e processa mensagem
|
|
272
|
+
|
|
273
|
+
### Fluxo Principal
|
|
274
|
+
|
|
275
|
+
```mermaid
|
|
276
|
+
sequenceDiagram
|
|
277
|
+
participant Client as Cliente WhatsApp
|
|
278
|
+
participant WA as WhatsApp Platform
|
|
279
|
+
participant Meta as Meta Webhook Service
|
|
280
|
+
participant Route as Webhook Route<br/>(SDK)
|
|
281
|
+
participant SDK as NatyMeta EventEmitter
|
|
282
|
+
participant App as Aplicação
|
|
283
|
+
|
|
284
|
+
Note over Client,Meta: 1. Cliente Envia Mensagem
|
|
285
|
+
Client->>WA: Envia mensagem pelo WhatsApp
|
|
286
|
+
WA->>Meta: Processa e prepara webhook
|
|
287
|
+
|
|
288
|
+
Note over Meta,Route: 2. Meta Envia Webhook
|
|
289
|
+
Meta->>Route: POST /webhooks/messages<br/>{ object: 'whatsapp_business_account', entry: [...] }
|
|
290
|
+
|
|
291
|
+
Note over Route: 3. Processamento do Payload
|
|
292
|
+
Route->>Route: Valida estrutura do payload
|
|
293
|
+
Route->>Route: Extrai dados:<br/>- companyId (de phoneNumberId)<br/>- sender (número do cliente)<br/>- message (conteúdo)
|
|
294
|
+
|
|
295
|
+
Route->>Route: Cria WhatsappMessage entity
|
|
296
|
+
Route->>Route: Instancia WhatsappResponse<br/>(para responder)
|
|
297
|
+
|
|
298
|
+
Note over SDK,App: 4. Emissão de Evento
|
|
299
|
+
Route->>SDK: emit('message', {<br/>companyId, sender, message, whatsappResponse })
|
|
300
|
+
SDK->>App: Dispara listeners registrados
|
|
301
|
+
|
|
302
|
+
Note over App: 5. Processamento pela Aplicação
|
|
303
|
+
App->>App: Executa lógica de negócio<br/>(chatbot, IA, regras)
|
|
304
|
+
|
|
305
|
+
alt Resposta Automática
|
|
306
|
+
App->>Route: whatsappResponse.send_text({ to: sender, message })
|
|
307
|
+
Route->>Meta: POST /messages
|
|
308
|
+
Meta->>WA: Envia resposta
|
|
309
|
+
WA->>Client: Cliente recebe resposta
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
Route-->>Meta: 200 OK (confirma recebimento)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Estrutura do Payload (Meta)
|
|
316
|
+
|
|
317
|
+
<details>
|
|
318
|
+
<summary>Ver payload completo de exemplo (clique para expandir)</summary>
|
|
319
|
+
|
|
320
|
+
```json
|
|
321
|
+
{
|
|
322
|
+
"object": "whatsapp_business_account",
|
|
323
|
+
"entry": [
|
|
324
|
+
{
|
|
325
|
+
"id": "123456789",
|
|
326
|
+
"changes": [
|
|
327
|
+
{
|
|
328
|
+
"value": {
|
|
329
|
+
"messaging_product": "whatsapp",
|
|
330
|
+
"metadata": {
|
|
331
|
+
"display_phone_number": "5511999999999",
|
|
332
|
+
"phone_number_id": "123456789012345"
|
|
333
|
+
},
|
|
334
|
+
"contacts": [
|
|
335
|
+
{
|
|
336
|
+
"profile": { "name": "João Silva" },
|
|
337
|
+
"wa_id": "5511888888888"
|
|
338
|
+
}
|
|
339
|
+
],
|
|
340
|
+
"messages": [
|
|
341
|
+
{
|
|
342
|
+
"from": "5511888888888",
|
|
343
|
+
"id": "wamid.HBgNNTUxMTk4ODg4ODg4OBUCABIYIDNBNjdGMzQ5QzE5MEFCMTY1MjU0",
|
|
344
|
+
"timestamp": "1709740800",
|
|
345
|
+
"type": "text",
|
|
346
|
+
"text": { "body": "Olá, preciso de ajuda!" }
|
|
347
|
+
}
|
|
348
|
+
]
|
|
349
|
+
},
|
|
350
|
+
"field": "messages"
|
|
351
|
+
}
|
|
352
|
+
]
|
|
353
|
+
}
|
|
354
|
+
]
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
</details>
|
|
358
|
+
|
|
359
|
+
### Código Exemplo - Listener
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
// Registrar listener para mensagens
|
|
363
|
+
sdk.on('message', async (data) => {
|
|
364
|
+
console.log(`Nova mensagem de ${data.sender}:`);
|
|
365
|
+
console.log(`Tipo: ${data.message.type}`);
|
|
366
|
+
|
|
367
|
+
// Processar diferentes tipos de mensagem
|
|
368
|
+
if (data.message.type === 'text') {
|
|
369
|
+
const userText = data.message.text.body.toLowerCase();
|
|
370
|
+
|
|
371
|
+
// Lógica de chatbot simples
|
|
372
|
+
let response = '';
|
|
373
|
+
|
|
374
|
+
if (userText.includes('oi') || userText.includes('olá')) {
|
|
375
|
+
response = 'Olá! Como posso ajudar você hoje?';
|
|
376
|
+
} else if (userText.includes('horário')) {
|
|
377
|
+
response = 'Atendemos de segunda a sexta, das 9h às 18h.';
|
|
378
|
+
} else if (userText.includes('preço')) {
|
|
379
|
+
response = 'Para consultar preços, envie "CATALOGO".';
|
|
380
|
+
} else {
|
|
381
|
+
response = 'Desculpe, não entendi. Digite MENU para ver as opções.';
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Responder automaticamente
|
|
385
|
+
await data.whatsappResponse.send_text({
|
|
386
|
+
to: data.sender,
|
|
387
|
+
message: response
|
|
388
|
+
});
|
|
389
|
+
} else if (data.message.type === 'interactive') {
|
|
390
|
+
// Cliente clicou em botão ou selecionou item de lista
|
|
391
|
+
const buttonId = data.message.interactive.button_reply?.id ||
|
|
392
|
+
data.message.interactive.list_reply?.id;
|
|
393
|
+
|
|
394
|
+
console.log(`Cliente selecionou: ${buttonId}`);
|
|
395
|
+
|
|
396
|
+
// Processar seleção
|
|
397
|
+
await handleInteraction(buttonId, data);
|
|
398
|
+
} else if (data.message.type === 'image') {
|
|
399
|
+
// Cliente enviou imagem
|
|
400
|
+
const mediaId = data.message.image.id;
|
|
401
|
+
|
|
402
|
+
// Download da imagem
|
|
403
|
+
const imageResult = await data.whatsappResponse.download_media(mediaId);
|
|
404
|
+
|
|
405
|
+
if (imageResult.isRight()) {
|
|
406
|
+
const buffer = imageResult.value;
|
|
407
|
+
// Salvar ou processar imagem (ex: OCR, análise de IA)
|
|
408
|
+
await processImage(buffer);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Código Exemplo - Sistema Avançado de Roteamento
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
// Gerenciador de conversação
|
|
418
|
+
class ConversationManager {
|
|
419
|
+
private contexts = new Map<string, string>(); // sender -> context
|
|
420
|
+
|
|
421
|
+
async handleMessage(data: MessageEventData) {
|
|
422
|
+
const sender = data.sender;
|
|
423
|
+
const context = this.contexts.get(sender) || 'initial';
|
|
424
|
+
|
|
425
|
+
// Máquina de estados
|
|
426
|
+
switch (context) {
|
|
427
|
+
case 'initial':
|
|
428
|
+
await this.showMainMenu(data);
|
|
429
|
+
this.contexts.set(sender, 'menu');
|
|
430
|
+
break;
|
|
431
|
+
|
|
432
|
+
case 'menu':
|
|
433
|
+
await this.handleMenuSelection(data);
|
|
434
|
+
break;
|
|
435
|
+
|
|
436
|
+
case 'collecting_name':
|
|
437
|
+
await this.collectName(data);
|
|
438
|
+
this.contexts.set(sender, 'collecting_email');
|
|
439
|
+
break;
|
|
440
|
+
|
|
441
|
+
case 'collecting_email':
|
|
442
|
+
await this.collectEmail(data);
|
|
443
|
+
this.contexts.set(sender, 'completed');
|
|
444
|
+
break;
|
|
445
|
+
|
|
446
|
+
default:
|
|
447
|
+
this.contexts.delete(sender);
|
|
448
|
+
await this.showMainMenu(data);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private async showMainMenu(data: MessageEventData) {
|
|
453
|
+
const buttons = new Button()
|
|
454
|
+
.setBody('Bem-vindo! Escolha uma opção:')
|
|
455
|
+
.addButton({ id: 'info', title: 'Informações' })
|
|
456
|
+
.addButton({ id: 'support', title: 'Suporte' })
|
|
457
|
+
.addButton({ id: 'register', title: 'Cadastrar' })
|
|
458
|
+
.build();
|
|
459
|
+
|
|
460
|
+
await data.whatsappResponse.send_buttons({
|
|
461
|
+
to: data.sender,
|
|
462
|
+
...buttons
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ... outros métodos
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const conversationMgr = new ConversationManager();
|
|
470
|
+
|
|
471
|
+
sdk.on('message', (data) => {
|
|
472
|
+
conversationMgr.handleMessage(data);
|
|
473
|
+
});
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Pós-condições (Sucesso)
|
|
477
|
+
- ✅ Mensagem recebida e processada
|
|
478
|
+
- ✅ Evento emitido para todos os listeners
|
|
479
|
+
- ✅ Webhook confirmado (200 OK) para Meta
|
|
480
|
+
- ✅ Resposta enviada (se aplicável)
|
|
481
|
+
|
|
482
|
+
### Pós-condições (Falha)
|
|
483
|
+
- ❌ Se webhook falhar (erro 5xx), Meta retentar até 3x
|
|
484
|
+
- ❌ Se timeout (>15s), Meta considerar falha
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Fluxo 4: Verificação de Conexão
|
|
489
|
+
|
|
490
|
+
### Objetivo
|
|
491
|
+
Validar se uma conexão WhatsApp está ativa e obter informações da conta.
|
|
492
|
+
|
|
493
|
+
### Pré-condições
|
|
494
|
+
- Conexão criada no sistema (ConnectionEntity)
|
|
495
|
+
- Webhook de verificação configurado na Meta
|
|
496
|
+
|
|
497
|
+
### Fluxo Principal
|
|
498
|
+
|
|
499
|
+
```mermaid
|
|
500
|
+
sequenceDiagram
|
|
501
|
+
participant Meta as Meta Webhook<br/>Verification
|
|
502
|
+
participant Route as Webhook Route<br/>/webhooks/connections
|
|
503
|
+
participant NatyAPI as Naty Meta API
|
|
504
|
+
participant MetaAPI as Meta Graph API
|
|
505
|
+
participant SDK as NatyMeta EventEmitter
|
|
506
|
+
participant App as Aplicação
|
|
507
|
+
|
|
508
|
+
Note over Meta,Route: 1. Verification Request (GET)
|
|
509
|
+
Meta->>Route: GET /webhooks/connections?<br/>hub.mode=subscribe&<br/>hub.challenge=xyz&<br/>hub.verify_token=conn_abc123
|
|
510
|
+
|
|
511
|
+
Route->>Route: Extrai connectionId do verify_token
|
|
512
|
+
Route-->>Meta: 200 OK { challenge: 'xyz' }
|
|
513
|
+
|
|
514
|
+
Note over Meta,Route: 2. Connection Event (POST)
|
|
515
|
+
Meta->>Route: POST /webhooks/connections<br/>{ verify_token: 'conn_abc123', ... }
|
|
516
|
+
|
|
517
|
+
Note over Route,MetaAPI: 3. Buscar Informações
|
|
518
|
+
Route->>Route: Extrai connectionId
|
|
519
|
+
Route->>NatyAPI: GET /connection?id=conn_abc123
|
|
520
|
+
NatyAPI-->>Route: ConnectionEntity { phoneNumberId, accessToken }
|
|
521
|
+
|
|
522
|
+
Route->>MetaAPI: GET /{phoneNumberId}<br/>Authorization: Bearer {accessToken}
|
|
523
|
+
MetaAPI-->>Route: {<br/> id: '123',<br/> verified_name: 'Minha Empresa',<br/> display_phone_number: '+55...',<br/> quality_rating: 'GREEN'<br/>}
|
|
524
|
+
|
|
525
|
+
Note over SDK,App: 4. Emitir Evento
|
|
526
|
+
Route->>SDK: emit('connection', {<br/>connectionId, accountInfo })
|
|
527
|
+
SDK->>App: Dispara listener
|
|
528
|
+
|
|
529
|
+
App->>App: Confirma conexão ativa<br/>Atualiza dashboard
|
|
530
|
+
|
|
531
|
+
Route-->>Meta: 200 OK
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Código Exemplo
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
sdk.on('connection', (data) => {
|
|
538
|
+
console.log('=== Conexão Verificada ===');
|
|
539
|
+
console.log(`Connection ID: ${data.connectionId}`);
|
|
540
|
+
console.log(`Nome Verificado: ${data.accountInfo.verified_name}`);
|
|
541
|
+
console.log(`Número: ${data.accountInfo.display_phone_number}`);
|
|
542
|
+
console.log(`Quality Rating: ${data.accountInfo.quality_rating}`);
|
|
543
|
+
|
|
544
|
+
// Atualizar status no banco de dados
|
|
545
|
+
updateConnectionStatus(data.connectionId, 'active');
|
|
546
|
+
|
|
547
|
+
// Alertar equipe se quality rating estiver baixo
|
|
548
|
+
if (data.accountInfo.quality_rating !== 'GREEN') {
|
|
549
|
+
notifyAdmins(`ALERTA: Quality rating ${data.accountInfo.quality_rating} para ${data.accountInfo.display_phone_number}`);
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Pós-condições
|
|
555
|
+
- ✅ Conexão confirmada como ativa
|
|
556
|
+
- ✅ Informações da conta atualizadas
|
|
557
|
+
- ✅ Quality rating monitorado
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## Fluxo 5: Upload e Download de Mídia
|
|
562
|
+
|
|
563
|
+
### Objetivo
|
|
564
|
+
Fazer upload de mídia (imagem, vídeo, documento) e fazer download de mídia recebida.
|
|
565
|
+
|
|
566
|
+
### Fluxo 5A: Upload de Mídia
|
|
567
|
+
|
|
568
|
+
```mermaid
|
|
569
|
+
sequenceDiagram
|
|
570
|
+
participant App as Aplicação
|
|
571
|
+
participant WR as WhatsappResponse
|
|
572
|
+
participant MetaAPI as Meta Graph API
|
|
573
|
+
|
|
574
|
+
Note over App,WR: 1. Preparar Stream
|
|
575
|
+
App->>App: Ler arquivo do disco<br/>const stream = fs.createReadStream('image.jpg')
|
|
576
|
+
|
|
577
|
+
Note over App,MetaAPI: 2. Upload
|
|
578
|
+
App->>WR: createMedia(stream)
|
|
579
|
+
WR->>WR: Prepara FormData<br/>{ file: stream, messaging_product: 'whatsapp' }
|
|
580
|
+
WR->>MetaAPI: POST /{phoneNumberId}/media<br/>Content-Type: multipart/form-data
|
|
581
|
+
MetaAPI-->>WR: 200 OK { id: 'media_123abc' }
|
|
582
|
+
WR-->>App: Either.right({ id: 'media_123abc' })
|
|
583
|
+
|
|
584
|
+
Note over App,MetaAPI: 3. Usar Media ID
|
|
585
|
+
App->>WR: send_image({<br/> to: '5511999999999',<br/> media_id: 'media_123abc',<br/> caption: 'Veja esta imagem!'<br/>})
|
|
586
|
+
WR->>MetaAPI: POST /messages { type: 'image', image: { id: 'media_123abc' } }
|
|
587
|
+
MetaAPI-->>WR: Mensagem enviada
|
|
588
|
+
WR-->>App: Either.right({ id: 'wamid.xxx' })
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
#### Código Exemplo - Upload
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
import fs from 'fs';
|
|
595
|
+
|
|
596
|
+
// Upload de imagem
|
|
597
|
+
const stream = fs.createReadStream('./assets/product.jpg');
|
|
598
|
+
const uploadResult = await whatsapp.createMedia(stream);
|
|
599
|
+
|
|
600
|
+
if (uploadResult.isRight()) {
|
|
601
|
+
const mediaId = uploadResult.value.id;
|
|
602
|
+
console.log('Mídia enviada! ID:', mediaId);
|
|
603
|
+
|
|
604
|
+
// Enviar imagem usando media_id
|
|
605
|
+
const sendResult = await whatsapp.send_image({
|
|
606
|
+
to: '5511999999999',
|
|
607
|
+
media_id: mediaId,
|
|
608
|
+
caption: 'Confira nosso novo produto!'
|
|
609
|
+
});
|
|
610
|
+
} else {
|
|
611
|
+
console.error('Erro no upload:', uploadResult.value);
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### Fluxo 5B: Download de Mídia
|
|
616
|
+
|
|
617
|
+
```mermaid
|
|
618
|
+
sequenceDiagram
|
|
619
|
+
participant App as Aplicação
|
|
620
|
+
participant WR as WhatsappResponse
|
|
621
|
+
participant MetaAPI as Meta Graph API
|
|
622
|
+
|
|
623
|
+
Note over App: 1. Receber Media ID (via webhook)
|
|
624
|
+
App->>App: message.image.id = 'media_123abc'
|
|
625
|
+
|
|
626
|
+
Note over App,MetaAPI: 2. Obter URL Temporária
|
|
627
|
+
App->>WR: get_media_url('media_123abc')
|
|
628
|
+
WR->>MetaAPI: GET /media_123abc<br/>Authorization: Bearer {token}
|
|
629
|
+
MetaAPI-->>WR: 200 OK {<br/> url: 'https://mmg.whatsapp.net/d/...',<br/> mime_type: 'image/jpeg',<br/> file_size: 52431<br/>}
|
|
630
|
+
WR-->>App: Either.right({ url, mime_type, file_size })
|
|
631
|
+
|
|
632
|
+
Note over App,MetaAPI: 3. Download do Arquivo
|
|
633
|
+
App->>WR: download_media('media_123abc')
|
|
634
|
+
WR->>MetaAPI: GET {url} (URL temporária, válida ~5min)
|
|
635
|
+
MetaAPI-->>WR: Binary data (Buffer)
|
|
636
|
+
WR-->>App: Either.right(Buffer)
|
|
637
|
+
|
|
638
|
+
Note over App: 4. Salvar ou Processar
|
|
639
|
+
App->>App: fs.writeFileSync('downloads/image.jpg', buffer)<br/>ou processImage(buffer)
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
#### Código Exemplo - Download
|
|
643
|
+
|
|
644
|
+
```typescript
|
|
645
|
+
sdk.on('message', async (data) => {
|
|
646
|
+
if (data.message.type === 'image') {
|
|
647
|
+
const mediaId = data.message.image.id;
|
|
648
|
+
|
|
649
|
+
// Método 1: Obter URL temporária
|
|
650
|
+
const urlResult = await data.whatsappResponse.get_media_url(mediaId);
|
|
651
|
+
if (urlResult.isRight()) {
|
|
652
|
+
console.log('URL da mídia:', urlResult.value.url);
|
|
653
|
+
console.log('MIME Type:', urlResult.value.mime_type);
|
|
654
|
+
console.log('Tamanho:', urlResult.value.file_size, 'bytes');
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Método 2: Download direto como Buffer
|
|
658
|
+
const downloadResult = await data.whatsappResponse.download_media(mediaId);
|
|
659
|
+
if (downloadResult.isRight()) {
|
|
660
|
+
const imageBuffer = downloadResult.value;
|
|
661
|
+
|
|
662
|
+
// Salvar no disco
|
|
663
|
+
fs.writeFileSync(`./downloads/${mediaId}.jpg`, imageBuffer);
|
|
664
|
+
console.log('Imagem salva!');
|
|
665
|
+
|
|
666
|
+
// Ou processar (ex: análise de IA, OCR)
|
|
667
|
+
const analysis = await analyzeImage(imageBuffer);
|
|
668
|
+
await data.whatsappResponse.send_text({
|
|
669
|
+
to: data.sender,
|
|
670
|
+
message: `Análise da imagem: ${analysis}`
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Pós-condições
|
|
678
|
+
- ✅ Mídia enviada/baixada com sucesso
|
|
679
|
+
- ✅ Media ID retornado para uso posterior
|
|
680
|
+
|
|
681
|
+
---
|
|
682
|
+
|
|
683
|
+
## Fluxo 6: Gestão de Tokens
|
|
684
|
+
|
|
685
|
+
### Objetivo
|
|
686
|
+
Garantir que tokens de acesso estejam sempre válidos através de refresh automático.
|
|
687
|
+
|
|
688
|
+
### Fluxo Principal
|
|
689
|
+
|
|
690
|
+
```mermaid
|
|
691
|
+
sequenceDiagram
|
|
692
|
+
participant App as Aplicação
|
|
693
|
+
participant WR as WhatsappResponse
|
|
694
|
+
participant Conn as Connection Manager
|
|
695
|
+
participant NatyAPI as Naty Meta API
|
|
696
|
+
participant MetaAPI as Meta OAuth API
|
|
697
|
+
|
|
698
|
+
Note over App,WR: 1. Aplicação Solicita Envio
|
|
699
|
+
App->>WR: new WhatsappResponse({ companyId, phone_number_id })
|
|
700
|
+
|
|
701
|
+
Note over WR,NatyAPI: 2. Buscar Token Atual
|
|
702
|
+
WR->>Conn: getConnection(companyId, phoneNumberId)
|
|
703
|
+
Conn->>NatyAPI: GET /connection
|
|
704
|
+
NatyAPI-->>Conn: { accessToken: 'token_antigo', appId, appSecret }
|
|
705
|
+
Conn-->>WR: ConnectionEntity
|
|
706
|
+
|
|
707
|
+
Note over WR,MetaAPI: 3. Refresh Automático
|
|
708
|
+
WR->>WR: Verifica se token precisa refresh<br/>(executado sempre por segurança)
|
|
709
|
+
WR->>MetaAPI: POST /oauth/access_token<br/>{<br/> grant_type: 'fb_exchange_token',<br/> client_id: appId,<br/> client_secret: appSecret,<br/> fb_exchange_token: token_antigo<br/>}
|
|
710
|
+
|
|
711
|
+
alt Token Refreshed com Sucesso
|
|
712
|
+
MetaAPI-->>WR: {<br/> access_token: 'token_novo',<br/> token_type: 'bearer',<br/> expires_in: 5184000<br/>}
|
|
713
|
+
|
|
714
|
+
Note over WR,NatyAPI: 4. Salvar Novo Token
|
|
715
|
+
WR->>Conn: updateConnection(id, { accessToken: 'token_novo' })
|
|
716
|
+
Conn->>NatyAPI: PUT /connection { accessToken: 'token_novo' }
|
|
717
|
+
NatyAPI-->>Conn: ConnectionEntity atualizada
|
|
718
|
+
|
|
719
|
+
WR->>WR: Usa token_novo para requisição
|
|
720
|
+
else Token Expirado Definitivamente
|
|
721
|
+
MetaAPI-->>WR: 401 Unauthorized { error: 'invalid_grant' }
|
|
722
|
+
WR-->>App: Either.left(Error: 'Token expirado, reautenticação necessária')
|
|
723
|
+
App->>App: Alertar administrador<br/>Solicitar novo short-lived token da Meta
|
|
724
|
+
end
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### Ciclo de Vida do Token
|
|
728
|
+
|
|
729
|
+
```mermaid
|
|
730
|
+
stateDiagram-v2
|
|
731
|
+
[*] --> ShortLived: Meta gera token inicial
|
|
732
|
+
ShortLived --> LongLived: Exchange via OAuth (1h)
|
|
733
|
+
LongLived --> Refreshed: Auto-refresh a cada uso
|
|
734
|
+
Refreshed --> LongLived: Token atualizado (60 dias)
|
|
735
|
+
LongLived --> Expired: Sem uso por >60 dias
|
|
736
|
+
Expired --> ShortLived: Reautenticação manual
|
|
737
|
+
Expired --> [*]
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
### Código Exemplo - Detecção de Expiração
|
|
741
|
+
|
|
742
|
+
```typescript
|
|
743
|
+
// O SDK faz refresh automaticamente, mas você pode monitorar
|
|
744
|
+
sdk.on('error', (error) => {
|
|
745
|
+
if (error.message.includes('Token expirado')) {
|
|
746
|
+
// Alertar equipe
|
|
747
|
+
sendAlert({
|
|
748
|
+
priority: 'HIGH',
|
|
749
|
+
message: 'Token WhatsApp expirado! Reautenticação necessária.',
|
|
750
|
+
connectionId: error.context?.connectionId
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
// Desativar conexão temporariamente
|
|
754
|
+
disableConnection(error.context?.connectionId);
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
---
|
|
760
|
+
|
|
761
|
+
## Fluxos Alternativos e Tratamento de Erros
|
|
762
|
+
|
|
763
|
+
### Cenário 1: Número Inválido
|
|
764
|
+
|
|
765
|
+
**Trigger:** Aplicação tenta enviar para número mal formatado.
|
|
766
|
+
|
|
767
|
+
```typescript
|
|
768
|
+
// Número sem código do país
|
|
769
|
+
const result = await whatsapp.send_text({
|
|
770
|
+
to: '11999999999', // ❌ Falta +55
|
|
771
|
+
message: 'Teste'
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
// Meta API retorna erro
|
|
775
|
+
if (result.isLeft()) {
|
|
776
|
+
console.error(result.value);
|
|
777
|
+
// Error: [Meta API] Invalid phone number format
|
|
778
|
+
}
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
**Solução:** Sempre usar formato E.164 (`+5511999999999`).
|
|
782
|
+
|
|
783
|
+
---
|
|
784
|
+
|
|
785
|
+
### Cenário 2: Rate Limit Excedido
|
|
786
|
+
|
|
787
|
+
**Trigger:** Envio de mensagens acima do limite diário (Tier).
|
|
788
|
+
|
|
789
|
+
```typescript
|
|
790
|
+
// Meta retorna 429 Too Many Requests
|
|
791
|
+
const result = await whatsapp.send_text({
|
|
792
|
+
to: '5511999999999',
|
|
793
|
+
message: 'Mensagem 1001' // Excedeu limite do Tier 1
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
if (result.isLeft()) {
|
|
797
|
+
const error = result.value;
|
|
798
|
+
if (error.statusCode === 429) {
|
|
799
|
+
console.log('Rate limit atingido. Aguardar até amanhã.');
|
|
800
|
+
// Implementar fila com retry após 24h
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
---
|
|
806
|
+
|
|
807
|
+
### Cenário 3: Webhook Timeout
|
|
808
|
+
|
|
809
|
+
**Trigger:** Processamento de webhook demora mais de 15 segundos.
|
|
810
|
+
|
|
811
|
+
```typescript
|
|
812
|
+
sdk.on('message', async (data) => {
|
|
813
|
+
// ❌ Operação demorada (ex: consulta lenta no DB)
|
|
814
|
+
const result = await slowDatabaseQuery(); // 20 segundos
|
|
815
|
+
|
|
816
|
+
// Meta já desistiu (timeout), mas SDK ainda processa
|
|
817
|
+
await data.whatsappResponse.send_text({
|
|
818
|
+
to: data.sender,
|
|
819
|
+
message: result
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
**Solução:** Responder webhook imediatamente (200 OK) e processar assíncrono.
|
|
825
|
+
|
|
826
|
+
```typescript
|
|
827
|
+
sdk.on('message', async (data) => {
|
|
828
|
+
// Processar em background
|
|
829
|
+
processMessageAsync(data).catch(console.error);
|
|
830
|
+
|
|
831
|
+
// Não aguardar processamento completo
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
async function processMessageAsync(data) {
|
|
835
|
+
const result = await slowDatabaseQuery();
|
|
836
|
+
await data.whatsappResponse.send_text({
|
|
837
|
+
to: data.sender,
|
|
838
|
+
message: result
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
### Cenário 4: Mensagem Fora da Janela de 24h
|
|
846
|
+
|
|
847
|
+
**Trigger:** Enviar mensagem livre após 24h da última interação do cliente.
|
|
848
|
+
|
|
849
|
+
```typescript
|
|
850
|
+
// Cliente interagiu há 2 dias
|
|
851
|
+
const result = await whatsapp.send_text({
|
|
852
|
+
to: '5511999999999',
|
|
853
|
+
message: 'Oi! Tudo bem?' // ❌ Não é template aprovado
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
if (result.isLeft()) {
|
|
857
|
+
console.error(result.value);
|
|
858
|
+
// Error: [Meta API] Message failed to send because more than 24 hours have passed
|
|
859
|
+
}
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
**Solução:** Usar template aprovado.
|
|
863
|
+
|
|
864
|
+
```typescript
|
|
865
|
+
const result = await whatsapp.send_text_template({
|
|
866
|
+
to: '5511999999999',
|
|
867
|
+
template: {
|
|
868
|
+
name: 'follow_up_aprovado', // Template aprovado pela Meta
|
|
869
|
+
language: 'pt_BR',
|
|
870
|
+
components: []
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
---
|
|
876
|
+
|
|
877
|
+
## 🔗 Próximos Passos
|
|
878
|
+
|
|
879
|
+
Com os fluxos compreendidos, explore:
|
|
880
|
+
|
|
881
|
+
- **[Integrações](05-integracoes.md)** - Detalhes das APIs externas
|
|
882
|
+
- **[Entidades](06-entidades.md)** - Estrutura de dados
|
|
883
|
+
- **[Guia Prático](07-guia-pratico.md)** - Mais exemplos de código
|
|
884
|
+
|
|
885
|
+
---
|
|
886
|
+
|
|
887
|
+
[⬅️ Anterior: Arquitetura](03-arquitetura.md) | [⬆️ Voltar ao Índice](README.md) | [➡️ Próximo: Integrações](05-integracoes.md)
|