@warriorteam/redai-zalo-sdk 1.3.0 → 1.4.1

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.
Files changed (76) hide show
  1. package/README.md +565 -550
  2. package/dist/index.d.ts +0 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -5
  5. package/dist/index.js.map +1 -1
  6. package/dist/services/article.service.d.ts +1 -1
  7. package/dist/services/article.service.d.ts.map +1 -1
  8. package/dist/services/article.service.js +24 -16
  9. package/dist/services/article.service.js.map +1 -1
  10. package/dist/services/auth.service.d.ts +1 -0
  11. package/dist/services/auth.service.d.ts.map +1 -1
  12. package/dist/services/auth.service.js +23 -9
  13. package/dist/services/auth.service.js.map +1 -1
  14. package/dist/services/consultation.service.d.ts +63 -16
  15. package/dist/services/consultation.service.d.ts.map +1 -1
  16. package/dist/services/consultation.service.js +264 -49
  17. package/dist/services/consultation.service.js.map +1 -1
  18. package/dist/services/general-message.service.d.ts +2 -25
  19. package/dist/services/general-message.service.d.ts.map +1 -1
  20. package/dist/services/general-message.service.js +11 -112
  21. package/dist/services/general-message.service.js.map +1 -1
  22. package/dist/services/group-management.service.d.ts +1 -1
  23. package/dist/services/group-management.service.d.ts.map +1 -1
  24. package/dist/services/group-management.service.js +59 -27
  25. package/dist/services/group-management.service.js.map +1 -1
  26. package/dist/services/group-message.service.d.ts +1 -1
  27. package/dist/services/group-message.service.d.ts.map +1 -1
  28. package/dist/services/group-message.service.js +49 -23
  29. package/dist/services/group-message.service.js.map +1 -1
  30. package/dist/services/message-management.service.d.ts +21 -2
  31. package/dist/services/message-management.service.d.ts.map +1 -1
  32. package/dist/services/message-management.service.js +83 -7
  33. package/dist/services/message-management.service.js.map +1 -1
  34. package/dist/services/oa.service.d.ts +1 -0
  35. package/dist/services/oa.service.d.ts.map +1 -1
  36. package/dist/services/oa.service.js +23 -5
  37. package/dist/services/oa.service.js.map +1 -1
  38. package/dist/services/user.service.d.ts +108 -18
  39. package/dist/services/user.service.d.ts.map +1 -1
  40. package/dist/services/user.service.js +260 -59
  41. package/dist/services/user.service.js.map +1 -1
  42. package/dist/services/video-upload.service.d.ts +1 -1
  43. package/dist/services/video-upload.service.d.ts.map +1 -1
  44. package/dist/services/video-upload.service.js +11 -8
  45. package/dist/services/video-upload.service.js.map +1 -1
  46. package/dist/services/zns.service.d.ts +88 -21
  47. package/dist/services/zns.service.d.ts.map +1 -1
  48. package/dist/services/zns.service.js +157 -42
  49. package/dist/services/zns.service.js.map +1 -1
  50. package/dist/types/group.d.ts +5 -5
  51. package/dist/types/group.d.ts.map +1 -1
  52. package/dist/types/user.d.ts +155 -12
  53. package/dist/types/user.d.ts.map +1 -1
  54. package/dist/types/user.js.map +1 -1
  55. package/dist/types/webhook.d.ts +8 -0
  56. package/dist/types/webhook.d.ts.map +1 -1
  57. package/dist/types/webhook.js.map +1 -1
  58. package/dist/types/zns.d.ts +67 -33
  59. package/dist/types/zns.d.ts.map +1 -1
  60. package/dist/zalo-sdk.d.ts +3 -11
  61. package/dist/zalo-sdk.d.ts.map +1 -1
  62. package/dist/zalo-sdk.js +0 -10
  63. package/dist/zalo-sdk.js.map +1 -1
  64. package/docs/CONSULTATION_SERVICE.md +512 -330
  65. package/docs/GROUP_MANAGEMENT.md +2 -2
  66. package/docs/USER_MANAGEMENT.md +481 -1248
  67. package/docs/WEBHOOK_EVENTS.md +55 -3
  68. package/package.json +1 -1
  69. package/dist/services/tag.service.d.ts +0 -144
  70. package/dist/services/tag.service.d.ts.map +0 -1
  71. package/dist/services/tag.service.js +0 -184
  72. package/dist/services/tag.service.js.map +0 -1
  73. package/dist/services/user-management.service.d.ts +0 -117
  74. package/dist/services/user-management.service.d.ts.map +0 -1
  75. package/dist/services/user-management.service.js +0 -239
  76. package/dist/services/user-management.service.js.map +0 -1
@@ -1,330 +1,512 @@
1
- # Consultation Service - Hướng Dẫn Sử Dụng
2
-
3
- ## Tổng Quan
4
-
5
- `ConsultationService` là dịch vụ chuyên dụng để gửi tin nhắn tư vấn (Customer Service) qua Zalo Official Account. Tin nhắn tư vấn cho phép OA gửi tin nhắn chủ động đến người dùng trong khung thời gian nhất định để hỗ trợ và tư vấn khách hàng.
6
-
7
- ## Điều Kiện Gửi Tin Nhắn Tư Vấn
8
-
9
- ### 1. Thời Gian Gửi
10
- - **Khung thời gian**: Chỉ được gửi trong vòng **48 giờ** kể từ khi người dùng tương tác cuối cùng với OA
11
- - **Tương tác bao gồm**:
12
- - Gửi tin nhắn đến OA
13
- - Nhấn button/quick reply
14
- - Gọi điện thoại từ OA
15
- - Truy cập website từ OA
16
-
17
- ### 2. Nội Dung Tin Nhắn
18
- - **Mục đích**: Phải liên quan đến tư vấn, hỗ trợ khách hàng
19
- - **Bao gồm**: Trả lời câu hỏi, hướng dẫn sử dụng, hỗ trợ kỹ thuật
20
- - **Không được**: Chứa nội dung quảng cáo trực tiếp
21
-
22
- ### 3. Điều Kiện Người Dùng
23
- - Người dùng phải đã follow OA
24
- - Người dùng không được block OA
25
- - Người dùng phải tương tác gần đây với OA
26
-
27
- ### 4. Tần Suất Gửi
28
- - Không giới hạn số lượng tin nhắn tư vấn trong ngày
29
- - Cần tuân thủ nguyên tắc không spam
30
-
31
- ## Khởi Tạo Service
32
-
33
- ```typescript
34
- import { ZaloSDK } from "@warriorteam/redai-zalo-sdk";
35
-
36
- const zalo = new ZaloSDK({
37
- appId: "your-app-id",
38
- appSecret: "your-app-secret",
39
- debug: true
40
- });
41
-
42
- // Lấy consultation service
43
- const consultationService = zalo.consultation;
44
- ```
45
-
46
- ## Các Phương Thức Chính
47
-
48
- ### 1. Gửi Tin Nhắn Văn Bản
49
-
50
- ```typescript
51
- // Gửi tin nhắn văn bản tư vấn
52
- const response = await consultationService.sendTextMessage(
53
- accessToken,
54
- { user_id: "user-id-here" },
55
- {
56
- type: "text",
57
- text: "Xin chào! Tôi có thể hỗ trợ gì cho bạn?"
58
- }
59
- );
60
-
61
- console.log("Message ID:", response.message_id);
62
- console.log("Quota remaining:", response.quota?.remain);
63
- ```
64
-
65
- **Giới hạn văn bản:**
66
- - Nội dung không được để trống
67
- - Tối đa 2000 ký tự
68
-
69
- ### 2. Gửi Tin Nhắn Hình Ảnh
70
-
71
- ```typescript
72
- // Gửi hình ảnh tư vấn
73
- const response = await consultationService.sendImageMessage(
74
- accessToken,
75
- { user_id: "user-id-here" },
76
- {
77
- type: "image",
78
- attachment: {
79
- type: "image",
80
- payload: {
81
- url: "https://example.com/support-image.jpg"
82
- }
83
- }
84
- }
85
- );
86
- ```
87
-
88
- ### 3. Gửi File Đính Kèm
89
-
90
- ```typescript
91
- // Gửi file hướng dẫn
92
- const response = await consultationService.sendFileMessage(
93
- accessToken,
94
- { user_id: "user-id-here" },
95
- {
96
- type: "file",
97
- url: "https://example.com/user-manual.pdf",
98
- filename: "Hướng dẫn sử dụng.pdf",
99
- attachment: {
100
- type: "file",
101
- payload: {
102
- url: "https://example.com/user-manual.pdf"
103
- }
104
- }
105
- }
106
- );
107
- ```
108
-
109
- ### 4. Gửi Sticker
110
-
111
- ```typescript
112
- // Gửi sticker thân thiện
113
- const response = await consultationService.sendStickerMessage(
114
- accessToken,
115
- { user_id: "user-id-here" },
116
- {
117
- type: "sticker",
118
- sticker_id: "sticker-id",
119
- attachment: {
120
- type: "sticker",
121
- payload: {
122
- id: "sticker-id"
123
- }
124
- }
125
- }
126
- );
127
- ```
128
-
129
- ### 5. Gửi Tin Nhắn Tổng Quát
130
-
131
- ```typescript
132
- // Gửi bất kỳ loại tin nhắn nào
133
- const response = await consultationService.sendMessage(
134
- accessToken,
135
- { user_id: "user-id-here" },
136
- {
137
- type: "text",
138
- text: "Cảm ơn bạn đã liên hệ. Chúng tôi sẽ hỗ trợ bạn ngay!"
139
- }
140
- );
141
- ```
142
-
143
- ## Xử Lý Lỗi
144
-
145
- ```typescript
146
- import { ZaloSDKError } from "@warriorteam/redai-zalo-sdk";
147
-
148
- try {
149
- const response = await consultationService.sendTextMessage(
150
- accessToken,
151
- { user_id: "user-id" },
152
- { type: "text", text: "Hello!" }
153
- );
154
- } catch (error) {
155
- if (error instanceof ZaloSDKError) {
156
- console.error("Zalo API Error:", error.message);
157
- console.error("Error Code:", error.code);
158
-
159
- // Xử lý các lỗi phổ biến
160
- switch (error.code) {
161
- case -216:
162
- console.log("Access token không hợp lệ");
163
- break;
164
- case -201:
165
- console.log("Tham số không hợp lệ");
166
- break;
167
- case -223:
168
- console.log("Đã vượt quá quota");
169
- break;
170
- default:
171
- console.log("Lỗi khác:", error.message);
172
- }
173
- }
174
- }
175
- ```
176
-
177
- ## Dụ Thực Tế
178
-
179
- ### Hệ Thống Hỗ Trợ Khách Hàng
180
-
181
- ```typescript
182
- class CustomerSupportBot {
183
- constructor(private consultationService: ConsultationService) {}
184
-
185
- async handleUserQuestion(accessToken: string, userId: string, question: string) {
186
- try {
187
- // Phân tích câu hỏi và tạo phản hồi
188
- const response = this.generateResponse(question);
189
-
190
- // Gửi tin nhắn vấn
191
- await this.consultationService.sendTextMessage(
192
- accessToken,
193
- { user_id: userId },
194
- {
195
- type: "text",
196
- text: response
197
- }
198
- );
199
-
200
- // Gửi thêm hình ảnh hướng dẫn nếu cần
201
- if (this.needsVisualGuide(question)) {
202
- await this.consultationService.sendImageMessage(
203
- accessToken,
204
- { user_id: userId },
205
- {
206
- type: "image",
207
- attachment: {
208
- type: "image",
209
- payload: {
210
- url: "https://example.com/guide-image.jpg"
211
- }
212
- }
213
- }
214
- );
215
- }
216
-
217
- } catch (error) {
218
- console.error("Failed to send consultation message:", error);
219
- }
220
- }
221
-
222
- private generateResponse(question: string): string {
223
- // Logic tạo phản hồi dựa trên câu hỏi
224
- if (question.includes("đăng nhập")) {
225
- return "Để đăng nhập, bạn vui lòng làm theo các bước sau...";
226
- }
227
-
228
- if (question.includes("thanh toán")) {
229
- return "Về vấn đề thanh toán, chúng tôi hỗ trợ các phương thức...";
230
- }
231
-
232
- return "Cảm ơn bạn đã liên hệ. Chúng tôi sẽ hỗ trợ bạn ngay!";
233
- }
234
-
235
- private needsVisualGuide(question: string): boolean {
236
- return question.includes("hướng dẫn") || question.includes("cách làm");
237
- }
238
- }
239
- ```
240
-
241
- ### Tích Hợp Với Webhook
242
-
243
- ```typescript
244
- // Xử lý tin nhắn từ webhook
245
- app.post('/webhook', async (req, res) => {
246
- const event = req.body;
247
-
248
- if (event.event_name === 'user_send_text') {
249
- const userId = event.sender.id;
250
- const userMessage = event.message.text;
251
-
252
- // Kiểm tra xem có phải câu hỏi cần hỗ trợ không
253
- if (userMessage.includes('help') || userMessage.includes('hỗ trợ')) {
254
-
255
- // Gửi tin nhắn tư vấn
256
- await consultationService.sendTextMessage(
257
- accessToken,
258
- { user_id: userId },
259
- {
260
- type: "text",
261
- text: "Tôi có thể hỗ trợ bạn về các vấn đề sau:\n1. Đăng nhập\n2. Thanh toán\n3. Sử dụng sản phẩm\nBạn cần hỗ trợ về vấn đề nào?"
262
- }
263
- );
264
- }
265
- }
266
-
267
- res.status(200).send('OK');
268
- });
269
- ```
270
-
271
- ## Best Practices
272
-
273
- ### 1. Kiểm Tra Thời Gian Tương Tác
274
- ```typescript
275
- // Kiểm tra thời gian tương tác cuối cùng trước khi gửi
276
- const lastInteraction = await getUserLastInteraction(userId);
277
- const hoursSinceLastInteraction = (Date.now() - lastInteraction) / (1000 * 60 * 60);
278
-
279
- if (hoursSinceLastInteraction > 48) {
280
- console.log("Không thể gửi tin nhắn tư vấn - quá 48 giờ");
281
- return;
282
- }
283
- ```
284
-
285
- ### 2. Quản Lý Quota
286
- ```typescript
287
- // Kiểm tra quota trước khi gửi
288
- const quota = await zalo.oa.getQuotaSummary(accessToken);
289
- if (quota.consultation.remaining <= 0) {
290
- console.log("Đã hết quota tin nhắn tư vấn");
291
- return;
292
- }
293
- ```
294
-
295
- ### 3. Retry Logic
296
- ```typescript
297
- async function sendWithRetry(
298
- consultationService: ConsultationService,
299
- accessToken: string,
300
- recipient: MessageRecipient,
301
- message: Message,
302
- maxRetries = 3
303
- ) {
304
- for (let i = 0; i < maxRetries; i++) {
305
- try {
306
- return await consultationService.sendMessage(accessToken, recipient, message);
307
- } catch (error) {
308
- if (i === maxRetries - 1) throw error;
309
-
310
- // Đợi trước khi retry
311
- await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
312
- }
313
- }
314
- }
315
- ```
316
-
317
- ## Lưu Ý Quan Trọng
318
-
319
- 1. **Tuân thủ chính sách**: Chỉ gửi tin nhắn tư vấn thực sự, không spam
320
- 2. **Theo dõi quota**: Kiểm tra quota thường xuyên để tránh vượt giới hạn
321
- 3. **Xử lỗi**: Luôn chế xử lỗi phù hợp
322
- 4. **Logging**: Ghi log để theo dõi và debug
323
- 5. **Rate limiting**: Tránh gửi quá nhiều tin nhắn trong thời gian ngắn
324
-
325
- ## Tài Liệu Liên Quan
326
-
327
- - [Message Types](./MESSAGE_TYPES.md) - Các loại tin nhắn được hỗ trợ
328
- - [Webhook Events](./WEBHOOK_EVENTS.md) - Xử sự kiện webhook
329
- - [Error Handling](./ERROR_HANDLING.md) - Xử lý lỗi chi tiết
330
- - [Quota Management](./QUOTA_MANAGEMENT.md) - Quản lý quota tin nhắn
1
+ # Consultation Service - Hướng Dẫn Sử Dụng
2
+
3
+ ## Tổng Quan
4
+
5
+ `ConsultationService` là dịch vụ chuyên dụng để gửi tin nhắn tư vấn (Customer Service) qua Zalo Official Account. Tin nhắn tư vấn cho phép OA gửi tin nhắn chủ động đến người dùng trong khung thời gian nhất định để hỗ trợ và tư vấn khách hàng.
6
+
7
+ **Endpoint API**: `https://openapi.zalo.me/v3.0/oa/message/cs`
8
+
9
+ ## Điều Kiện Gửi Tin Nhắn Tư Vấn
10
+
11
+ ### 1. Thời Gian Gửi
12
+ - **Khung thời gian**: Chỉ được gửi trong vòng **48 giờ** kể từ khi người dùng tương tác cuối cùng với OA
13
+ - **Tương tác bao gồm**:
14
+ - Gửi tin nhắn đến OA
15
+ - Nhấn button/quick reply
16
+ - Gọi điện thoại từ OA
17
+ - Truy cập website từ OA
18
+
19
+ ### 2. Nội Dung Tin Nhắn
20
+ - **Mục đích**: Phải liên quan đến vấn, hỗ trợ khách hàng
21
+ - **Bao gồm**: Trả lời câu hỏi, hướng dẫn sử dụng, hỗ trợ kỹ thuật
22
+ - **Không được**: Chứa nội dung quảng cáo trực tiếp
23
+
24
+ ### 3. Điều Kiện Người Dùng
25
+ - Người dùng phải đã follow OA
26
+ - Người dùng không được block OA
27
+ - Người dùng phải có tương tác gần đây với OA
28
+
29
+ ### 4. Tần Suất Gửi
30
+ - Không giới hạn số lượng tin nhắn tư vấn trong ngày
31
+ - Cần tuân thủ nguyên tắc không spam
32
+
33
+ ## Khởi Tạo Service
34
+
35
+ ```typescript
36
+ import { ConsultationService } from "@warriorteam/redai-zalo-sdk";
37
+ import { ZaloClient } from "@warriorteam/redai-zalo-sdk";
38
+
39
+ const client = new ZaloClient();
40
+ const consultationService = new ConsultationService(client);
41
+ ```
42
+
43
+ ## Các Phương Thức Chính
44
+
45
+ ### 1. Gửi Tin Nhắn Văn Bản
46
+
47
+ ```typescript
48
+ // Gửi tin nhắn văn bản tư vấn
49
+ const response = await consultationService.sendTextMessage(
50
+ accessToken,
51
+ "user-id-here",
52
+ "Xin chào! Tôi thể hỗ trợ gì cho bạn?"
53
+ );
54
+
55
+ console.log("Message ID:", response.message_id);
56
+ console.log("User ID:", response.user_id);
57
+ console.log("Quota info:", response.quota);
58
+ ```
59
+
60
+ **Tham số:**
61
+ - `accessToken`: Access token của OA
62
+ - `userId`: ID người nhận
63
+ - `text`: Nội dung tin nhắn (tối đa 2000 ký tự)
64
+
65
+ **Giới hạn:**
66
+ - Nội dung không được để trống
67
+ - Tối đa 2000 ký tự
68
+
69
+ ### 2. Gửi Tin Nhắn Hình Ảnh
70
+
71
+ #### 2.1. Gửi ảnh bằng URL
72
+
73
+ ```typescript
74
+ // Gửi hình ảnh tư vấn bằng URL
75
+ const response = await consultationService.sendImageMessage(
76
+ accessToken,
77
+ "user-id-here",
78
+ "https://example.com/support-image.jpg",
79
+ "Hướng dẫn sử dụng sản phẩm" // Text tùy chọn
80
+ );
81
+ ```
82
+
83
+ **Tham số:**
84
+ - `accessToken`: Access token của OA
85
+ - `userId`: ID người nhận
86
+ - `imageUrl`: URL hình ảnh (jpg, png, tối đa 1MB)
87
+ - `text`: Tiêu đề ảnh (tùy chọn, tối đa 2000 ký tự)
88
+
89
+ #### 2.2. Gửi ảnh bằng Attachment ID
90
+
91
+ ```typescript
92
+ // Gửi hình ảnh đã upload trước đó
93
+ const response = await consultationService.sendImageByAttachmentId(
94
+ accessToken,
95
+ "user-id-here",
96
+ "attachment-id-from-upload-api",
97
+ "Hướng dẫn chi tiết"
98
+ );
99
+ ```
100
+
101
+ **Tham số:**
102
+ - `accessToken`: Access token của OA
103
+ - `userId`: ID người nhận
104
+ - `attachmentId`: ID ảnh đã upload
105
+ - `text`: Tiêu đề ảnh (tùy chọn)
106
+
107
+ **Lưu ý:**
108
+ - Chỉ sử dụng 1 trong 2: `url` hoặc `attachment_id`
109
+ - Định dạng hỗ trợ: jpg, png
110
+ - Dung lượng tối đa: 1MB
111
+ - Kích thước tối ưu: 16:9, vùng safe zone 14:9
112
+
113
+ ### 3. Gửi Tin Nhắn GIF
114
+
115
+ ```typescript
116
+ // Gửi ảnh GIF động
117
+ const response = await consultationService.sendGifMessage(
118
+ accessToken,
119
+ "user-id-here",
120
+ "https://example.com/animation.gif",
121
+ 400, // width
122
+ 300, // height
123
+ "Hướng dẫn động" // text tùy chọn
124
+ );
125
+ ```
126
+
127
+ **Tham số:**
128
+ - `accessToken`: Access token của OA
129
+ - `userId`: ID người nhận
130
+ - `gifUrl`: URL file GIF
131
+ - `width`: Chiều rộng (bắt buộc cho GIF)
132
+ - `height`: Chiều cao (bắt buộc cho GIF)
133
+ - `text`: Tiêu đề (tùy chọn)
134
+
135
+ ### 4. Gửi File Đính Kèm
136
+
137
+ ```typescript
138
+ // Gửi file đính kèm (cần upload file trước)
139
+ const response = await consultationService.sendFileMessage(
140
+ accessToken,
141
+ "user-id-here",
142
+ "file-token-from-upload-api"
143
+ );
144
+ ```
145
+
146
+ **Tham số:**
147
+ - `accessToken`: Access token của OA
148
+ - `userId`: ID người nhận
149
+ - `fileToken`: Token file đã upload
150
+
151
+ **Lưu ý:** Cần sử dụng API upload file trước để lấy token
152
+
153
+ ### 5. Gửi Sticker
154
+
155
+ ```typescript
156
+ // Gửi sticker
157
+ const response = await consultationService.sendStickerMessage(
158
+ accessToken,
159
+ "user-id-here",
160
+ "bfe458bf64fa8da4d4eb"
161
+ );
162
+ ```
163
+
164
+ **Tham số:**
165
+ - `accessToken`: Access token của OA
166
+ - `userId`: ID người nhận
167
+ - `stickerAttachmentId`: ID sticker
168
+
169
+ **Nguồn sticker:**
170
+ - Website: https://stickers.zaloapp.com/
171
+ - Video hướng dẫn: https://vimeo.com/649330161
172
+
173
+ ### 6. Gửi Mẫu Yêu Cầu Thông Tin Người Dùng
174
+
175
+ ```typescript
176
+ // Gửi template yêu cầu thông tin
177
+ const response = await consultationService.sendRequestUserInfoMessage(
178
+ accessToken,
179
+ "user-id-here",
180
+ "OA Chatbot (Testing)",
181
+ "Đang yêu cầu thông tin từ bạn",
182
+ "https://developers.zalo.me/web/static/zalo.png"
183
+ );
184
+ ```
185
+
186
+ **Tham số:**
187
+ - `accessToken`: Access token của OA
188
+ - `userId`: ID người nhận
189
+ - `title`: Tiêu đề (tối đa 100 ký tự)
190
+ - `subtitle`: Tiêu đề phụ (tối đa 500 ký tự)
191
+ - `imageUrl`: URL hình ảnh
192
+
193
+ ## Response Format
194
+
195
+ Tất cả các API đều trả về response theo format sau:
196
+
197
+ ```typescript
198
+ interface SendMessageResponse {
199
+ message_id: string;
200
+ user_id: string;
201
+ quota?: {
202
+ quota_type: "reply" | "sub_quota" | "purchase_quota" | "reward_quota";
203
+ remain?: string;
204
+ total?: string;
205
+ expired_date?: string;
206
+ owner_type?: "OA" | "App";
207
+ owner_id?: string;
208
+ };
209
+ }
210
+ ```
211
+
212
+ **Các loại quota:**
213
+ 1. **reply**: Tin trong khung 8 tin 48h
214
+ 2. **sub_quota**: Tin miễn phí theo gói
215
+ 3. **purchase_quota**: Tin trong gói tính năng lẻ
216
+ 4. **reward_quota**: Tin trong hạn mức Redeem code
217
+ 5. **Không quota**: Tin tính phí
218
+
219
+ ## Xử Lý Lỗi
220
+
221
+ ```typescript
222
+ import { ZaloSDKError } from "@warriorteam/redai-zalo-sdk";
223
+
224
+ try {
225
+ const response = await consultationService.sendTextMessage(
226
+ accessToken,
227
+ "user-id",
228
+ "Hello!"
229
+ );
230
+
231
+ console.log("Sent successfully:", response.message_id);
232
+ } catch (error) {
233
+ if (error instanceof ZaloSDKError) {
234
+ console.error("Zalo API Error:", error.message);
235
+ console.error("Error Code:", error.code);
236
+
237
+ // Xử lý các lỗi phổ biến
238
+ switch (error.code) {
239
+ case -216:
240
+ console.log("Access token không hợp lệ");
241
+ break;
242
+ case -201:
243
+ console.log("Tham số không hợp lệ");
244
+ break;
245
+ case -223:
246
+ console.log("Đã vượt quá quota");
247
+ break;
248
+ default:
249
+ console.log("Lỗi khác:", error.message);
250
+ }
251
+ } else {
252
+ console.error("Unexpected error:", error);
253
+ }
254
+ }
255
+ ```
256
+
257
+ ## Ví Dụ Thực Tế
258
+
259
+ ### Hệ Thống Hỗ Trợ Khách Hàng
260
+
261
+ ```typescript
262
+ class CustomerSupportBot {
263
+ constructor(private consultationService: ConsultationService) {}
264
+
265
+ async handleUserQuestion(accessToken: string, userId: string, question: string) {
266
+ try {
267
+ // Phân tích câu hỏi và tạo phản hồi
268
+ const response = this.generateResponse(question);
269
+
270
+ // Gửi tin nhắn tư vấn
271
+ await this.consultationService.sendTextMessage(
272
+ accessToken,
273
+ userId,
274
+ response
275
+ );
276
+
277
+ // Gửi thêm hình ảnh hướng dẫn nếu cần
278
+ if (this.needsVisualGuide(question)) {
279
+ await this.consultationService.sendImageMessage(
280
+ accessToken,
281
+ userId,
282
+ "https://example.com/guide-image.jpg",
283
+ "Hướng dẫn chi tiết"
284
+ );
285
+ }
286
+
287
+ // Gửi file hướng dẫn nếu cần
288
+ if (this.needsDetailedGuide(question)) {
289
+ // Giả sử đã upload file và có token
290
+ const fileToken = "uploaded-file-token";
291
+ await this.consultationService.sendFileMessage(
292
+ accessToken,
293
+ userId,
294
+ fileToken
295
+ );
296
+ }
297
+
298
+ } catch (error) {
299
+ console.error("Failed to send consultation message:", error);
300
+
301
+ // Gửi tin nhắn lỗi cho user
302
+ try {
303
+ await this.consultationService.sendTextMessage(
304
+ accessToken,
305
+ userId,
306
+ "Xin lỗi, hệ thống đang gặp sự cố. Vui lòng thử lại sau."
307
+ );
308
+ } catch (fallbackError) {
309
+ console.error("Failed to send fallback message:", fallbackError);
310
+ }
311
+ }
312
+ }
313
+
314
+ private generateResponse(question: string): string {
315
+ // Logic tạo phản hồi dựa trên câu hỏi
316
+ if (question.includes("đăng nhập")) {
317
+ return "Để đăng nhập, bạn vui lòng làm theo các bước sau:\n1. Mở ứng dụng\n2. Nhấn 'Đăng nhập'\n3. Nhập thông tin tài khoản";
318
+ }
319
+
320
+ if (question.includes("thanh toán")) {
321
+ return "Về vấn đề thanh toán, chúng tôi hỗ trợ các phương thức:\n• Thẻ tín dụng\n• Chuyển khoản\n• Ví điện tử";
322
+ }
323
+
324
+ if (question.includes("hỗ trợ") || question.includes("help")) {
325
+ return "Tôi thể hỗ trợ bạn về:\n1. Đăng nhập tài khoản\n2. Thanh toán\n3. Sử dụng sản phẩm\n4. Khắc phục sự cố\n\nBạn cần hỗ trợ về vấn đề nào?";
326
+ }
327
+
328
+ return "Cảm ơn bạn đã liên hệ. Chúng tôi sẽ hỗ trợ bạn ngay! Vui lòng mô tả chi tiết vấn đề bạn gặp phải.";
329
+ }
330
+
331
+ private needsVisualGuide(question: string): boolean {
332
+ return question.includes("hướng dẫn") ||
333
+ question.includes("cách làm") ||
334
+ question.includes("how to");
335
+ }
336
+
337
+ private needsDetailedGuide(question: string): boolean {
338
+ return question.includes("tài liệu") ||
339
+ question.includes("manual") ||
340
+ question.includes("chi tiết");
341
+ }
342
+ }
343
+ ```
344
+
345
+ ### Tích Hợp Với Webhook
346
+
347
+ ```typescript
348
+ import express from 'express';
349
+ import { ConsultationService } from '@warriorteam/redai-zalo-sdk';
350
+
351
+ const app = express();
352
+ app.use(express.json());
353
+
354
+ // Xử lý tin nhắn từ webhook
355
+ app.post('/webhook', async (req, res) => {
356
+ const event = req.body;
357
+
358
+ if (event.event_name === 'user_send_text') {
359
+ const userId = event.sender.id;
360
+ const userMessage = event.message.text;
361
+
362
+ // Kiểm tra xem có phải câu hỏi cần hỗ trợ không
363
+ if (userMessage.includes('help') || userMessage.includes('hỗ trợ')) {
364
+
365
+ // Gửi tin nhắn tư vấn
366
+ await consultationService.sendTextMessage(
367
+ accessToken,
368
+ userId,
369
+ "Tôi có thể hỗ trợ bạn về các vấn đề sau:\n1. Đăng nhập\n2. Thanh toán\n3. Sử dụng sản phẩm\nBạn cần hỗ trợ về vấn đề nào?"
370
+ );
371
+
372
+ // Gửi sticker thân thiện
373
+ await consultationService.sendStickerMessage(
374
+ accessToken,
375
+ userId,
376
+ "friendly-sticker-id"
377
+ );
378
+ }
379
+
380
+ // Xử lý yêu cầu thông tin
381
+ if (userMessage.includes('thông tin') || userMessage.includes('profile')) {
382
+ await consultationService.sendRequestUserInfoMessage(
383
+ accessToken,
384
+ userId,
385
+ "Yêu cầu thông tin",
386
+ "Để hỗ trợ bạn tốt hơn, vui lòng chia sẻ thông tin",
387
+ "https://example.com/info-request.png"
388
+ );
389
+ }
390
+ }
391
+
392
+ res.status(200).send('OK');
393
+ });
394
+ ```
395
+
396
+ ## Best Practices
397
+
398
+ ### 1. Kiểm Tra Thời Gian Tương Tác
399
+ ```typescript
400
+ // Kiểm tra thời gian tương tác cuối cùng trước khi gửi
401
+ const lastInteraction = await getUserLastInteraction(userId);
402
+ const hoursSinceLastInteraction = (Date.now() - lastInteraction) / (1000 * 60 * 60);
403
+
404
+ if (hoursSinceLastInteraction > 48) {
405
+ console.log("Không thể gửi tin nhắn tư vấn - quá 48 giờ");
406
+ return;
407
+ }
408
+ ```
409
+
410
+ ### 2. Quản Lý Quota
411
+ ```typescript
412
+ // Kiểm tra quota từ response
413
+ const response = await consultationService.sendTextMessage(
414
+ accessToken,
415
+ userId,
416
+ "Hello!"
417
+ );
418
+
419
+ if (response.quota) {
420
+ console.log(`Quota type: ${response.quota.quota_type}`);
421
+ console.log(`Quota remaining: ${response.quota.remain}/${response.quota.total}`);
422
+
423
+ // Cảnh báo khi quota sắp hết
424
+ if (response.quota.remain && parseInt(response.quota.remain) < 5) {
425
+ console.warn("Quota is running low!");
426
+ }
427
+
428
+ // Kiểm tra ngày hết hạn
429
+ if (response.quota.expired_date) {
430
+ console.log(`Quota expires: ${response.quota.expired_date}`);
431
+ }
432
+ }
433
+ ```
434
+
435
+ ### 3. Retry Logic
436
+ ```typescript
437
+ async function sendWithRetry(
438
+ consultationService: ConsultationService,
439
+ accessToken: string,
440
+ userId: string,
441
+ text: string,
442
+ maxRetries = 3
443
+ ) {
444
+ for (let i = 0; i < maxRetries; i++) {
445
+ try {
446
+ return await consultationService.sendTextMessage(accessToken, userId, text);
447
+ } catch (error) {
448
+ console.log(`Attempt ${i + 1} failed:`, error.message);
449
+
450
+ if (i === maxRetries - 1) throw error;
451
+
452
+ // Đợi trước khi retry (exponential backoff)
453
+ await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
454
+ }
455
+ }
456
+ }
457
+ ```
458
+
459
+ ### 4. Rate Limiting
460
+ ```typescript
461
+ class RateLimitedConsultationService {
462
+ private lastSent = new Map<string, number>();
463
+ private readonly minInterval = 1000; // 1 giây giữa các tin nhắn
464
+
465
+ constructor(private consultationService: ConsultationService) {}
466
+
467
+ async sendTextMessage(accessToken: string, userId: string, text: string) {
468
+ const now = Date.now();
469
+ const lastSentTime = this.lastSent.get(userId) || 0;
470
+
471
+ if (now - lastSentTime < this.minInterval) {
472
+ throw new Error(`Rate limit exceeded for user ${userId}`);
473
+ }
474
+
475
+ const result = await this.consultationService.sendTextMessage(accessToken, userId, text);
476
+ this.lastSent.set(userId, now);
477
+
478
+ return result;
479
+ }
480
+ }
481
+ ```
482
+
483
+ ## API Methods Summary
484
+
485
+ | Method | Description | Parameters |
486
+ |--------|-------------|------------|
487
+ | `sendTextMessage` | Gửi tin nhắn văn bản | `accessToken`, `userId`, `text` |
488
+ | `sendImageMessage` | Gửi ảnh bằng URL | `accessToken`, `userId`, `imageUrl`, `text?` |
489
+ | `sendImageByAttachmentId` | Gửi ảnh bằng attachment ID | `accessToken`, `userId`, `attachmentId`, `text?` |
490
+ | `sendGifMessage` | Gửi ảnh GIF | `accessToken`, `userId`, `gifUrl`, `width`, `height`, `text?` |
491
+ | `sendFileMessage` | Gửi file đính kèm | `accessToken`, `userId`, `fileToken` |
492
+ | `sendStickerMessage` | Gửi sticker | `accessToken`, `userId`, `stickerAttachmentId` |
493
+ | `sendRequestUserInfoMessage` | Yêu cầu thông tin người dùng | `accessToken`, `userId`, `title`, `subtitle`, `imageUrl` |
494
+
495
+ ## Lưu Ý Quan Trọng
496
+
497
+ 1. **Tuân thủ chính sách**: Chỉ gửi tin nhắn tư vấn thực sự, không spam
498
+ 2. **Theo dõi quota**: Kiểm tra quota thường xuyên để tránh vượt giới hạn
499
+ 3. **Xử lý lỗi**: Luôn có cơ chế xử lý lỗi phù hợp
500
+ 4. **Logging**: Ghi log để theo dõi và debug
501
+ 5. **Rate limiting**: Tránh gửi quá nhiều tin nhắn trong thời gian ngắn
502
+ 6. **48h rule**: Chỉ gửi trong vòng 48h sau tương tác cuối của user
503
+ 7. **File upload**: Cần upload file trước khi gửi file message
504
+ 8. **Sticker source**: Lấy sticker ID từ https://stickers.zaloapp.com/
505
+
506
+ ## Tài Liệu Liên Quan
507
+
508
+ - [Zalo Official Account API Documentation](https://developers.zalo.me/docs/api/official-account-api)
509
+ - [Consultation Message API](https://developers.zalo.me/docs/api/official-account-api/gui-tin-nhan-tu-van)
510
+ - [Upload File API](https://developers.zalo.me/docs/api/official-account-api/upload-file)
511
+ - [Sticker Collection](https://stickers.zaloapp.com/)
512
+ - [Video Guide](https://vimeo.com/649330161)