@apihawk/billia-sdk
Version:
The ApiHawk Billia SDK
502 lines (501 loc) • 19.2 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const moment = require("moment");
const qs = require("qs");
const billia_sdk_service_base_1 = require("../lib/billia-sdk-service-base");
const billia_sdk_catalog_1 = require("./billia-sdk-catalog");
const query_builder_1 = require("./common/query-builder");
const to_rest_resource_1 = require("./common/to-rest-resource");
const OPTIONS_VISIBILITY = {
'product-details': 'customer-portal__product-details',
'product-configuration': 'customer-portal__product-configuration'
};
class BilliaSDKCustomerProduct extends billia_sdk_service_base_1.BilliaSDKServiceBase {
constructor(api) {
super(api);
this.billiaSDKCatalog = new billia_sdk_catalog_1.BilliaSDKCatalog(api);
}
/**
* Get all customer products
* @param {IApihawkSession} session
* @param {IRestPaginationQuery} pagination
* @returns {Promise<IRestPaginatedResource<ICustomerProduct>>}
*/
getAll(session, pagination) {
return __awaiter(this, void 0, void 0, function* () {
const query = query_builder_1.default.parse(pagination);
const customerProducts = yield this.api.call({
url: `/customer/product`,
method: 'GET',
session,
query,
headers: {
'Accept-Response': 'Advanced'
}
});
return to_rest_resource_1.toRestResource(customerProducts, 'customer_product');
});
}
/**
* Get products by cateogry_id
* @param {number} categoryId
* @param {IApihawkSession} session
* @param {IRestPaginationQuery} pagination
* @returns {Promise<IRestPaginatedResource<ICustomerProduct>>}
*/
getProductsByCategoryId(categoryId, session, pagination) {
if (!pagination.filter) {
pagination.filter = {};
}
if (!pagination.equalTo) {
pagination.equalTo = { 'catalog_product.category_id': `${categoryId}` };
}
else {
pagination.equalTo['catalog_product.category_id'] = `${categoryId}`;
}
// if the client provided filter by category_id (e.g. where in (ids..) )
// remove the equalTo query and use the explicitly set one
if (Array.isArray(pagination.filter['catalog_product.category_id']) &&
pagination.filter['catalog_product.category_id'].length > 0) {
delete pagination.equalTo;
}
return this.getAll(session, pagination);
}
/**
* Get expiring customer products
* @param {IApihawkSession} session user session
* @param {IExpiringProductsPeriod} period filter only products expiring this week/month/quarter
*/
getExpiringProducts(session, period) {
return __awaiter(this, void 0, void 0, function* () {
let days = 31;
switch (period) {
case 'week':
days = 7;
break;
case 'month':
days = 31;
break;
case 'quarter':
days = 31 * 3;
break;
}
const queryFilter = {
where: [
{
field: 'expire_date',
where: 'and',
type: 'lessThanOrEqualTo',
value: moment().add(days, 'days').format('YYYY-MM-DD HH:mm:ss')
},
{
field: 'expire_date',
where: 'and',
type: 'greaterThanOrEqualTo',
value: moment().format('YYYY-MM-DD HH:mm:ss')
},
{
field: 'billing_type',
where: 'and',
type: 'notEqualTo',
value: 'onetime'
},
{
field: 'status',
where: 'and',
type: 'in',
values: ['active', 'expired']
}
],
order: [
{
sort: 'ASC',
field: 'expire_date'
}
]
};
const filter = qs.stringify(queryFilter);
return this.api
.call({
url: `/customer/product?page_size=-1${filter ? '&' + filter : ''}`,
method: 'GET',
session,
headers: {
'Accept-Response': 'Advanced'
}
})
.then((response) => {
const categories = Array.from(new Set(response._embedded.customer_product
.map((p) => (p.product ? p.product.category_id : 0))
.filter((p) => p > 0)));
if (categories.length) {
const filterData = qs.stringify({
where: [
{
field: 'id',
where: 'and',
type: 'in',
values: categories
}
]
});
return this.api
.call({
url: `/billing/category?${filterData}`,
method: 'GET',
headers: {
Accept: 'application/vnd.image+json'
}
})
.then((res) => {
const categoriesDetails = res._embedded.catalog_category || [];
const categoriesById = categoriesDetails.reduce((acc, c) => {
acc[c.id] = c;
return acc;
}, {});
response._embedded.customer_product.forEach((p) => {
if (p.product) {
p.product.category = categoriesById[p.product.category_id];
}
});
return response._embedded.customer_product;
});
}
return [];
});
});
}
/**
* Get customer product by ID
* @param {IApihawkSession} session
* @param {number} customerProductId
* @param {{}} query
* @returns {Promise<ICustomerProduct>}
*/
getCustomerProductById(session, customerProductId, query = {}) {
return __awaiter(this, void 0, void 0, function* () {
const customerProduct = yield this.api.call({
url: `/customer/product/${customerProductId}`,
method: 'GET',
session,
headers: {
'Accept-Response': 'Advanced'
}
});
const catalogProduct = yield this.getCatalogProduct(session, customerProduct.product_id);
customerProduct.options = (catalogProduct.options || [])
.map((catalogOption) => {
const matchingOption = (customerProduct.options || []).find((po) => po.catalog_option_id === catalogOption.option_id);
if (matchingOption) {
return this.catalogOptionToProductOption(customerProduct,
// @ts-ignore
customerProduct.options.find((po) => po.catalog_option_id === catalogOption.option_id), catalogOption);
}
return this.catalogOptionToProductOption(customerProduct,
// @ts-ignore
{
customer_product_option_id: 0,
value: '',
user_id: customerProduct.user_id
}, catalogOption);
})
.filter((o) => !!o)
.map((option, index, arr) => {
// calculates the option's value
option.value = this.getCurrentOptionValue(option);
// finds group option's children and moves them inside the parent group option
if (Array.isArray(option.option.group)) {
return this.attachGroupChildren(option, arr);
}
return option;
})
.filter(this.filterVisibleOptions(query))
.filter((o) => o.option.provider === 'user' || o.option.provider === 'system');
return customerProduct;
});
}
/**
* Gets customer product notes
* @param {IApihawkSession} session
* @param {number} customerProductId
*/
getCustomerProductNotes(session, customerProductId) {
return __awaiter(this, void 0, void 0, function* () {
const notes = yield this.api.call({
url: `/customer/product/${customerProductId}/notes`,
method: 'GET',
session
});
return to_rest_resource_1.toRestResource(notes, 'notes');
});
}
/**
* Edits a customer product.
*
* There are three types of editing:
*
* 1. Renew
* { 'renew': { 'quantity': 12 } }
*
* 2. Edit
* { 'edit': { 'auto_renew': 'disabled', options: [{ 'option_id': 42, value: 'new value' }] } }
*
* 3. Upgrade
* { 'upgrade': { product_id: 1 } }
*
* @param {Object} session - session
* @param {number} productId - customer product ID
* @param {Object} data - request body
* @param {boolean} dryRun - calculate prices without altering the product
*/
edit(session, productId, data = {}, dryRun = false) {
return __awaiter(this, void 0, void 0, function* () {
let queryString = '';
if (dryRun) {
queryString = 'dryrun=true';
}
return this.api.call({
url: `/customer/product-redact/${productId}?${queryString}`,
method: 'POST',
body: data,
session
});
});
}
/**
* Get products count per category.
*
* @param {IApihawkSession} session
* @returns hash-table (category ID -> count)
*/
getProductsCountPerCategory(session) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield this.api.call({
url: '/customer/product-category-counter',
method: 'GET',
session
});
const hashTable = response.reduce((result, category) => {
result[category.category_id] = parseInt(category.count, 10);
return result;
}, {});
return hashTable;
});
}
/**
* Toggle auto-renew
* @param {IApihawkSession} session
* @param {number} productId
* @param {boolean} autoRenew
* @returns {Promise<ICustomerProduct>}
*/
toggleAutoRenew(session, productId, autoRenew) {
return __awaiter(this, void 0, void 0, function* () {
return this.api.call({
url: `/customer/product/${productId}`,
method: 'PATCH',
body: {
auto_renew: autoRenew
},
session
});
});
}
/**
* Check if product can be temporary unsuspended
* @param {IApihawkSession} session
* @param {number} customerProductId
* @returns {Promise<boolean>}
*/
checkIsCustomerProductTemporaryUnsuspendActive(session, customerProductId) {
return __awaiter(this, void 0, void 0, function* () {
return this.api
.call({
url: `/customer/product-temporary-unsuspend?customer_product_id=${customerProductId}`,
method: 'GET',
session
})
.then((result) => result.valid);
});
}
/**
* Temporary unsuspend customer product
* @param {IApihawkSession} session
* @param {number} customerProductId
* @returns {Promise<boolean>}
*/
customerProductTemporaryUnsuspend(session, customerProductId) {
return __awaiter(this, void 0, void 0, function* () {
return this.api
.call({
url: `/customer/product-temporary-unsuspend?customer_product_id=${customerProductId}`,
method: 'POST',
session
})
.then(() => true);
});
}
/**
* Delete customer product option
* @param {IApihawkSession} session
* @param {number} customerProductOptionId
* @returns {Promise<boolean>}
*/
deleteCustomerProductOption(session, customerProductOptionId) {
return __awaiter(this, void 0, void 0, function* () {
return this.api
.call({
url: `/customer/product_option/${customerProductOptionId}`,
method: 'DELETE',
session
})
.then(() => true);
});
}
/**
* Get catalog product
* @param {} session
* @param {number} productId
* @returns {Promise<ICatalogProduct>}
*/
getCatalogProduct(session, productId) {
return __awaiter(this, void 0, void 0, function* () {
return this.api.call({
url: `/billing/product/${productId}`,
method: 'GET',
session,
headers: {
'Accept-Response': 'Advanced'
}
});
});
}
/**
* Hash customer product options
* @param {ICustomerProductOption[]} options
* @param {ICatalogOption[]} catalogProductOptions
* @returns {ICustomerProductOption[]}
*/
hashOptions(options, catalogProductOptions) {
const hashedOptions = options.reduce((hash, option) => {
// @ts-ignore
hash[option.option.option_key] = this.getCurrentOptionValue(option);
return hash;
}, {});
catalogProductOptions.forEach((o) => {
if (!hashedOptions[o.option_key]) {
hashedOptions[o.option_key] = null;
}
});
return hashedOptions;
}
/**
* Calculates the current option's value in order to be
* data-bound to the front-end form controls.
*
* @param {Object} option - product option
*
* @returns {String | number | boolean} - primitive value
*/
getCurrentOptionValue(option) {
if (!option) {
return '';
}
if (typeof option.choice !== 'undefined') {
if (option.choice.name === 'on' || option.choice.name === 'off') {
// in case it's of type 'on/off' - use 'on' or 'off' as value
return option.choice.name;
}
else {
// in case it's another type - use the choice_id as value
return option.choice.choice_id;
}
}
else {
// if there is no choice associated with this option - use the text value
return option.value;
}
}
/**
* Maps a catalog option (not attached to a product)
* to a product option (attached to a product).
*
* @param {ICustomerProduct} product
* @param {ICustomerProductOption} productOption
* @param {ICatalogOption} catalogOption
*
* @returns {ICustomerProductOption} - product option
*/
catalogOptionToProductOption(product, productOption, catalogOption) {
if (!product || !productOption || !catalogOption) {
throw new Error('Missing required paramethers.');
}
const result = {
customer_product_option_id: productOption.customer_product_option_id,
customer_product_id: product.customer_product_id,
catalog_option_id: catalogOption.option_id,
catalog_choice_id: '',
value: productOption.value,
trash: catalogOption.trash || 0,
user_id: productOption.user_id,
state: productOption.state,
previous: productOption.previous
};
result.option = catalogOption;
// if there is a chosen option, find it from the catalog product options
// and assign it in order to send more detailed information to the client
if (productOption.choice) {
result.choice = (catalogOption.choices || []).find((c) => productOption.choice.choice_id === c.choice_id);
}
else {
// otherwise set the default choice
result.choice = (catalogOption.choices || []).find((c) => c.default === 1);
}
return result;
}
/**
* Moves option group children options inside the parent option group.
*
* @param {ICustomerProductOption} optionGroup - product option of type "group"
* @param {ICustomerProductOption[]} allOptions - list of all product options
*
* @returns {ICustomerProductOption} - option group with attached children options
*/
attachGroupChildren(optionGroup, allOptions) {
if (optionGroup.option && Array.isArray(optionGroup.option.group)) {
optionGroup.option.group = allOptions.filter((option) => {
if (!optionGroup.option || !Array.isArray(optionGroup.option.group)) {
return false;
}
return optionGroup.option.group.find((groupChild) => groupChild.option_id === option.catalog_option_id);
});
}
return optionGroup;
}
/**
* Filters visible options according to a given criteria.
*
* @param {Object} query - HTTP query hash
*
* @returns {Function} - predicate function
*/
filterVisibleOptions(query = {}) {
return (option) => {
if (!option.option) {
return false;
}
return query.options_visibility
? (option.option.visibility || []).includes(OPTIONS_VISIBILITY[query.options_visibility])
: true;
};
}
}
exports.BilliaSDKCustomerProduct = BilliaSDKCustomerProduct;