@warriorteam/redai-zalo-sdk
Version:
Comprehensive TypeScript/JavaScript SDK for Zalo APIs - Official Account, ZNS, Consultation Service, Group Messaging, and Social APIs
448 lines • 18 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ZNSService = void 0;
/**
* Service for Zalo Notification Service (ZNS)
*
* Requirements for using ZNS API:
* - Official Account must be approved and have ZNS sending permission
* - Valid Official Account access token
* - ZNS template must be approved before use
* - Recipient phone number must be in correct format and valid
* - Template data must match defined parameters
* - Comply with message quantity limits according to service package
*
* Reference: https://developers.zalo.me/docs/zalo-notification-service/
*/
class ZNSService {
constructor(client) {
this.client = client;
// Zalo ZNS endpoints - organized by functionality
this.endpoints = {
message: {
sendTemplate: "https://business.openapi.zalo.me/message/template",
sendHashPhone: "https://business.openapi.zalo.me/message/template/hash_phone",
sendDev: "https://business.openapi.zalo.me/message/template/dev",
sendRsa: "https://business.openapi.zalo.me/message/template/rsa",
sendJourney: "https://business.openapi.zalo.me/message/template/journey",
status: "https://business.openapi.zalo.me/message/status",
quota: "https://business.openapi.zalo.me/message/quota",
templateTag: "https://business.openapi.zalo.me/message/template-tag",
},
template: {
list: "https://business.openapi.zalo.me/template/all",
detail: "https://business.openapi.zalo.me/template/info/v2",
create: "https://business.openapi.zalo.me/template/create",
edit: "https://business.openapi.zalo.me/template/edit",
uploadImage: "https://business.openapi.zalo.me/upload/image",
quality: "https://business.openapi.zalo.me/template/quality",
sampleData: "https://business.openapi.zalo.me/template/sample-data",
},
rating: {
get: "https://business.openapi.zalo.me/rating/get",
},
quality: {
oa: "https://business.openapi.zalo.me/quality",
},
};
}
/**
* Send ZNS message
* @param accessToken OA access token
* @param message ZNS message data
* @returns Send result
*/
async sendMessage(accessToken, message) {
try {
const response = await this.client.apiPost(this.endpoints.message.sendTemplate, accessToken, message);
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to send ZNS message");
}
}
/**
* Send ZNS message with phone hash
* @param accessToken OA access token
* @param message ZNS message with hashed phone
* @returns Send result
*/
async sendHashPhoneMessage(accessToken, message) {
try {
const response = await this.client.apiPost(this.endpoints.message.sendHashPhone, accessToken, message);
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to send ZNS hash phone message");
}
}
/**
* Send ZNS message in development mode
* @param accessToken OA access token
* @param message ZNS dev mode message
* @returns Send result
*/
async sendDevModeMessage(accessToken, message) {
try {
const response = await this.client.apiPost(this.endpoints.message.sendDev, accessToken, message);
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to send ZNS dev mode message");
}
}
/**
* Send ZNS message with RSA encryption
* @param accessToken OA access token
* @param message ZNS RSA message
* @returns Send result
*/
async sendRsaMessage(accessToken, message) {
try {
const response = await this.client.apiPost(this.endpoints.message.sendRsa, accessToken, message);
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to send ZNS RSA message");
}
}
/**
* Send ZNS journey message
* @param accessToken OA access token
* @param message ZNS journey message
* @returns Send result
*/
async sendJourneyMessage(accessToken, message) {
try {
const response = await this.client.apiPost(this.endpoints.message.sendJourney, accessToken, message);
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to send ZNS journey message");
}
}
/**
* Get ZNS message status
* @param accessToken OA access token
* @param messageId Message ID
* @returns Message status info
*
* Response data:
* - delivery_time: Thời gian thiết bị nhận được thông báo
* - status: Trạng thái thông báo
* * -1: The message does not exist
* * 0: The message is pushed successfully to Zalo server but has not yet delivered to user's phone
* * 1: The message was delivered to the user's phone
* - message: Mô tả trạng thái thông báo
*/
async getMessageStatus(accessToken, messageId) {
try {
const response = await this.client.apiGet(this.endpoints.message.status, accessToken, { message_id: messageId });
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to get ZNS message status");
}
}
/**
* Get ZNS quota information
* @param accessToken OA access token
* @returns Quota information
*
* Response data:
* - dailyQuota: Số thông báo ZNS OA được gửi trong 1 ngày
* - remainingQuota: Số thông báo ZNS OA được gửi trong ngày còn lại
* - dailyQuotaPromotion: Số tin ZNS hậu mãi OA được gửi trong ngày (null từ 1/11)
* - remainingQuotaPromotion: Số tin ZNS hậu mãi còn lại OA được gửi trong ngày (null từ 1/11)
* - monthlyPromotionQuota: Số tin ZNS hậu mãi OA được gửi trong tháng
* - remainingMonthlyPromotionQuota: Số tin ZNS hậu mãi còn lại OA được gửi trong tháng
* - estimatedNextMonthPromotionQuota: Số tin ZNS hậu mãi dự kiến mà OA có thể gửi trong tháng tiếp theo
*/
async getQuotaInfo(accessToken) {
try {
const response = await this.client.apiGet(this.endpoints.message.quota, accessToken);
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to get ZNS quota info");
}
}
/**
* Get ZNS allowed content types
* @param accessToken OA access token
* @returns Allowed content types
*
* Response data:
* - Mảng các loại nội dung mà OA có thể gửi:
* * "TRANSACTION": Giao dịch (cấp độ 1)
* * "CUSTOMER_CARE": Chăm sóc khách hàng (cấp độ 2)
* * "PROMOTION": Hậu mãi (cấp độ 3)
* - Dựa theo chất lượng gửi ZNS của OA, Zalo sẽ tự động điều chỉnh loại nội dung OA được gửi
*/
async getAllowedContentTypes(accessToken) {
try {
const response = await this.client.apiGet(this.endpoints.message.templateTag, accessToken);
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to get ZNS allowed content types");
}
}
/**
* Get ZNS template list
* @param accessToken OA access token
* @param offset Offset for pagination (template được tạo gần nhất có thứ tự 0)
* @param limit Limit for pagination (tối đa 100)
* @param status Template status filter (optional)
* @returns Template list
*
* Status values:
* - 1: ENABLE templates
* - 2: PENDING_REVIEW templates
* - 3: REJECT templates
* - 4: DISABLE templates
* - undefined: All templates
*/
async getTemplateList(accessToken, offset = 0, limit = 10, status) {
try {
// Validate limit (max 100)
if (limit > 100) {
throw new Error("Limit không được vượt quá 100");
}
// Prepare query parameters
const params = { offset, limit };
if (status !== undefined) {
params.status = status;
}
const response = await this.client.apiGet(this.endpoints.template.list, accessToken, params);
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to get ZNS template list");
}
}
/**
* Get ZNS template details
* @param accessToken OA access token
* @param templateId Template ID
* @returns Template details
*
* Response data:
* - templateId: ID của template
* - templateName: Tên của template
* - status: Trạng thái template (ENABLE, PENDING_REVIEW, DELETE, REJECT, DISABLE)
* - reason: Lý do template có trạng thái hiện tại
* - listParams: Danh sách các thuộc tính của template
* - listButtons: Danh sách các buttons/CTAs của template
* - timeout: Thời gian timeout của template
* - previewUrl: Đường dẫn đến bản xem trước của template
* - templateQuality: Chất lượng template (null từ 10/12)
* - templateTag: Loại nội dung (TRANSACTION, CUSTOMER_CARE, PROMOTION)
* - price: Đơn giá của template
*/
async getTemplateDetails(accessToken, templateId) {
try {
const response = await this.client.apiGet(this.endpoints.template.detail, accessToken, { template_id: templateId });
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to get ZNS template details");
}
}
/**
* Get ZNS template sample data
* @param accessToken OA access token
* @param templateId Template ID
* @returns Template sample data
*
* Response data:
* - Chứa tham số và dữ liệu mẫu của template
* - Ví dụ: { "balance_debt": 2000, "due_date": "01/01/1970", "customer_name": "customer_name_sample" }
*/
async getTemplateSampleData(accessToken, templateId) {
try {
const response = await this.client.apiGet(this.endpoints.template.sampleData, accessToken, { template_id: templateId });
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to get ZNS template sample data");
}
}
/**
* Get customer rating information
* @param accessToken OA access token
* @param templateId Template ID
* @param fromTime Start time (timestamp in milliseconds)
* @param toTime End time (timestamp in milliseconds)
* @param offset Position of first rating to return
* @param limit Maximum number of ratings to return
* @returns Customer rating information
*
* Lưu ý:
* - Chỉ có thể lấy thông tin đánh giá từ template đánh giá dịch vụ được tạo bởi ứng dụng
* - Access token phải ứng với template ID được tạo bởi app và OA
* - Thời gian theo định dạng timestamp (millisecond)
*/
async getCustomerRating(accessToken, templateId, fromTime, toTime, offset, limit) {
try {
const response = await this.client.apiGet(this.endpoints.rating.get, accessToken, {
template_id: templateId,
from_time: fromTime,
to_time: toTime,
offset,
limit,
});
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to get customer rating");
}
}
/**
* Get OA ZNS sending quality information
* @param accessToken OA access token
* @returns OA quality information
*
* Response data:
* - oaCurrentQuality: Chất lượng gửi ZNS trong 48 giờ gần nhất
* - oa7dayQuality: Chất lượng gửi ZNS trong 7 ngày gần nhất
*
* Quality levels:
* - HIGH: Mức độ chất lượng tốt
* - MEDIUM: Mức độ chất lượng trung bình
* - LOW: Mức độ chất lượng kém
* - UNDEFINED: Chưa được xác định (OA không gửi ZNS trong khung thời gian đánh giá)
*/
async getOAQuality(accessToken) {
try {
const response = await this.client.apiGet(this.endpoints.quality.oa, accessToken);
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to get OA quality information");
}
}
/**
* Create ZNS template
* @param accessToken OA access token
* @param templateData Template creation data theo chuẩn Zalo API
* @returns Created template response
*
* API: POST https://business.openapi.zalo.me/template/create
*/
async createTemplate(accessToken, templateData) {
try {
// Validate required fields
if (!templateData.template_name) {
throw new Error("template_name is required");
}
if (!templateData.template_type) {
throw new Error("template_type is required");
}
if (!templateData.tag) {
throw new Error("tag is required");
}
if (!templateData.layout) {
throw new Error("layout is required");
}
if (!templateData.tracking_id) {
throw new Error("tracking_id is required");
}
const response = await this.client.apiPost(this.endpoints.template.create, accessToken, templateData);
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to create ZNS template");
}
}
/**
* Edit ZNS template (chỉnh sửa template có trạng thái REJECT)
* @param accessToken OA access token
* @param templateData Template edit data theo chuẩn Zalo API
* @returns Edited template response
*
* Lưu ý:
* - Chỉ có thể chỉnh sửa template có trạng thái REJECT
* - Template sau khi chỉnh sửa sẽ chuyển về trạng thái PENDING_REVIEW
* - Daily quota: 100 requests/ngày
* - Cần quyền "Quản lý tài sản"
*
* API: POST https://business.openapi.zalo.me/template/edit
*/
async updateTemplate(accessToken, templateData) {
try {
// Validate required fields
if (!templateData.template_id) {
throw new Error("template_id is required");
}
if (!templateData.template_name) {
throw new Error("template_name is required");
}
if (!templateData.template_type) {
throw new Error("template_type is required");
}
if (!templateData.tag) {
throw new Error("tag is required");
}
if (!templateData.layout) {
throw new Error("layout is required");
}
if (!templateData.tracking_id) {
throw new Error("tracking_id is required");
}
const response = await this.client.apiPost(this.endpoints.template.edit, accessToken, templateData);
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to edit ZNS template");
}
}
/**
* Upload image for ZNS template
* @param accessToken OA access token
* @param imageFile Image file (Buffer or ReadableStream)
* @param filename Filename with extension
* @returns Upload result with media_id
*
* Lưu ý:
* - Định dạng hỗ trợ: JPG, PNG
* - Dung lượng tối đa: 500KB
* - Hạn mức: 5000 ảnh/tháng/app
* - Logo: PNG, 400x96px
* - Hình ảnh: JPG/PNG, tỉ lệ 16:9
* - Cần quyền "Quản lý tài sản"
*/
async uploadImage(accessToken, imageFile, filename = "image.jpg") {
try {
// Validate file extension
const allowedExtensions = [".jpg", ".jpeg", ".png"];
const fileExtension = filename
.toLowerCase()
.substring(filename.lastIndexOf("."));
if (!allowedExtensions.includes(fileExtension)) {
throw new Error("File phải có định dạng JPG hoặc PNG");
}
// Validate file size if it's a Buffer (max 500KB)
if (Buffer.isBuffer(imageFile)) {
const maxSize = 500 * 1024; // 500KB
if (imageFile.length > maxSize) {
throw new Error("Dung lượng file không được vượt quá 500KB");
}
}
const response = await this.client.apiUploadFile(this.endpoints.template.uploadImage, accessToken, imageFile, filename);
return response;
}
catch (error) {
throw this.handleZNSError(error, "Failed to upload ZNS image");
}
}
handleZNSError(error, defaultMessage) {
if (error.response?.data) {
const errorData = error.response.data;
return new Error(`${defaultMessage}: ${errorData.message || errorData.error || "Unknown error"}`);
}
return new Error(`${defaultMessage}: ${error.message || "Unknown error"}`);
}
}
exports.ZNSService = ZNSService;
//# sourceMappingURL=zns.service.js.map