UNPKG

@fabrix/spool-cart

Version:

Spool - eCommerce Spool for Fabrix

621 lines (620 loc) 25.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const common_1 = require("@fabrix/fabrix/dist/common"); const _ = require("lodash"); const shortid = require("shortid"); const errors_1 = require("@fabrix/spool-sequelize/dist/errors"); const enums_1 = require("../../enums"); const enums_2 = require("../../enums"); const enums_3 = require("../../enums"); class CartService extends common_1.FabrixService { publish(type, event, options = {}) { if (this.app.services.EventsService) { options.include = options.include || [{ model: this.app.models.EventItem.instance, as: 'objects' }]; return this.app.services.EventsService.publish(type, event, options); } this.app.log.debug('spool-events is not installed, please install it to use publish'); return Promise.resolve(); } create(cart, options = {}) { const Cart = this.app.models['Cart']; if (!cart.line_items) { cart.line_items = []; } const items = cart.line_items; delete cart.line_items; if (cart.shipping_address && !cart.billing_address) { cart.billing_address = cart.shipping_address; } if (cart.billing_address && !cart.shipping_address) { cart.shipping_address = cart.billing_address; } let resCart; return Cart.create({ email: cart.email, shop_id: cart.shop_id, customer_id: cart.customer_id, currency: cart.currency, notes: cart.notes, owners: cart.owners, ip: cart.ip, client_details: cart.client_details, user_id: cart.user_id, status: cart.status || enums_2.CART_STATUS.OPEN }, { include: [ { model: this.app.models['Address'].instance, as: 'shipping_address' }, { model: this.app.models['Address'].instance, as: 'billing_address' } ] }) .then(_cart => { if (!_cart) { throw new Error('Cart was not created'); } resCart = _cart; if (cart.shipping_address && !_.isEmpty(cart.shipping_address)) { return resCart.updateShippingAddress(cart.shipping_address, { transaction: options.transaction || null }); } return; }) .then(() => { if (cart.billing_address) { return resCart.updateBillingAddress(cart.billing_address, { transaction: options.transaction || null }); } return; }) .then(() => { if (resCart.customer_id && !cart.shipping_address) { return resCart.resolveCustomer({ transaction: options.transaction || null }) .then(() => { if (resCart.Customer && resCart.Customer.shipping_address_id) { return resCart.setShipping_address(resCart.Customer.shipping_address_id, { transaction: options.transaction || null }); } return; }); } return; }) .then(() => { if (resCart.customer_id && !cart.billing_address) { return resCart.resolveCustomer({ transaction: options.transaction || null }) .then(() => { if (resCart.Customer && resCart.Customer.billing_address_id) { return resCart.setBilling_address(resCart.Customer.billing_address_id, { transaction: options.transaction || null }); } return; }); } return; }) .then(() => { return Cart.sequelize.Promise.mapSeries(items, item => { return this.app.services.ProductService.resolveItem(item, { transaction: options.transaction || null }); }); }) .then(resolvedItems => { return Cart.sequelize.Promise.mapSeries(resolvedItems, (item, index) => { return resCart.addLine(item, items[index].quantity, items[index].properties, items[index].shop); }); }) .then(() => { return resCart.save({ transaction: options.transaction || null }); }); } update(identifier, cart, options = {}) { const Cart = this.app.models['Cart']; let resCart; const update = _.pick(cart, ['customer_id', 'host', 'ip', 'update_ip', 'client_details']); return Cart.resolve(identifier, { transaction: options.transaction || null }) .then(_cart => { if (!_cart) { throw new Error('Could not resolve Cart'); } resCart = _.extend(_cart, update); if (cart.shipping_address) { return resCart.updateShippingAddress(cart.shipping_address, { transaction: options.transaction || null }); } return; }) .then(() => { if (cart.billing_address) { return resCart.updateBillingAddress(cart.billing_address, { transaction: options.transaction || null }); } return; }) .then(() => { return resCart.save({ transaction: options.transaction || null }); }) .then(() => { return resCart.resolveCustomer({ transaction: options.transaction || null }); }) .then(() => { return resCart.resolveShippingAddress({ transaction: options.transaction || null }); }) .then(() => { return resCart.resolveBillingAddress({ transaction: options.transaction || null }); }); } checkout(req, options = {}) { if (!req.body.cart) { const err = new errors_1.ModelError('E_NOT_FOUND', 'Cart is missing in request'); return Promise.reject(err); } let resOrder; return this.app.models['Cart'].datastore.transaction(t => { options.transaction = t; return this.prepareForOrder(req, { transaction: options.transaction || null }) .then(newOrder => { return this.app.services.OrderService.create(newOrder, { transaction: options.transaction || null }); }) .then(order => { if (!order) { throw new Error('Unexpected error during checkout'); } resOrder = order; return this.afterOrder(req, resOrder, { transaction: options.transaction || null }); }) .then(() => { if (resOrder.customer_id) { const event = { object_id: resOrder.customer_id, object: 'customer', objects: [{ customer: resOrder.customer_id }, { order: resOrder.id }], type: 'customer.cart.checkout', message: `Customer Cart ${resOrder.cart_token} checked out and created Order ${resOrder.name}`, data: resOrder }; return this.publish(event.type, event, { save: true, transaction: options.transaction || null }); } else { return; } }) .then(() => { if (resOrder.financial_status === enums_3.ORDER_FINANCIAL.PARTIALLY_PAID) { return resOrder.sendPartiallyPaidEmail({ transaction: options.transaction || null }); } else if (resOrder.financial_status === enums_3.ORDER_FINANCIAL.PAID) { return resOrder.sendPaidEmail({ transaction: options.transaction || null }); } else { return resOrder.sendCreatedEmail({ transaction: options.transaction || null }); } }) .then(email => { return this.createAndSwitch(req, { transaction: options.transaction || null }); }) .then(newCart => { const results = { cart: newCart, order: resOrder, }; if (resOrder.Customer) { results.customer = resOrder.Customer; } return results; }); }); } prepareForOrder(req, options = {}) { const AccountService = this.app.services.AccountService; const Cart = this.app.models['Cart']; const Customer = this.app.models['Customer']; let resCart, userID; if (req.user && req.user.id) { userID = req.user.id; } return Cart.resolve(req.body.cart, { transaction: options.transaction || null }) .then(_cart => { if (!_cart) { throw new errors_1.ModelError('E_NOT_FOUND', 'Cart Not Found'); } if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) { throw new Error(`Cart is already ${_cart.status}`); } resCart = _cart; if (req.body.email) { resCart.email = req.body.email; } if (req.body.customer && req.body.customer.id) { return resCart.setCustomer(req.body.customer.id, { transaction: options.transaction || null }); } else if (req.body.customer_id) { resCart.customer_id = req.body.customer_id; return resCart.setCustomer(req.body.customer_id, { transaction: options.transaction || null }); } else { return; } }) .then(() => { if (req.body.shipping_address && !_.isEmpty(req.body.shipping_address)) { return resCart.updateShippingAddress(req.body.shipping_address, { transaction: options.transaction || null }); } return; }) .then(() => { return resCart.resolveShippingAddress({ transaction: options.transaction || null }); }) .then(() => { if (req.body.billing_address && !_.isEmpty(req.body.billing_address)) { return resCart.updateBillingAddress(req.body.billing_address, { transaction: options.transaction || null }); } return; }) .then(() => { return resCart.resolveBillingAddress({ transaction: options.transaction || null }); }) .then(() => { if (resCart.email && !resCart.customer_id) { return Customer.resolve({ email: req.body.email, first_name: req.body.first_name, last_name: req.body.last_name, shipping_address: resCart.shipping_address, billing_address: resCart.billing_address, cart: resCart }, { transaction: options.transaction || null, create: true }) .then(customer => { return resCart.setCustomer(customer.id, { transaction: options.transaction || null }); }); } else { return; } }) .then(() => { return resCart.resolveCustomer({ transaction: options.transaction || null }); }) .then(() => { if (!resCart.email && resCart.Customer) { resCart.email = resCart.Customer.email; } if (!resCart.email && !resCart.Customer) { throw new Error('Order Missing Identifier (customer and email), please provide an email address'); } resCart.close(enums_2.CART_STATUS.CLOSED); return resCart.recalculate({ transaction: options.transaction || null }); }) .then(cart => { if (resCart.Customer && (req.body.payment_details && req.body.payment_details.length > 0)) { return AccountService.resolvePaymentDetailsToSources(resCart.Customer, req.body.payment_details, { transaction: options.transaction || null }) .then(paymentDetails => { return paymentDetails; }); } else if (resCart.Customer && (req.body.payment_details && req.body.payment_details.length === 0)) { return resCart.Customer.getDefaultSource({ transaction: options.transaction || null }) .then(source => { if (!source) { return []; } return [{ gateway: source.gateway, source: source, }]; }); } else { return req.body.payment_details; } }) .then(paymentDetails => { return resCart.buildOrder({ client_details: req.body.client_details, ip: req.body.ip, payment_details: paymentDetails, payment_kind: req.body.payment_kind, transaction_kind: req.body.transaction_kind, fulfillment_kind: req.body.fulfillment_kind, processing_method: enums_1.PAYMENT_PROCESSING_METHOD.CHECKOUT, shipping_address: req.body.shipping_address, billing_address: req.body.billing_address, customer_id: resCart.customer_id, email: resCart.email || null, user_id: userID || null, }); }); } afterOrder(req, order, options = {}) { const Cart = this.app.models['Cart']; return Cart.resolve(req.body.cart, { transaction: options.transaction || null }) .then(cart => { cart.ordered(order); return cart.save({ transaction: options.transaction || null }); }); } pricingOverrides(overrides, id, admin, options = {}) { const Cart = this.app.models['Cart']; if (_.isObject(overrides) && overrides.pricing_overrides) { overrides = overrides.pricing_overrides; } overrides = overrides.map(override => { override.admin_id = override.admin_id ? override.admin_id : admin.id; override.price = this.app.services.ProxyCartService.normalizeCurrency(parseInt(override.price, 10)); return override; }); let resCart; return Cart.resolve(id, { transaction: options.transaction || null }) .then(_cart => { if (!_cart) { throw new Error('Cart could not be resolved'); } if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) { throw new Error(`Cart is already ${_cart.status}`); } resCart = _cart; resCart.pricing_overrides = overrides; resCart.pricing_override_id = admin.id; return resCart.save({ transaction: options.transaction || null }); }); } addDiscountToCart(cart, options) { return Promise.resolve(cart); } removeDiscountFromCart(cart, options) { return Promise.resolve(cart); } addCouponToCart(cart, options) { return Promise.resolve(cart); } removeCouponFromCart(cart, options) { return Promise.resolve(cart); } addGiftCardToCart(cart, options) { return Promise.resolve(cart); } removeGiftCardFromCart(cart, options) { return Promise.resolve(cart); } addItemsToCart(items, cart, options = {}) { const Cart = this.app.models['Cart']; if (items.line_items) { items = items.line_items; } let resCart; return Cart.resolve(cart, { transaction: options.transaction || null }) .then(_cart => { if (!_cart) { throw new errors_1.ModelError('E_NOT_FOUND', 'Cart Not Found'); } if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) { throw new Error(`Cart is already ${_cart.status}`); } resCart = _cart; return Cart.sequelize.Promise.mapSeries(items, item => { return this.app.services.ProductService.resolveItem(item, { transaction: options.transaction || null }); }); }) .then(resolvedItems => { return Cart.sequelize.Promise.mapSeries(resolvedItems, (item, index) => { return resCart.addLine(item, items[index].quantity, items[index].properties, items[index].shop, { transaction: options.transaction || null }); }); }) .then(resolvedItems => { return resCart.save({ transaction: options.transaction || null }); }); } removeItemsFromCart(items, cart, options = {}) { const Cart = this.app.models['Cart']; if (items.line_items) { items = items.line_items; } let resCart; return Cart.resolve(cart, { transaction: options.transaction || null }) .then(_cart => { if (!_cart) { throw new errors_1.ModelError('E_NOT_FOUND', 'Cart Not Found'); } if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) { throw new Error(`Cart is already ${_cart.status}`); } resCart = _cart; return Cart.sequelize.Promise.mapSeries(items, item => { return this.app.services.ProductService.resolveItem(item, { transaction: options.transaction || null }); }); }) .then(resolvedItems => { return Cart.sequelize.Promise.mapSeries(resolvedItems, (item, index) => { return resCart.removeLine(item, items[index].quantity, { transaction: options.transaction || null }); }); }) .then(resolvedItems => { return resCart.save({ transaction: options.transaction || null }); }); } clearCart(cart, options = {}) { const Cart = this.app.models['Cart']; let resCart; return Cart.resolve(cart, { transaction: options.transaction || null }) .then(_cart => { if (!_cart) { throw new errors_1.ModelError('E_NOT_FOUND', 'Cart Not Found'); } if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) { throw new Error(`Cart is already ${_cart.status}`); } resCart = _cart; resCart.clear(); return resCart.save({ transaction: options.transaction || null }); }); } createAndSwitch(req, options = {}) { const User = this.app.models['User']; const cart = {}; const owners = []; let customerId; if (req.user) { owners.push(req.user); customerId = req.user.current_customer_id; cart.customer_id = customerId; } if (!customerId && req.customer) { cart.customer_id = req.customer.id; } let resCart, resUser; return this.create(cart, { transaction: options.transaction || null }) .then(createdCart => { if (!createdCart) { throw new Error('New Cart was not able to be created'); } resCart = createdCart; if (req.user) { return User.resolve(req.user, { transaction: options.transaction || null }) .then(_user => { if (!_user) { throw new Error('User could not be resolved'); } resUser = _user; resUser.current_cart_id = resCart.id; return resUser.save({ transaction: options.transaction || null }); }); } else { return; } }) .then(() => { return new Promise((resolve, reject) => { req.loginCart(resCart, (err) => { if (err) { return reject(err); } return resolve(resCart); }); }); }); } addShipping(cart, shipping, options = {}) { if (!shipping) { throw new errors_1.ModelError('E_NOT_FOUND', 'Shipping is not defined'); } let resCart; const Cart = this.app.models['Cart']; return Cart.resolve(cart, options) .then(_cart => { if (!_cart) { throw new errors_1.ModelError('E_NOT_FOUND', 'Cart not found'); } if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) { throw new Error(`Cart is already ${_cart.status}`); } resCart = _cart; return resCart.addShipping(shipping, { transaction: options.transaction || null }); }); } removeShipping(cart, shipping, options = {}) { if (!shipping) { throw new errors_1.ModelError('E_BAD_REQUEST', 'Shipping is not defined'); } let resCart; const Cart = this.app.models['Cart']; return Cart.resolve(cart, options) .then(_cart => { if (!_cart) { throw new errors_1.ModelError('E_NOT_FOUND', 'Cart not found'); } if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) { throw new Error(`Cart is already ${_cart.status}`); } resCart = _cart; return resCart.removeShipping(shipping, { transaction: options.transaction || null }); }); } addTaxes(cart, taxes, options = {}) { if (!taxes) { throw new errors_1.ModelError('E_BAD_REQUEST', 'Taxes is not defined'); } let resCart; const Cart = this.app.models['Cart']; return Cart.resolve(cart, options) .then(_cart => { if (!_cart) { throw new errors_1.ModelError('E_NOT_FOUND', 'Cart not found'); } if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) { throw new Error(`Cart is already ${_cart.status}`); } resCart = _cart; return resCart.addTaxes(taxes, { transaction: options.transaction || null }); }); } removeTaxes(cart, taxes, options = {}) { if (!taxes) { throw new errors_1.ModelError('E_BAD_REQUEST', 'Taxes is not defined'); } let resCart; const Cart = this.app.models['Cart']; return Cart.resolve(cart, options) .then(_cart => { if (!_cart) { throw new errors_1.ModelError('E_NOT_FOUND', 'Cart not found'); } if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(_cart.status) === -1) { throw new Error(`Cart is already ${_cart.status}`); } resCart = _cart; return resCart.removeTaxes(taxes, { transaction: options.transaction || null }); }); } retarget(options) { } beforeCreate(cart, options = {}) { if (cart.ip) { cart.create_ip = cart.ip; } if (!cart.token) { cart.token = `cart_${shortid.generate()}`; } return this.app.models['Shop'].resolve(cart.shop_id, { transaction: options.transaction || null, default: true }) .then(shop => { cart.shop_id = shop.id; return cart.recalculate({ transaction: options.transaction || null }); }) .catch(err => { return cart.recalculate({ transaction: options.transaction || null }); }); } beforeUpdate(cart, options = {}) { if (cart.ip) { cart.update_ip = cart.ip; } if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(cart.status) > -1) { return cart.recalculate({ transaction: options.transaction || null }); } else { return Promise.resolve(cart); } } beforeSave(cart, options = {}) { if ([enums_2.CART_STATUS.OPEN, enums_2.CART_STATUS.DRAFT].indexOf(cart.status) > -1) { return cart.recalculate({ transaction: options.transaction || null }); } else { return Promise.resolve(cart); } } } exports.CartService = CartService;