@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
417 lines • 18.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PurchaseService = void 0;
const common_1 = require("../types/common");
const purchase_1 = require("../types/purchase");
/**
* Service xử lý các API mua sản phẩm/dịch vụ OA của Zalo
*
* ĐIỀU KIỆN SỬ DỤNG PURCHASE API:
*
* 1. QUYỀN TRUY CẬP:
* - Ứng dụng cần được cấp quyền quản lý "Mua sản phẩm dịch vụ OA"
* - OA phải được xác minh và có quyền bán sản phẩm/dịch vụ
*
* 2. THỜI GIAN TẠO ĐỚN:
* - Order chỉ được tạo từ 00:01 đến 23:54 hàng ngày
* - Không thể tạo đơn hàng trong khung giờ bảo trì (23:55 - 00:00)
*
* 3. SẢN PHẨM/DỊCH VỤ:
* - Sản phẩm phải có sẵn và được phép bán
* - Đối tượng thụ hưởng (beneficiary) phải phù hợp với sản phẩm
* - Có thể sử dụng product_id HOẶC redeem_code, không được dùng cả hai
*
* 4. THANH TOÁN:
* - Tài khoản ZCA phải có đủ số dư để thanh toán
* - Voucher code (nếu có) phải hợp lệ và chưa hết hạn
*
* 5. XÁC THỰC:
* - Mỗi đơn hàng sẽ có verified_token (OTT) để xác nhận thanh toán
* - OTT có hiệu lực trong vòng 5 phút kể từ khi tạo đơn
*
* LỖI THƯỜNG GẶP:
* - 1001: Beneficiary không hợp lệ
* - 1002: Product ID không tồn tại hoặc không khả dụng
* - 1003: Redeem code không hợp lệ hoặc đã được sử dụng
* - 1004: Voucher code không hợp lệ hoặc đã hết hạn
* - 1005: Sản phẩm không khả dụng cho đối tượng thụ hưởng
* - 1006: Số dư tài khoản không đủ
* - 1007: Ngoài thời gian cho phép tạo đơn hàng
* - 1008: Đơn hàng trùng lặp
* - 1009: Không có quyền truy cập
*/
class PurchaseService {
constructor(client) {
this.client = client;
this.purchaseApiUrl = "https://openapi.zalo.me/v3.0/oa/purchase";
}
/**
* Tạo đơn hàng mua sản phẩm/dịch vụ OA
* @param accessToken Access token của Official Account
* @param request Thông tin đơn hàng cần tạo
* @returns Thông tin đơn hàng đã tạo
*/
async createOrder(accessToken, request) {
try {
// Validate request
this.validateCreateOrderRequest(request);
const result = await this.client.apiPost(`${this.purchaseApiUrl}/create_order`, accessToken, request);
if (result.error !== 0) {
throw new common_1.ZaloSDKError(result.message || "Failed to create order", result.error, result);
}
return result.data;
}
catch (error) {
if (error instanceof common_1.ZaloSDKError) {
throw error;
}
throw new common_1.ZaloSDKError(`Failed to create order: ${error.message}`, purchase_1.PurchaseErrorCode.SYSTEM_ERROR, error);
}
}
/**
* Tạo đơn hàng với product_id
* @param accessToken Access token của Official Account
* @param beneficiary Đối tượng thụ hưởng (OA hoặc APP)
* @param productId ID sản phẩm
* @param voucherCode Mã giảm giá (tùy chọn)
* @returns Thông tin đơn hàng đã tạo
*/
async createOrderWithProduct(accessToken, beneficiary, productId, voucherCode) {
const request = {
beneficiary,
product_id: productId,
...(voucherCode && { voucher_code: voucherCode }),
};
return this.createOrder(accessToken, request);
}
/**
* Tạo đơn hàng với redeem_code (mã quà tặng)
* @param accessToken Access token của Official Account
* @param beneficiary Đối tượng thụ hưởng (OA hoặc APP)
* @param redeemCode Mã quà tặng
* @param voucherCode Mã giảm giá (tùy chọn)
* @returns Thông tin đơn hàng đã tạo
*/
async createOrderWithRedeemCode(accessToken, beneficiary, redeemCode, voucherCode) {
const request = {
beneficiary,
redeem_code: redeemCode,
...(voucherCode && { voucher_code: voucherCode }),
};
return this.createOrder(accessToken, request);
}
/**
* Validate create order request
* @param request Request to validate
*/
validateCreateOrderRequest(request) {
// Validate beneficiary
if (!request.beneficiary || !["OA", "APP"].includes(request.beneficiary)) {
throw new common_1.ZaloSDKError("Beneficiary must be either 'OA' or 'APP'", purchase_1.PurchaseErrorCode.INVALID_BENEFICIARY);
}
// Validate that either product_id or redeem_code is provided, but not both
const hasProductId = (0, purchase_1.isProductOrderRequest)(request);
const hasRedeemCode = (0, purchase_1.isRedeemOrderRequest)(request);
if (!hasProductId && !hasRedeemCode) {
throw new common_1.ZaloSDKError("Either product_id or redeem_code must be provided", purchase_1.PurchaseErrorCode.INVALID_PRODUCT_ID);
}
if (hasProductId && hasRedeemCode) {
throw new common_1.ZaloSDKError("Cannot use both product_id and redeem_code in the same request", purchase_1.PurchaseErrorCode.INVALID_PRODUCT_ID);
}
// Validate product_id
if (hasProductId && (!request.product_id || request.product_id <= 0)) {
throw new common_1.ZaloSDKError("Product ID must be a positive number", purchase_1.PurchaseErrorCode.INVALID_PRODUCT_ID);
}
// Validate redeem_code
if (hasRedeemCode && (!request.redeem_code || request.redeem_code.trim() === "")) {
throw new common_1.ZaloSDKError("Redeem code cannot be empty", purchase_1.PurchaseErrorCode.INVALID_REDEEM_CODE);
}
// Validate voucher_code if provided
if (request.voucher_code && request.voucher_code.trim() === "") {
throw new common_1.ZaloSDKError("Voucher code cannot be empty if provided", purchase_1.PurchaseErrorCode.INVALID_VOUCHER_CODE);
}
}
/**
* Kiểm tra thời gian có thể tạo đơn hàng
* @returns true nếu trong thời gian cho phép tạo đơn hàng
*/
isOrderCreationTimeValid() {
const now = new Date();
const hours = now.getHours();
const minutes = now.getMinutes();
// Order chỉ được tạo từ 00:01 đến 23:54
if (hours === 0 && minutes === 0) {
return false; // 00:00
}
if (hours === 23 && minutes >= 55) {
return false; // 23:55 - 23:59
}
return true;
}
/**
* Tính toán thời gian hết hạn của OTT (One Time Token)
* @param createdTime Thời gian tạo đơn hàng (milliseconds)
* @returns Thời gian hết hạn OTT (milliseconds)
*/
calculateOTTExpiration(createdTime) {
// OTT có hiệu lực trong vòng 5 phút
return createdTime + 5 * 60 * 1000;
}
/**
* Kiểm tra OTT còn hiệu lực hay không
* @param createdTime Thời gian tạo đơn hàng (milliseconds)
* @returns true nếu OTT còn hiệu lực
*/
isOTTValid(createdTime) {
const now = Date.now();
const expirationTime = this.calculateOTTExpiration(createdTime);
return now < expirationTime;
}
/**
* Xác nhận thanh toán đơn hàng
* @param accessToken Access token của Official Account
* @param request Thông tin xác nhận đơn hàng
* @returns Thông tin đơn hàng đã được xác nhận thanh toán
*/
async confirmOrder(accessToken, request) {
try {
// Validate request
this.validateConfirmOrderRequest(request);
const result = await this.client.apiPost(`${this.purchaseApiUrl}/confirm_order`, accessToken, request);
if (result.error !== 0) {
throw new common_1.ZaloSDKError(result.message || "Failed to confirm order", result.error, result);
}
return result.data;
}
catch (error) {
if (error instanceof common_1.ZaloSDKError) {
throw error;
}
throw new common_1.ZaloSDKError(`Failed to confirm order: ${error.message}`, purchase_1.PurchaseErrorCode.SYSTEM_ERROR, error);
}
}
/**
* Xác nhận thanh toán đơn hàng với order ID và verified token
* @param accessToken Access token của Official Account
* @param orderId ID đơn hàng
* @param verifiedToken OTT token để xác nhận
* @returns Thông tin đơn hàng đã được xác nhận thanh toán
*/
async confirmOrderById(accessToken, orderId, verifiedToken) {
const request = {
order_id: orderId,
verified_token: verifiedToken,
};
return this.confirmOrder(accessToken, request);
}
/**
* Lấy thông tin sản phẩm theo ID
* @param productId ID sản phẩm
* @returns Thông tin sản phẩm hoặc undefined nếu không tìm thấy
*/
getProductInfo(productId) {
return purchase_1.PRODUCT_INFO[productId];
}
/**
* Lấy danh sách tất cả sản phẩm có sẵn
* @returns Danh sách thông tin sản phẩm
*/
getAllProducts() {
return Object.values(purchase_1.PRODUCT_INFO);
}
/**
* Lấy danh sách sản phẩm theo loại
* @param category Loại sản phẩm
* @returns Danh sách sản phẩm thuộc loại đó
*/
getProductsByCategory(category) {
return Object.values(purchase_1.PRODUCT_INFO).filter(product => product.category === category);
}
/**
* Lấy danh sách sản phẩm theo đối tượng thụ hưởng
* @param beneficiary Đối tượng thụ hưởng
* @returns Danh sách sản phẩm phù hợp với đối tượng thụ hưởng
*/
getProductsByBeneficiary(beneficiary) {
return Object.values(purchase_1.PRODUCT_INFO).filter(product => product.beneficiary.includes(beneficiary));
}
/**
* Kiểm tra sản phẩm có phù hợp với đối tượng thụ hưởng không
* @param productId ID sản phẩm
* @param beneficiary Đối tượng thụ hưởng
* @returns true nếu sản phẩm phù hợp với đối tượng thụ hưởng
*/
isProductCompatibleWithBeneficiary(productId, beneficiary) {
const product = this.getProductInfo(productId);
if (!product) {
return false;
}
return product.beneficiary.includes(beneficiary);
}
// ==================== SPECIALIZED PRODUCT APIS ====================
/**
* Tạo đơn hàng gói OA Subscription
* @param accessToken Access token của Official Account
* @param subscriptionType Loại gói subscription
* @param voucherCode Mã giảm giá (tùy chọn)
* @returns Thông tin đơn hàng đã tạo
*/
async createOASubscriptionOrder(accessToken, subscriptionType, voucherCode) {
const productIdMap = {
'advanced_6m': purchase_1.OA_PRODUCTS.OA_ADVANCED_6M,
'advanced_12m': purchase_1.OA_PRODUCTS.OA_ADVANCED_12M,
'premium_6m': purchase_1.OA_PRODUCTS.OA_PREMIUM_6M,
'premium_12m': purchase_1.OA_PRODUCTS.OA_PREMIUM_12M,
};
const productId = productIdMap[subscriptionType];
return this.createOrderWithProduct(accessToken, "OA", productId, voucherCode);
}
/**
* Tạo đơn hàng gói GMF (Group Message Framework)
* @param accessToken Access token của Official Account
* @param memberLimit Giới hạn số thành viên
* @param voucherCode Mã giảm giá (tùy chọn)
* @returns Thông tin đơn hàng đã tạo
*/
async createGMFOrder(accessToken, memberLimit, voucherCode) {
const productIdMap = {
10: purchase_1.OA_PRODUCTS.GMF_10_MEMBERS,
50: purchase_1.OA_PRODUCTS.GMF_50_MEMBERS,
100: purchase_1.OA_PRODUCTS.GMF_100_MEMBERS,
1000: purchase_1.OA_PRODUCTS.GMF_1000_MEMBERS,
};
const productId = productIdMap[memberLimit];
return this.createOrderWithProduct(accessToken, "OA", productId, voucherCode);
}
/**
* Tạo đơn hàng gói quota tin nhắn giao dịch
* @param accessToken Access token của Official Account
* @param beneficiary Đối tượng thụ hưởng (OA hoặc APP)
* @param quotaSize Kích thước gói quota
* @param voucherCode Mã giảm giá (tùy chọn)
* @returns Thông tin đơn hàng đã tạo
*/
async createTransactionQuotaOrder(accessToken, beneficiary, quotaSize, voucherCode) {
const productIdMap = {
'5k': purchase_1.OA_PRODUCTS.TRANSACTION_5K,
'50k': purchase_1.OA_PRODUCTS.TRANSACTION_50K,
'500k': purchase_1.OA_PRODUCTS.TRANSACTION_500K,
};
const productId = productIdMap[quotaSize];
// Validate beneficiary compatibility
if (!this.isProductCompatibleWithBeneficiary(productId, beneficiary)) {
throw new common_1.ZaloSDKError(`Product ${quotaSize} transaction quota is not compatible with beneficiary ${beneficiary}`, purchase_1.PurchaseErrorCode.PRODUCT_NOT_AVAILABLE);
}
return this.createOrderWithProduct(accessToken, beneficiary, productId, voucherCode);
}
// ==================== BULK ORDER OPERATIONS ====================
/**
* Tạo nhiều đơn hàng cùng lúc (batch operation)
* @param accessToken Access token của Official Account
* @param orders Danh sách đơn hàng cần tạo
* @returns Danh sách kết quả tạo đơn hàng
*/
async createMultipleOrders(accessToken, orders) {
const results = [];
for (const order of orders) {
try {
const orderData = await this.createOrder(accessToken, order);
results.push({ success: true, data: orderData });
}
catch (error) {
results.push({
success: false,
error: error instanceof common_1.ZaloSDKError ? error.message : error.message
});
}
}
return results;
}
/**
* Tạo combo đơn hàng OA Premium + GMF
* @param accessToken Access token của Official Account
* @param premiumDuration Thời hạn gói Premium
* @param gmfMemberLimit Giới hạn thành viên GMF
* @param voucherCode Mã giảm giá (tùy chọn)
* @returns Danh sách kết quả tạo đơn hàng
*/
async createOAPremiumGMFCombo(accessToken, premiumDuration, gmfMemberLimit, voucherCode) {
const premiumResult = await this.createOASubscriptionOrder(accessToken, premiumDuration === '6m' ? 'premium_6m' : 'premium_12m', voucherCode).then(data => ({ success: true, data }))
.catch(error => ({
success: false,
error: error instanceof common_1.ZaloSDKError ? error.message : error.message
}));
const gmfResult = await this.createGMFOrder(accessToken, gmfMemberLimit, voucherCode).then(data => ({ success: true, data }))
.catch(error => ({
success: false,
error: error instanceof common_1.ZaloSDKError ? error.message : error.message
}));
return {
premium: premiumResult,
gmf: gmfResult
};
}
// ==================== PRODUCT RECOMMENDATION ====================
/**
* Gợi ý sản phẩm dựa trên nhu cầu
* @param requirements Yêu cầu của khách hàng
* @returns Danh sách sản phẩm được gợi ý
*/
recommendProducts(requirements) {
let recommendations = [];
// Filter by beneficiary
const compatibleProducts = this.getProductsByBeneficiary(requirements.beneficiary);
// Add recommendations based on features
if (requirements.features?.includes('subscription')) {
const subscriptionProducts = compatibleProducts.filter(p => p.category === 'subscription');
if (requirements.duration === 'long') {
recommendations.push(...subscriptionProducts.filter(p => p.name.includes('12 tháng')));
}
else {
recommendations.push(...subscriptionProducts.filter(p => p.name.includes('6 tháng')));
}
}
if (requirements.features?.includes('group_messaging')) {
const gmfProducts = compatibleProducts.filter(p => p.category === 'gmf');
if (requirements.budget === 'low') {
recommendations.push(...gmfProducts.filter(p => p.name.includes('10 thành viên')));
}
else if (requirements.budget === 'high') {
recommendations.push(...gmfProducts.filter(p => p.name.includes('1000 thành viên')));
}
else {
recommendations.push(...gmfProducts.filter(p => p.name.includes('50 thành viên') || p.name.includes('100 thành viên')));
}
}
if (requirements.features?.includes('transaction_quota')) {
const quotaProducts = compatibleProducts.filter(p => p.category === 'quota');
if (requirements.budget === 'low') {
recommendations.push(...quotaProducts.filter(p => p.name.includes('5k tin')));
}
else if (requirements.budget === 'high') {
recommendations.push(...quotaProducts.filter(p => p.name.includes('500k tin')));
}
else {
recommendations.push(...quotaProducts.filter(p => p.name.includes('50k tin')));
}
}
// Remove duplicates
return recommendations.filter((product, index, self) => index === self.findIndex(p => p.id === product.id));
}
/**
* Validate confirm order request
* @param request Request to validate
*/
validateConfirmOrderRequest(request) {
// Validate order_id
if (!request.order_id || request.order_id.trim() === "") {
throw new common_1.ZaloSDKError("Order ID cannot be empty", purchase_1.PurchaseErrorCode.INVALID_ORDER_ID);
}
// Validate verified_token
if (!request.verified_token || request.verified_token.trim() === "") {
throw new common_1.ZaloSDKError("Verified token cannot be empty", purchase_1.PurchaseErrorCode.INVALID_VERIFIED_TOKEN);
}
}
}
exports.PurchaseService = PurchaseService;
//# sourceMappingURL=purchase.service.js.map