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

1,224 lines (1,032 loc) 32.3 kB
# RedAI Zalo SDK - Message Services Guide ## Tổng quan RedAI Zalo SDK cung cấp các Message Services chuyên biệt để gửi các loại tin nhắn khác nhau qua Zalo Official Account. Mỗi loại tin nhắn có mục đích, quy tắc và giới hạn riêng. ## Các loại Message Services | Service | Mục đích | Thời gian gửi | Giới hạn | |---------|----------|---------------|----------| | **ConsultationService** | Hỗ trợ khách hàng | Trong 48h sau tương tác | Không giới hạn | | **TransactionService** | Thông báo giao dịch | Bất cứ lúc nào | Theo quota | | **PromotionService** | Tin nhắn khuyến mại | Theo quy định | Giới hạn theo gói | | **GeneralMessageService** | Tin nhắn chung | Phản hồi user | Theo quota | | **MessageManagementService** | Quản lý tin nhắn | N/A | N/A | --- ## ConsultationService Dùng để **hỗ trợ khách hàng** trong vòng 48 giờ sau khi user tương tác với OA. ### Khởi tạo ```typescript import { ZaloSDK } from "@warriorteam/redai-zalo-sdk"; const zalo = new ZaloSDK({ appId: "your-app-id", appSecret: "your-app-secret" }); // Access consultation service const consultationService = zalo.consultation; ``` ### 1. Gửi tin nhắn text ```typescript // Gửi text consultation message await zalo.consultation.sendTextMessage( accessToken, { user_id: "user-zalo-id" }, { type: "text", text: "Xin chào! Tôi có thể hỗ trợ gì cho bạn không?" } ); ``` ### 2. Gửi hình ảnh ```typescript // Gửi hình ảnh hướng dẫn await zalo.consultation.sendImageMessage( accessToken, { user_id: "user-zalo-id" }, { type: "image", attachment_id: "image-attachment-id", // ID sau khi upload // hoặc sử dụng URL attachment: { type: "image", payload: { url: "https://example.com/support-image.jpg" } } } ); ``` ### 3. Gửi file đính kèm ```typescript // Gửi file hướng dẫn, catalog await zalo.consultation.sendFileMessage( accessToken, { user_id: "user-zalo-id" }, { type: "file", url: "https://example.com/user-manual.pdf", filename: "Hướng dẫn sử dụng.pdf", attachment: { type: "file", payload: { url: "https://example.com/user-manual.pdf" } } } ); ``` ### 4. Gửi sticker ```typescript // Gửi sticker để tạo không khí thân thiện await zalo.consultation.sendStickerMessage( accessToken, { user_id: "user-zalo-id" }, { type: "sticker", attachment: { type: "sticker", payload: { sticker_id: "sticker-id" } } } ); ``` ### 5. Gửi tin nhắn quote (trích dẫn) ```typescript // Quote tin nhắn trước đó của user await zalo.consultation.sendQuoteMessage( accessToken, { user_id: "user-zalo-id" }, { type: "quote", text: "Cảm ơn bạn đã hỏi về sản phẩm này", quote_msg_id: "original-message-id" } ); ``` ### 6. Yêu cầu thông tin user ```typescript // Yêu cầu user cung cấp thông tin await zalo.consultation.sendRequestInfoMessage( accessToken, { user_id: "user-zalo-id" }, { type: "request_user_info", title: "Thông tin liên hệ", subtitle: "Vui lòng cung cấp thông tin để chúng tôi hỗ trợ tốt hơn", image_url: "https://example.com/contact-form.jpg" } ); ``` ### Consultation Use Cases ```typescript class CustomerSupportService { constructor(private zalo: ZaloSDK, private accessToken: string) {} // Phản hồi câu hỏi về sản phẩm async respondToProductInquiry(userId: string, productId: string) { const product = await this.getProductInfo(productId); // Gửi thông tin sản phẩm await this.zalo.consultation.sendTextMessage( this.accessToken, { user_id: userId }, { type: "text", text: `Thông tin sản phẩm ${product.name}: 💰 Giá: ${product.price.toLocaleString('vi-VN')} VNĐ 📦 Tình trạng: ${product.stock > 0 ? 'Còn hàng' : 'Hết hàng'} 🚚 Giao hàng: ${product.deliveryTime} Bạn có muốn đặt hàng không?` } ); // Gửi hình ảnh sản phẩm if (product.imageUrl) { await this.zalo.consultation.sendImageMessage( this.accessToken, { user_id: userId }, { type: "image", attachment: { type: "image", payload: { url: product.imageUrl } } } ); } } // Hỗ trợ khiếu nại async handleComplaint(userId: string, complaintText: string) { // Quote lại tin nhắn khiếu nại await this.zalo.consultation.sendQuoteMessage( this.accessToken, { user_id: userId }, { type: "quote", text: "Chúng tôi đã nhận được phản hồi của bạn và sẽ xử lý trong 24h. Cảm ơn bạn đã góp ý!", quote_msg_id: complaintText // ID của tin nhắn khiếu nại } ); // Yêu cầu thêm thông tin nếu cần await this.zalo.consultation.sendRequestInfoMessage( this.accessToken, { user_id: userId }, { type: "request_user_info", title: "Thông tin bổ sung", subtitle: "Để xử lý khiếu nại hiệu quả, vui lòng cung cấp thêm thông tin liên hệ" } ); } } ``` --- ## TransactionService Gửi tin nhắn **thông báo giao dịch** như đơn hàng, thanh toán, vận chuyển. ### Khởi tạo ```typescript const transactionService = zalo.transaction; ``` ### Gửi transaction message ```typescript // Thông báo đơn hàng await zalo.transaction.sendTransactionMessage( accessToken, { user_id: "user-zalo-id" }, { type: "transaction", template_type: "order_update", template_data: { order_id: "DH001234", status: "Đã xác nhận", total_amount: "299,000 VNĐ", items: "Áo thun x2, Quần jean x1", delivery_date: "25/12/2024" }, attachment: { type: "template", payload: { template_type: "receipt", elements: [{ title: "Đơn hàng #DH001234", subtitle: "Tổng tiền: 299,000 VNĐ", image_url: "https://example.com/order-receipt.jpg", buttons: [{ type: "web_url", title: "Xem chi tiết", url: "https://mystore.com/order/DH001234" }] }] } } } ); ``` ### Transaction Use Cases ```typescript class TransactionNotificationService { constructor(private zalo: ZaloSDK, private accessToken: string) {} // Thông báo xác nhận đơn hàng async sendOrderConfirmation(order: Order) { await this.zalo.transaction.sendTransactionMessage( this.accessToken, { user_id: order.customerId }, { type: "transaction", template_type: "order_confirmation", template_data: { customer_name: order.customerName, order_id: order.id, order_date: this.formatDate(order.createdAt), total_amount: this.formatCurrency(order.totalAmount), payment_method: order.paymentMethod, delivery_address: order.deliveryAddress, estimated_delivery: this.formatDate(order.estimatedDelivery) } } ); } // Cập nhật trạng thái vận chuyển async sendShippingUpdate(order: Order, trackingInfo: TrackingInfo) { await this.zalo.transaction.sendTransactionMessage( this.accessToken, { user_id: order.customerId }, { type: "transaction", template_type: "shipping_update", template_data: { order_id: order.id, status: trackingInfo.status, tracking_code: trackingInfo.trackingCode, current_location: trackingInfo.currentLocation, estimated_delivery: this.formatDate(trackingInfo.estimatedDelivery) }, attachment: { type: "template", payload: { template_type: "generic", elements: [{ title: `Đơn hàng ${order.id}`, subtitle: `Trạng thái: ${trackingInfo.status}`, buttons: [{ type: "web_url", title: "Tra cứu vận đơn", url: `https://tracking.com/${trackingInfo.trackingCode}` }] }] } } } ); } // Thông báo thanh toán async sendPaymentNotification(payment: Payment) { await this.zalo.transaction.sendTransactionMessage( this.accessToken, { user_id: payment.customerId }, { type: "transaction", template_type: "payment_confirmation", template_data: { transaction_id: payment.transactionId, amount: this.formatCurrency(payment.amount), payment_method: payment.method, payment_date: this.formatDateTime(payment.createdAt), merchant_name: payment.merchantName } } ); } private formatCurrency(amount: number): string { return new Intl.NumberFormat('vi-VN').format(amount) + ' VNĐ'; } private formatDate(date: Date): string { return date.toLocaleDateString('vi-VN'); } private formatDateTime(date: Date): string { return date.toLocaleString('vi-VN'); } } ``` --- ## PromotionService Gửi tin nhắn **khuyến mại và marketing** theo quy định của Zalo. ### Khởi tạo ```typescript const promotionService = zalo.promotion; ``` ### Gửi promotion message ```typescript // Thông báo khuyến mại await zalo.promotion.sendPromotionMessage( accessToken, { user_id: "user-zalo-id" }, { type: "promotion", template_type: "sale_announcement", template_data: { promotion_title: "FLASH SALE - Giảm 50%", discount_percentage: "50%", original_price: "500,000 VNĐ", discounted_price: "250,000 VNĐ", valid_until: "31/12/2024 23:59", terms_conditions: "Áp dụng cho đơn hàng từ 200,000 VNĐ" }, attachment: { type: "template", payload: { template_type: "generic", elements: [{ title: "🔥 FLASH SALE - Giảm 50%", subtitle: "Chỉ còn 2 ngày! Đừng bỏ lỡ cơ hội tiết kiệm lớn", image_url: "https://example.com/flash-sale-banner.jpg", buttons: [ { type: "web_url", title: "Mua ngay", url: "https://mystore.com/flash-sale" }, { type: "postback", title: "Xem thêm", payload: "VIEW_MORE_PROMOTIONS" } ] }] } } } ); ``` ### Promotion Use Cases ```typescript class MarketingCampaignService { constructor(private zalo: ZaloSDK, private accessToken: string) {} // Campaign sale sự kiện async sendEventSaleCampaign(userSegment: string[], campaign: Campaign) { const results = []; for (const userId of userSegment) { try { await this.zalo.promotion.sendPromotionMessage( this.accessToken, { user_id: userId }, { type: "promotion", template_type: "event_sale", template_data: { customer_name: await this.getUserName(userId), event_name: campaign.eventName, discount_code: this.generatePersonalizedCode(userId), discount_value: campaign.discountValue, min_order_value: campaign.minOrderValue, valid_until: this.formatDate(campaign.endDate) } } ); results.push({ userId, success: true }); } catch (error) { results.push({ userId, success: false, error: error.message }); } } return results; } // Thông báo sản phẩm mới async sendNewProductAnnouncement(product: Product, targetUsers: string[]) { for (const userId of targetUsers) { await this.zalo.promotion.sendPromotionMessage( this.accessToken, { user_id: userId }, { type: "promotion", template_type: "new_product", template_data: { product_name: product.name, product_description: product.description, launch_price: this.formatCurrency(product.launchPrice), regular_price: this.formatCurrency(product.regularPrice), availability_date: this.formatDate(product.launchDate) }, attachment: { type: "template", payload: { template_type: "generic", elements: [{ title: `🆕 ${product.name}`, subtitle: product.description, image_url: product.imageUrl, buttons: [{ type: "web_url", title: "Đặt hàng ngay", url: `https://mystore.com/product/${product.id}` }] }] } } } ); } } // Nhắc nhở giỏ hàng bỏ dở async sendAbandonedCartReminder(userId: string, cart: Cart) { await this.zalo.promotion.sendPromotionMessage( this.accessToken, { user_id: userId }, { type: "promotion", template_type: "cart_abandonment", template_data: { customer_name: cart.customerName, cart_total: this.formatCurrency(cart.totalValue), items_count: cart.items.length.toString(), special_offer: "Giảm thêm 10% nếu hoàn tất đơn hàng trong 24h", discount_code: this.generateCartRecoveryCode(userId) } } ); } private generatePersonalizedCode(userId: string): string { return `PERSONAL${userId.slice(-6).toUpperCase()}`; } private generateCartRecoveryCode(userId: string): string { return `RECOVERY${Date.now().toString().slice(-6)}`; } } ``` --- ## GeneralMessageService Gửi tin nhắn **phản hồi chung** và **anonymous messages**. ### Khởi tạo ```typescript const generalService = zalo.generalMessage; ``` ### 1. Gửi response message ```typescript // Phản hồi tin nhắn của user await zalo.generalMessage.sendResponseMessage( accessToken, { user_id: "user-zalo-id" }, { type: "text", text: "Cảm ơn bạn đã liên hệ với chúng tôi!" } ); ``` ### 2. Gửi anonymous message ```typescript // Gửi tin nhắn anonymous (không hiện tên OA) await zalo.generalMessage.sendAnonymousMessage( accessToken, { user_id: "user-zalo-id" }, { type: "text", text: "Đây là tin nhắn anonymous từ hệ thống", is_anonymous: true } ); ``` ### General Message Use Cases ```typescript class AutoResponseService { constructor(private zalo: ZaloSDK, private accessToken: string) {} // Auto response cho các câu hỏi thường gặp async handleAutoResponse(userId: string, messageText: string) { const responses = this.getAutoResponses(); for (const [keyword, response] of responses) { if (messageText.toLowerCase().includes(keyword)) { await this.zalo.generalMessage.sendResponseMessage( this.accessToken, { user_id: userId }, { type: "text", text: response } ); break; } } } // Gửi thông báo hệ thống async sendSystemNotification(userId: string, notificationType: string) { let message = ""; switch (notificationType) { case "maintenance": message = "🔧 Hệ thống đang bảo trì từ 2:00 - 4:00 sáng ngày mai. Xin lỗi vì sự bất tiện!"; break; case "service_update": message = "🆙 Chúng tôi vừa cập nhật tính năng mới! Hãy trải nghiệm nhé."; break; default: message = "📢 Thông báo từ hệ thống"; } await this.zalo.generalMessage.sendAnonymousMessage( this.accessToken, { user_id: userId }, { type: "text", text: message, is_anonymous: true } ); } private getAutoResponses(): Map<string, string> { return new Map([ ["giờ mở cửa", "🕒 Giờ mở cửa: 8:00 - 22:00 hàng ngày"], ["địa chỉ", "📍 Địa chỉ: 123 Nguyễn Văn A, Q.1, TP.HCM"], ["liên hệ", "📞 Hotline: 1800-xxx-xxx\n📧 Email: support@example.com"], ["giao hàng", "🚚 Giao hàng: 1-3 ngày làm việc trong nội thành"], ["thanh toán", "💳 Hình thức thanh toán: Tiền mặt, chuyển khoản, thẻ tín dụng"] ]); } } ``` --- ## MessageManagementService Quản lý và phân tích tin nhắn đã gửi. ### Khởi tạo ```typescript const messageManagement = zalo.messageManagement; ``` ### 1. Lấy lịch sử tin nhắn ```typescript // Lấy lịch sử tin nhắn với user const messageHistory = await zalo.messageManagement.getMessageHistory( accessToken, { user_id: "user-zalo-id", from_time: Date.now() - (7 * 24 * 60 * 60 * 1000), // 7 days ago to_time: Date.now(), offset: 0, limit: 50 } ); messageHistory.data.forEach(message => { console.log(`${message.time}: ${message.message}`); }); ``` ### 2. Thống kê tin nhắn ```typescript // Lấy analytics tin nhắn const analytics = await zalo.messageManagement.getMessageAnalytics( accessToken, { from_date: "2024-12-01", to_date: "2024-12-31", message_type: "all" // consultation, transaction, promotion } ); console.log("Tổng tin nhắn gửi:", analytics.total_sent); console.log("Tỷ lệ đọc:", analytics.read_rate); console.log("Tỷ lệ phản hồi:", analytics.response_rate); ``` ### 3. Kiểm tra trạng thái tin nhắn ```typescript // Kiểm tra tin nhắn đã được gửi thành công chưa const messageStatus = await zalo.messageManagement.getMessageStatus( accessToken, "message-id" ); console.log("Status:", messageStatus.status); // sent, delivered, read, failed console.log("Sent time:", messageStatus.sent_time); console.log("Delivered time:", messageStatus.delivered_time); ``` ### Message Management Use Cases ```typescript class MessageAnalyticsService { constructor(private zalo: ZaloSDK, private accessToken: string) {} // Báo cáo hiệu quả campaign async getCampaignReport(campaignId: string, dateRange: DateRange) { const analytics = await this.zalo.messageManagement.getMessageAnalytics( this.accessToken, { from_date: this.formatDate(dateRange.start), to_date: this.formatDate(dateRange.end), campaign_id: campaignId } ); return { campaignId, totalSent: analytics.total_sent, deliveryRate: (analytics.delivered / analytics.total_sent * 100).toFixed(2) + '%', readRate: (analytics.read / analytics.delivered * 100).toFixed(2) + '%', responseRate: (analytics.responses / analytics.read * 100).toFixed(2) + '%', conversionRate: analytics.conversion_rate || '0%' }; } // Phân tích tương tác của user async getUserEngagementAnalysis(userId: string) { const history = await this.zalo.messageManagement.getMessageHistory( this.accessToken, { user_id: userId, from_time: Date.now() - (30 * 24 * 60 * 60 * 1000), // 30 days to_time: Date.now(), limit: 1000 } ); const sentMessages = history.data.filter(msg => msg.from_oa); const receivedMessages = history.data.filter(msg => !msg.from_oa); return { userId, messagesSent: sentMessages.length, messagesReceived: receivedMessages.length, avgResponseTime: this.calculateAvgResponseTime(history.data), lastInteraction: history.data[0]?.time, engagementLevel: this.calculateEngagementLevel(sentMessages, receivedMessages) }; } // Tìm tin nhắn chưa được phản hồi async findUnrespondedMessages(timeWindow: number = 24 * 60 * 60 * 1000) { // Lấy tất cả conversations trong khoảng thời gian const cutoffTime = Date.now() - timeWindow; const conversations = await this.getAllActiveConversations(); const unresponded = []; for (const conv of conversations) { const history = await this.zalo.messageManagement.getMessageHistory( this.accessToken, { user_id: conv.user_id, from_time: cutoffTime, to_time: Date.now(), limit: 10 } ); const lastMessage = history.data[0]; if (lastMessage && !lastMessage.from_oa && lastMessage.time > cutoffTime) { unresponded.push({ userId: conv.user_id, lastMessage: lastMessage.message, waitingTime: Date.now() - lastMessage.time }); } } return unresponded; } private calculateAvgResponseTime(messages: any[]): number { let totalResponseTime = 0; let responseCount = 0; for (let i = 1; i < messages.length; i++) { const current = messages[i]; const previous = messages[i - 1]; if (current.from_oa && !previous.from_oa) { totalResponseTime += (previous.time - current.time); responseCount++; } } return responseCount > 0 ? totalResponseTime / responseCount : 0; } private calculateEngagementLevel(sentMessages: any[], receivedMessages: any[]): string { const ratio = receivedMessages.length / (sentMessages.length || 1); if (ratio > 2) return "Very High"; if (ratio > 1.5) return "High"; if (ratio > 1) return "Medium"; if (ratio > 0.5) return "Low"; return "Very Low"; } } ``` --- ## Message Templates & Rich Content ### 1. Template Messages ```typescript // Generic template với buttons const genericTemplate = { type: "template", attachment: { type: "template", payload: { template_type: "generic", elements: [{ title: "Sản phẩm mới ra mắt", subtitle: "iPhone 15 Pro Max - Titanium mới", image_url: "https://example.com/iphone15.jpg", buttons: [ { type: "web_url", title: "Xem chi tiết", url: "https://store.com/iphone15" }, { type: "postback", title: "Đặt hàng", payload: "ORDER_IPHONE15" } ] }] } } }; await zalo.consultation.sendMessage(accessToken, recipient, genericTemplate); ``` ### 2. List Template ```typescript // List template cho menu hoặc catalog const listTemplate = { type: "template", attachment: { type: "template", payload: { template_type: "list", top_element_style: "compact", // hoặc "large" elements: [ { title: "iPhone 15 Pro", subtitle: "From $999", image_url: "https://example.com/iphone15pro.jpg", buttons: [{ type: "web_url", title: "Buy", url: "https://store.com/iphone15pro" }] }, { title: "iPhone 15", subtitle: "From $799", image_url: "https://example.com/iphone15.jpg", buttons: [{ type: "web_url", title: "Buy", url: "https://store.com/iphone15" }] } ] } } }; ``` ### 3. Button Template ```typescript // Button template cho quick actions const buttonTemplate = { type: "template", attachment: { type: "template", payload: { template_type: "button", text: "Bạn muốn làm gì tiếp theo?", buttons: [ { type: "web_url", title: "Xem sản phẩm", url: "https://store.com/products" }, { type: "postback", title: "Liên hệ hỗ trợ", payload: "CONTACT_SUPPORT" }, { type: "phone_number", title: "Gọi hotline", payload: "+84123456789" } ] } } }; ``` --- ## Best Practices ### 1. Message Timing ```typescript // Kiểm tra thời gian thích hợp để gửi tin nhắn function isAppropriateTime(): boolean { const now = new Date(); const hour = now.getHours(); // Tránh gửi tin nhắn quá sớm (trước 8h) hoặc quá muộn (sau 22h) return hour >= 8 && hour <= 22; } async function sendMessageAtAppropriateTime( messageFunc: () => Promise<any> ): Promise<void> { if (isAppropriateTime()) { await messageFunc(); } else { // Schedule message for later console.log("Message scheduled for appropriate time"); // Implement scheduling logic } } ``` ### 2. Message Personalization ```typescript class MessagePersonalizer { static personalizeMessage(template: string, userData: UserData): string { return template .replace(/\{name\}/g, userData.name || "bạn") .replace(/\{first_name\}/g, userData.firstName || "bạn") .replace(/\{last_purchase\}/g, userData.lastPurchase || "gần đây") .replace(/\{city\}/g, userData.city || ""); } static getPersonalizedGreeting(userData: UserData): string { const hour = new Date().getHours(); let greeting; if (hour < 12) greeting = "Chào buổi sáng"; else if (hour < 18) greeting = "Chào buổi chiều"; else greeting = "Chào buổi tối"; const name = userData.name || "bạn"; return `${greeting} ${name}! `; } } ``` ### 3. Rate Limiting & Queue Management ```typescript class MessageQueue { private queue: Array<MessageTask> = []; private processing = false; private readonly maxConcurrent = 5; private readonly delayBetweenMessages = 1000; // 1 second async addMessage(task: MessageTask): Promise<void> { this.queue.push(task); if (!this.processing) { this.processQueue(); } } private async processQueue(): Promise<void> { this.processing = true; while (this.queue.length > 0) { const batch = this.queue.splice(0, this.maxConcurrent); const promises = batch.map(async (task) => { try { await task.execute(); console.log(`✅ Message sent to ${task.userId}`); } catch (error) { console.error(`❌ Failed to send to ${task.userId}:`, error); } }); await Promise.all(promises); // Delay between batches if (this.queue.length > 0) { await this.delay(this.delayBetweenMessages); } } this.processing = false; } private delay(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } } interface MessageTask { userId: string; execute: () => Promise<any>; } ``` --- ## Error Handling & Retry Logic ### 1. Message-specific Error Handling ```typescript async function sendMessageWithErrorHandling( messageFunc: () => Promise<any>, messageType: string ): Promise<boolean> { try { await messageFunc(); return true; } catch (error) { console.error(`Failed to send ${messageType} message:`, error); switch (error.code) { case -216: console.error("Access token invalid - need to refresh"); break; case -201: console.error("Invalid message parameters"); break; case -223: console.error(`${messageType} quota exceeded`); break; case -232: console.error("User has blocked the OA"); break; case -233: console.error("User not found"); break; default: console.error("Unexpected error occurred"); } return false; } } ``` ### 2. Comprehensive Message Service Wrapper ```typescript class MessageServiceWrapper { constructor(private zalo: ZaloSDK, private accessToken: string) {} async sendConsultation(userId: string, message: any): Promise<boolean> { return sendMessageWithErrorHandling( () => this.zalo.consultation.sendTextMessage(this.accessToken, { user_id: userId }, message), 'consultation' ); } async sendTransaction(userId: string, message: any): Promise<boolean> { return sendMessageWithErrorHandling( () => this.zalo.transaction.sendTransactionMessage(this.accessToken, { user_id: userId }, message), 'transaction' ); } async sendPromotion(userId: string, message: any): Promise<boolean> { return sendMessageWithErrorHandling( () => this.zalo.promotion.sendPromotionMessage(this.accessToken, { user_id: userId }, message), 'promotion' ); } } ``` --- ## Testing Message Services ### 1. Unit Tests ```typescript // message-services.test.ts import { ZaloSDK } from '@warriorteam/redai-zalo-sdk'; describe('Message Services', () => { const zalo = new ZaloSDK({ appId: 'test_app_id', appSecret: 'test_app_secret' }); it('should send consultation message', async () => { const mockResponse = { error: 0, message: "Success" }; jest.spyOn(zalo.consultation, 'sendTextMessage').mockResolvedValue(mockResponse); const result = await zalo.consultation.sendTextMessage( 'test_token', { user_id: 'test_user' }, { type: 'text', text: 'Test message' } ); expect(result.error).toBe(0); }); it('should handle different message types', async () => { const messageTypes = ['consultation', 'transaction', 'promotion']; for (const type of messageTypes) { const service = zalo[type]; expect(service).toBeDefined(); } }); }); ``` --- ## Performance Optimization ### 1. Message Caching ```typescript class MessageCache { private cache = new Map<string, any>(); private readonly ttl = 5 * 60 * 1000; // 5 minutes set(key: string, value: any): void { this.cache.set(key, { value, timestamp: Date.now() }); } get(key: string): any | null { const item = this.cache.get(key); if (!item) return null; if (Date.now() - item.timestamp > this.ttl) { this.cache.delete(key); return null; } return item.value; } // Cache user info để tránh gọi API nhiều lần async getUserInfo(userId: string): Promise<UserInfo> { const cached = this.get(`user_${userId}`); if (cached) return cached; const userInfo = await this.fetchUserInfo(userId); this.set(`user_${userId}`, userInfo); return userInfo; } } ``` ### 2. Batch Message Processing ```typescript class BatchMessageProcessor { async processBatchMessages( messages: Array<{userId: string, messageType: string, content: any}> ): Promise<BatchResult> { const results = { successful: 0, failed: 0, errors: [] as Array<{userId: string, error: string}> }; // Group messages by type for better processing const groupedMessages = this.groupMessagesByType(messages); for (const [messageType, messageGroup] of groupedMessages) { const batchResults = await this.processSameTypeMessages(messageType, messageGroup); results.successful += batchResults.successful; results.failed += batchResults.failed; results.errors.push(...batchResults.errors); } return results; } private groupMessagesByType(messages: any[]): Map<string, any[]> { const groups = new Map(); for (const message of messages) { if (!groups.has(message.messageType)) { groups.set(message.messageType, []); } groups.get(message.messageType).push(message); } return groups; } } ``` --- ## Next Steps Sau khi nắm vững Message Services: 1. **[User Management](./USER_MANAGEMENT.md)** - Quản lý danh sách người dùng 2. **[Tag Management](./TAG_MANAGEMENT.md)** - Phân đoạn và tag users 3. **[Webhook Events](./WEBHOOK_EVENTS.md)** - Xử lý events thời gian thực 4. **[Error Handling](./ERROR_HANDLING.md)** - Xử lý lỗi toàn diện 5. **[Group Management](./GROUP_MANAGEMENT.md)** - Quản lý Zalo groups Tham khảo **[API Reference](./API_REFERENCE.md)** để biết chi tiết về tất cả message methods có sẵn.