UNPKG

@warriorteam/redai-zalo-sdk

Version:

Comprehensive TypeScript/JavaScript SDK for Zalo APIs - Official Account v3.0, ZNS with Full Type Safety, Consultation Service, Broadcast Service, Group Messaging with List APIs, Social APIs, Enhanced Article Management, Promotion Service v3.0 with Multip

513 lines (417 loc) 15.7 kB
# Consultation Service - Hướng Dẫn Sử Dụng ## Tổng Quan `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. **Endpoint API**: `https://openapi.zalo.me/v3.0/oa/message/cs` ## Điều Kiện Gửi Tin Nhắn Tư Vấn ### 1. Thời Gian Gửi - **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 - **Tương tác bao gồm**: - Gửi tin nhắn đến OA - Nhấn button/quick reply - Gọi điện thoại từ OA - Truy cập website từ OA ### 2. Nội Dung Tin Nhắn - **Mục đích**: Phải liên quan đến tư vấn, hỗ trợ khách hàng - **Bao gồm**: Trả lời câu hỏi, hướng dẫn sử dụng, hỗ trợ kỹ thuật - **Không được**: Chứa nội dung quảng cáo trực tiếp ### 3. Điều Kiện Người Dùng - Người dùng phải đã follow OA - Người dùng không được block OA - Người dùng phải có tương tác gần đây với OA ### 4. Tần Suất Gửi - Không giới hạn số lượng tin nhắn tư vấn trong ngày - Cần tuân thủ nguyên tắc không spam ## Khởi Tạo Service ```typescript import { ConsultationService } from "@warriorteam/redai-zalo-sdk"; import { ZaloClient } from "@warriorteam/redai-zalo-sdk"; const client = new ZaloClient(); const consultationService = new ConsultationService(client); ``` ## Các Phương Thức Chính ### 1. Gửi Tin Nhắn Văn Bản ```typescript // Gửi tin nhắn văn bản tư vấn const response = await consultationService.sendTextMessage( accessToken, "user-id-here", "Xin chào! Tôi có thể hỗ trợ gì cho bạn?" ); console.log("Message ID:", response.message_id); console.log("User ID:", response.user_id); console.log("Quota info:", response.quota); ``` **Tham số:** - `accessToken`: Access token của OA - `userId`: ID người nhận - `text`: Nội dung tin nhắn (tối đa 2000 ký tự) **Giới hạn:** - Nội dung không được để trống - Tối đa 2000 ký tự ### 2. Gửi Tin Nhắn Hình Ảnh #### 2.1. Gửi ảnh bằng URL ```typescript // Gửi hình ảnh tư vấn bằng URL const response = await consultationService.sendImageMessage( accessToken, "user-id-here", "https://example.com/support-image.jpg", "Hướng dẫn sử dụng sản phẩm" // Text tùy chọn ); ``` **Tham số:** - `accessToken`: Access token của OA - `userId`: ID người nhận - `imageUrl`: URL hình ảnh (jpg, png, tối đa 1MB) - `text`: Tiêu đề ảnh (tùy chọn, tối đa 2000 ký tự) #### 2.2. Gửi ảnh bằng Attachment ID ```typescript // Gửi hình ảnh đã upload trước đó const response = await consultationService.sendImageByAttachmentId( accessToken, "user-id-here", "attachment-id-from-upload-api", "Hướng dẫn chi tiết" ); ``` **Tham số:** - `accessToken`: Access token của OA - `userId`: ID người nhận - `attachmentId`: ID ảnh đã upload - `text`: Tiêu đề ảnh (tùy chọn) **Lưu ý:** - Chỉ sử dụng 1 trong 2: `url` hoặc `attachment_id` - Định dạng hỗ trợ: jpg, png - Dung lượng tối đa: 1MB - Kích thước tối ưu: 16:9, vùng safe zone 14:9 ### 3. Gửi Tin Nhắn GIF ```typescript // Gửi ảnh GIF động const response = await consultationService.sendGifMessage( accessToken, "user-id-here", "https://example.com/animation.gif", 400, // width 300, // height "Hướng dẫn động" // text tùy chọn ); ``` **Tham số:** - `accessToken`: Access token của OA - `userId`: ID người nhận - `gifUrl`: URL file GIF - `width`: Chiều rộng (bắt buộc cho GIF) - `height`: Chiều cao (bắt buộc cho GIF) - `text`: Tiêu đề (tùy chọn) ### 4. Gửi File Đính Kèm ```typescript // Gửi file đính kèm (cần upload file trước) const response = await consultationService.sendFileMessage( accessToken, "user-id-here", "file-token-from-upload-api" ); ``` **Tham số:** - `accessToken`: Access token của OA - `userId`: ID người nhận - `fileToken`: Token file đã upload **Lưu ý:** Cần sử dụng API upload file trước để lấy token ### 5. Gửi Sticker ```typescript // Gửi sticker const response = await consultationService.sendStickerMessage( accessToken, "user-id-here", "bfe458bf64fa8da4d4eb" ); ``` **Tham số:** - `accessToken`: Access token của OA - `userId`: ID người nhận - `stickerAttachmentId`: ID sticker **Nguồn sticker:** - Website: https://stickers.zaloapp.com/ - Video hướng dẫn: https://vimeo.com/649330161 ### 6. Gửi Mẫu Yêu Cầu Thông Tin Người Dùng ```typescript // Gửi template yêu cầu thông tin const response = await consultationService.sendRequestUserInfoMessage( accessToken, "user-id-here", "OA Chatbot (Testing)", "Đang yêu cầu thông tin từ bạn", "https://developers.zalo.me/web/static/zalo.png" ); ``` **Tham số:** - `accessToken`: Access token của OA - `userId`: ID người nhận - `title`: Tiêu đề (tối đa 100 ký tự) - `subtitle`: Tiêu đề phụ (tối đa 500 ký tự) - `imageUrl`: URL hình ảnh ## Response Format Tất cả các API đều trả về response theo format sau: ```typescript interface SendMessageResponse { message_id: string; user_id: string; quota?: { quota_type: "reply" | "sub_quota" | "purchase_quota" | "reward_quota"; remain?: string; total?: string; expired_date?: string; owner_type?: "OA" | "App"; owner_id?: string; }; } ``` **Các loại quota:** 1. **reply**: Tin trong khung 8 tin 48h 2. **sub_quota**: Tin miễn phí theo gói 3. **purchase_quota**: Tin trong gói tính năng lẻ 4. **reward_quota**: Tin trong hạn mức Redeem code 5. **Không có quota**: Tin tính phí ## Xử Lý Lỗi ```typescript import { ZaloSDKError } from "@warriorteam/redai-zalo-sdk"; try { const response = await consultationService.sendTextMessage( accessToken, "user-id", "Hello!" ); console.log("Sent successfully:", response.message_id); } catch (error) { if (error instanceof ZaloSDKError) { console.error("Zalo API Error:", error.message); console.error("Error Code:", error.code); // Xử lý các lỗi phổ biến switch (error.code) { case -216: console.log("Access token không hợp lệ"); break; case -201: console.log("Tham số không hợp lệ"); break; case -223: console.log("Đã vượt quá quota"); break; default: console.log("Lỗi khác:", error.message); } } else { console.error("Unexpected error:", error); } } ``` ## Ví Dụ Thực Tế ### Hệ Thống Hỗ Trợ Khách Hàng ```typescript class CustomerSupportBot { constructor(private consultationService: ConsultationService) {} async handleUserQuestion(accessToken: string, userId: string, question: string) { try { // Phân tích câu hỏi và tạo phản hồi const response = this.generateResponse(question); // Gửi tin nhắn tư vấn await this.consultationService.sendTextMessage( accessToken, userId, response ); // Gửi thêm hình ảnh hướng dẫn nếu cần if (this.needsVisualGuide(question)) { await this.consultationService.sendImageMessage( accessToken, userId, "https://example.com/guide-image.jpg", "Hướng dẫn chi tiết" ); } // Gửi file hướng dẫn nếu cần if (this.needsDetailedGuide(question)) { // Giả sử đã upload file và có token const fileToken = "uploaded-file-token"; await this.consultationService.sendFileMessage( accessToken, userId, fileToken ); } } catch (error) { console.error("Failed to send consultation message:", error); // Gửi tin nhắn lỗi cho user try { await this.consultationService.sendTextMessage( accessToken, userId, "Xin lỗi, hệ thống đang gặp sự cố. Vui lòng thử lại sau." ); } catch (fallbackError) { console.error("Failed to send fallback message:", fallbackError); } } } private generateResponse(question: string): string { // Logic tạo phản hồi dựa trên câu hỏi if (question.includes("đăng nhập")) { 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"; } if (question.includes("thanh toán")) { 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ử"; } if (question.includes("hỗ trợ") || question.includes("help")) { return "Tôi có 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?"; } 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."; } private needsVisualGuide(question: string): boolean { return question.includes("hướng dẫn") || question.includes("cách làm") || question.includes("how to"); } private needsDetailedGuide(question: string): boolean { return question.includes("tài liệu") || question.includes("manual") || question.includes("chi tiết"); } } ``` ### Tích Hợp Với Webhook ```typescript import express from 'express'; import { ConsultationService } from '@warriorteam/redai-zalo-sdk'; const app = express(); app.use(express.json()); // Xử lý tin nhắn từ webhook app.post('/webhook', async (req, res) => { const event = req.body; if (event.event_name === 'user_send_text') { const userId = event.sender.id; const userMessage = event.message.text; // Kiểm tra xem có phải câu hỏi cần hỗ trợ không if (userMessage.includes('help') || userMessage.includes('hỗ trợ')) { // Gửi tin nhắn tư vấn await consultationService.sendTextMessage( accessToken, userId, "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?" ); // Gửi sticker thân thiện await consultationService.sendStickerMessage( accessToken, userId, "friendly-sticker-id" ); } // Xử lý yêu cầu thông tin if (userMessage.includes('thông tin') || userMessage.includes('profile')) { await consultationService.sendRequestUserInfoMessage( accessToken, userId, "Yêu cầu thông tin", "Để hỗ trợ bạn tốt hơn, vui lòng chia sẻ thông tin", "https://example.com/info-request.png" ); } } res.status(200).send('OK'); }); ``` ## Best Practices ### 1. Kiểm Tra Thời Gian Tương Tác ```typescript // Kiểm tra thời gian tương tác cuối cùng trước khi gửi const lastInteraction = await getUserLastInteraction(userId); const hoursSinceLastInteraction = (Date.now() - lastInteraction) / (1000 * 60 * 60); if (hoursSinceLastInteraction > 48) { console.log("Không thể gửi tin nhắn tư vấn - quá 48 giờ"); return; } ``` ### 2. Quản Lý Quota ```typescript // Kiểm tra quota từ response const response = await consultationService.sendTextMessage( accessToken, userId, "Hello!" ); if (response.quota) { console.log(`Quota type: ${response.quota.quota_type}`); console.log(`Quota remaining: ${response.quota.remain}/${response.quota.total}`); // Cảnh báo khi quota sắp hết if (response.quota.remain && parseInt(response.quota.remain) < 5) { console.warn("Quota is running low!"); } // Kiểm tra ngày hết hạn if (response.quota.expired_date) { console.log(`Quota expires: ${response.quota.expired_date}`); } } ``` ### 3. Retry Logic ```typescript async function sendWithRetry( consultationService: ConsultationService, accessToken: string, userId: string, text: string, maxRetries = 3 ) { for (let i = 0; i < maxRetries; i++) { try { return await consultationService.sendTextMessage(accessToken, userId, text); } catch (error) { console.log(`Attempt ${i + 1} failed:`, error.message); if (i === maxRetries - 1) throw error; // Đợi trước khi retry (exponential backoff) await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i))); } } } ``` ### 4. Rate Limiting ```typescript class RateLimitedConsultationService { private lastSent = new Map<string, number>(); private readonly minInterval = 1000; // 1 giây giữa các tin nhắn constructor(private consultationService: ConsultationService) {} async sendTextMessage(accessToken: string, userId: string, text: string) { const now = Date.now(); const lastSentTime = this.lastSent.get(userId) || 0; if (now - lastSentTime < this.minInterval) { throw new Error(`Rate limit exceeded for user ${userId}`); } const result = await this.consultationService.sendTextMessage(accessToken, userId, text); this.lastSent.set(userId, now); return result; } } ``` ## API Methods Summary | Method | Description | Parameters | |--------|-------------|------------| | `sendTextMessage` | Gửi tin nhắn văn bản | `accessToken`, `userId`, `text` | | `sendImageMessage` | Gửi ảnh bằng URL | `accessToken`, `userId`, `imageUrl`, `text?` | | `sendImageByAttachmentId` | Gửi ảnh bằng attachment ID | `accessToken`, `userId`, `attachmentId`, `text?` | | `sendGifMessage` | Gửi ảnh GIF | `accessToken`, `userId`, `gifUrl`, `width`, `height`, `text?` | | `sendFileMessage` | Gửi file đính kèm | `accessToken`, `userId`, `fileToken` | | `sendStickerMessage` | Gửi sticker | `accessToken`, `userId`, `stickerAttachmentId` | | `sendRequestUserInfoMessage` | Yêu cầu thông tin người dùng | `accessToken`, `userId`, `title`, `subtitle`, `imageUrl` | ## Lưu Ý Quan Trọng 1. **Tuân thủ chính sách**: Chỉ gửi tin nhắn tư vấn thực sự, không spam 2. **Theo dõi quota**: Kiểm tra quota thường xuyên để tránh vượt giới hạn 3. **Xử lý lỗi**: Luôn có cơ chế xử lý lỗi phù hợp 4. **Logging**: Ghi log để theo dõi và debug 5. **Rate limiting**: Tránh gửi quá nhiều tin nhắn trong thời gian ngắn 6. **48h rule**: Chỉ gửi trong vòng 48h sau tương tác cuối của user 7. **File upload**: Cần upload file trước khi gửi file message 8. **Sticker source**: Lấy sticker ID từ https://stickers.zaloapp.com/ ## Tài Liệu Liên Quan - [Zalo Official Account API Documentation](https://developers.zalo.me/docs/api/official-account-api) - [Consultation Message API](https://developers.zalo.me/docs/api/official-account-api/gui-tin-nhan-tu-van) - [Upload File API](https://developers.zalo.me/docs/api/official-account-api/upload-file) - [Sticker Collection](https://stickers.zaloapp.com/) - [Video Guide](https://vimeo.com/649330161)