UNPKG

@apihawk/billia-sdk

Version:

The ApiHawk Billia SDK

443 lines (442 loc) 17 kB
"use strict"; 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 billia_sdk_service_base_1 = require("../lib/billia-sdk-service-base"); const query_builder_1 = require("./common/query-builder"); const to_rest_resource_1 = require("./common/to-rest-resource"); const ORDER_DETAILS_VISIBILITY = 'customer-portal__order-details'; const PENDING_STATUSES = [ 'approved', 'processing', 'in-progress', 'provisioned', 'collapsed' ]; class BilliaSDKOrders extends billia_sdk_service_base_1.BilliaSDKServiceBase { constructor(api, paymentService, catalogService) { super(api); this.paymentService = paymentService; this.catalogService = catalogService; } /** * Lists purchases made by the user. Each purchase contains the nested order lines. * * @param session user session * @param query pagination query * @param options additional options */ getOrders(session, query = {}, options = { paymentMethods: false // whether to fetch payment methods for each purchase }) { return __awaiter(this, void 0, void 0, function* () { // decide if payment methods should be fetched const fetchPaymentMethods = options.paymentMethods ? () => this.paymentService.getPaymentMethods(session, { images: options && options.paymentMethods }) : () => Promise.resolve([]); const q = query_builder_1.default.parse(query); // check if there is a filter by order status // and if the order status is 'pending', // send all 'pending' statuses automatically if (Array.isArray(q.where)) { const statusFilter = q.where.find((f) => f.field === 'order.status'); if (statusFilter && statusFilter.values.indexOf('pending') >= 0) { statusFilter.values = [...statusFilter.values, ...PENDING_STATUSES]; } } const [ordersResponse, paymentMethods] = yield Promise.all([ this.api.call({ url: '/database/order-purchase', method: 'GET', session, query: q, headers: { Accept: 'application/hal+json', 'Accept-Response': 'Advanced' } }), fetchPaymentMethods() ]); const lang = ordersResponse._language.current_language; const ordersList = to_rest_resource_1.toRestResource(ordersResponse, 'order_purchase'); // attach computed properties ordersList.items = ordersList.items.map((item) => { const status = this.calculateOrderStatus(item); const type = this.calculateOrderType(item); const paid = this.calculatePaymentStatus(item); const paymentMethod = paymentMethods.find((method) => method.id === item.payment_method_id); const detailed = Object.assign({}, item, { status, type, paid, notes: [], overview: this.getOrderOverview(item.order, lang), payment_method: paymentMethod }); return detailed; }); return ordersList; }); } /** * Fetches an order (purchase) by its ID. * * @param session user session * @param orderId the ID of the order */ getOrderById(session, orderId) { return __awaiter(this, void 0, void 0, function* () { const response = yield this.api.call({ url: `/database/order-purchase/${orderId}`, method: 'GET', session, headers: { Accept: 'application/hal+json', 'Accept-Response': 'Advanced' } }); const orderLine = response.order[0]; const discountCode = orderLine ? orderLine.discount_code : undefined; const order = response; const uniqueCategoryIds = Array.from(new Set(order.order .filter((o) => !Array.isArray(o.product)) .map((o) => { const p = o.product; return p.category_id; }))); const [notes, paymentMethod, categories] = yield Promise.all([ this.getOrderNotes(session, orderId), this.paymentService .getPaymentMethodById(session, order.payment_method_id) .catch((error) => { if (error.status === 404) { return undefined; } throw error; }), this.catalogService.getAllCategories(uniqueCategoryIds) ]); const categoryByIdMap = new Map(); if (categories) { categories.items.forEach((c) => { categoryByIdMap.set(c.id, c); }); } // add the calculated order metadata order.discount_code = discountCode; order.status = this.calculateOrderStatus(response); order.type = this.calculateOrderType(response); order.paid = this.calculatePaymentStatus(response); if (notes) { order.notes = notes; } order.payment_method = paymentMethod; // attach product category to each order line order.order = order.order.map((o) => { if (Array.isArray(o.product) && o.product.length === 0) { return o; } const p = o.product; p.category = categoryByIdMap.get(p.category_id); o.product = p; return o; }); let totalTax = 0; order.order.map((item) => { totalTax += +item.price.tax.amount; }); order.total_amount.tax = { amount: +totalTax, percent: +order.order[0].price.tax.percent }; return order; }); } /** * Cancels a whole purchase which may consist of multiple order lines (products). * * @param session user session * @param orderId the ID of the order line */ cancelOrder(session, orderId) { return __awaiter(this, void 0, void 0, function* () { const order = yield this.getOrderById(session, orderId); const orderLines = order.order; return Promise.all(orderLines.map((ol) => this.cancelOrderLine(session, ol.order_id))); }); } /** * Cancels the purchase of a single order line (product). * * @param session user session * @param orderLineId the ID of the order line */ cancelOrderLine(session, orderLineId) { return this.api.call({ url: `/database/order/${orderLineId}`, method: 'PATCH', session, body: { status: 'canceled' } }); } /** * Gets detailed information about the order lines (product options) of a order. * * @param session user session * @param orderId the order ID */ getOrderLines(session, orderId) { return __awaiter(this, void 0, void 0, function* () { const filter = { where: [ { field: 'purchase_id', where: 'and', type: 'equalTo', value: orderId } ] }; const response = yield this.api.call({ url: '/database/order?' + filter, method: 'GET', session, headers: { 'Accept-Response': 'Advanced' } }); return this._transformOrderLines(response); }); } /** * Fetches a list of purchases which contain unpaid order lines. * * @param session - user session */ getUnpaidPurchases(session) { return __awaiter(this, void 0, void 0, function* () { const filter = { page_size: -1, where: [ { field: 'paid', where: 'and', type: 'equalTo', value: 0 }, { field: 'status', where: 'and', type: 'notEqualTo', value: 'canceled' } ] }; const ordersResource = yield this.api.call({ url: '/database/order', method: 'GET', query: filter, session }); const unpaidPurchaseIds = Array.from(new Set(ordersResource._embedded.order.map((ol) => ol.purchase_id))); // if there are no unpaid purchases, exit early if (unpaidPurchaseIds.length === 0) { return { items: [], page: 1, page_count: 1, page_size: 25, total_items: 0 }; } const purchasesResource = yield this.getOrders(session, { filter: { purchase_id: unpaidPurchaseIds.map((x) => `${x}`) } }, { paymentMethods: true }); const lang = purchasesResource._language ? purchasesResource._language.current_language : 'en-US'; const purchases = purchasesResource.items.map((purchase) => { purchase.currency = purchase.order[0] ? purchase.order[0].price._currency.currency : ''; purchase.overview = this.getOrderOverview(purchase.order, lang) || ''; return purchase; }); // add currency data to the total return Object.assign({}, purchasesResource, { items: purchases }); }); } /** * Lists all the notes associated with a purchase. * @param session user session * @param orderId order ID */ getOrderNotes(session, orderId) { return __awaiter(this, void 0, void 0, function* () { const response = yield this.api.call({ url: `/database/order-purchase/${orderId}/notes?page_size=-1`, method: 'GET', session }); const notes = response._embedded.notes; return notes; }); } /** * Flattens bundle items which are part of the purchase's ordered items. */ _transformOrderLines(orderLines) { const purchasedItems = orderLines._embedded.order || []; const purchasedItemsCount = purchasedItems.length; let nonBundles = []; let bundles = []; for (let i = 0; i < purchasedItemsCount; ++i) { if (purchasedItems[i].product_type === 'bundle') { bundles.push(purchasedItems[i]); } else { nonBundles.push(purchasedItems[i]); } } bundles = bundles.map((bundle) => { bundle.bundle_items = nonBundles.filter((i) => i.parent_id === bundle.order_id); // calculates the total price of the bundle bundle.price = bundle.bundle_items.reduce((accumulated, current) => { accumulated.total.amount += +current.price.total.amount || 0; accumulated.tax.amount += +current.price.tax.amount || 0; return accumulated; }, bundle.price); return bundle; }); nonBundles = nonBundles.filter((i) => !i.parent_id); const orders = bundles .concat(nonBundles) .map((orderLine) => { orderLine.options = (orderLine.options || []).filter((o) => (o.option.visibility || []).find((v) => v === ORDER_DETAILS_VISIBILITY)); return orderLine; }) .sort((a, b) => a.order_id - b.order_id); return orders; } getOrderOverview(orders, lang = 'en-US') { const DOMAIN_PROVIDERS = [ 'Ficora', 'Ascio', 'ResellerClub', 'RegistryEu', 'RegistryBg' ]; let sharedHostingDomain = null; const firstTwoProducts = []; let currentProduct = ''; let i = 0; let len = 0; if (orders[0] && orders[0].order_type === 'fund') { return 'Adding of funds'; } for (i = 0, len = orders.length; i < len; ++i) { currentProduct = ''; // if there are more than two products, just add elipsis if (i > 1) { return firstTwoProducts.join(', ') + ', ...'; } // when the type of the order is "add funds", // the product property is an empty array if (Array.isArray(orders[i].product)) { return 'Adding of funds'; } const product = orders[i].product; if (product._language.translations.name) { currentProduct = product._language.translations.name[lang].value; } // if the product is a domain - use the product descriptor instead of the name if (DOMAIN_PROVIDERS.indexOf(product.module) > -1) { currentProduct = orders[i].descriptor; } else if (product.module === 'Cpanel') { // show the domain name associated to the shared hosting plan if (orders[i].options) { sharedHostingDomain = (orders[i].options || []).find((option) => option.option.option_key === 'domain' || option.option.option_key === 'DomainName' || option.option.option_key === 'domain_name'); } currentProduct = sharedHostingDomain ? currentProduct + ' (' + sharedHostingDomain.value + ')' : currentProduct; } firstTwoProducts.push(currentProduct); } return firstTwoProducts.join(', '); } /** * Calculates the type of an order * by iterating over the types of its nested order lines. * * @param order - order */ calculateOrderType(order) { const orders = order.order; let i = 0; let len = 0; const typesMap = {}; let typesArray; for (i = 0, len = orders.length; i < len; i++) { typesMap[orders[i].order_type] = true; } typesArray = Object.keys(typesMap); if (typesArray.length > 1) { return 'mixed'; } else { return typesArray[0] || 'unknown'; } } /** * Calculates the status of an order * by iterating over the statuses of its nested order lines. * * @param order - order record */ calculateOrderStatus(order) { const orders = order.order; if (orders.length === 0) { return 'unknown'; } for (let i = 0, len = orders.length; i < len - 1; ++i) { if (orders[i].status !== orders[i + 1].status) { return 'mixed'; } } return orders[0].status ? orders[0].status : 'unknown'; } /** * Calculates the payment status of an order * by iterating over the statuses of its nested order lines. * * @param order - order details */ calculatePaymentStatus(order) { const paidOrders = order.order.filter((o) => { return !!o.paid; }); if (paidOrders.length === 0) { return 'no'; } else if (paidOrders.length === order.order.length) { return 'yes'; } else { return 'partially'; } } } exports.BilliaSDKOrders = BilliaSDKOrders;